commit 1ab607f65a44f27e97568ef5dbb49aa7a53c5ca7 Author: 18338781771 Date: Wed May 14 13:19:23 2025 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..516299c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/runtime +/.idea +/.vscode +/vendor +*.log +.env +/tests/tmp +/tests/.phpunit.result.cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2c66292 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 walkor and contributors (see https://github.com/walkor/webman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4031784 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +
+

webman

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

学习

+ + + +
+ +

赞助商

+ +

特别赞助

+ + + + +

铂金赞助

+ + + + +
+ + +
+ +

请作者喝咖啡

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

LICENSE

+The webman is open-sourced software licensed under the MIT. +
+ +
+ + diff --git a/SGHint.zip b/SGHint.zip new file mode 100644 index 0000000..11ffb38 Binary files /dev/null and b/SGHint.zip differ diff --git a/app/apis/controller/AgreementController.php b/app/apis/controller/AgreementController.php new file mode 100644 index 0000000..d21e897 --- /dev/null +++ b/app/apis/controller/AgreementController.php @@ -0,0 +1,109 @@ + $item['id'], + 'key' => $item['key'], + 'status' => $item['status'], + 'title' => isset($item['translation']) ? $item['translation']['title'] : '', + 'content' => isset($item['translation']) ? $item['translation']['content'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'one') { + return [ + 'id' => $item['id'], + 'title' => isset($item['translation']) ? $item['translation']['title'] : '', + 'content' => isset($item['translation']) ? $item['translation']['content'] : '' + // 'title' => isset($item['translation']) ? htmlspecialchars($item['translation']['title'], ENT_NOQUOTES) : '', + // 'content' => isset($item['translation']) ? htmlspecialchars($item['translation']['content'], ENT_NOQUOTES) : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + + public function one(Request $request, string $scene = 'one'): Response + { + try { + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/AgreementTranslationController.php b/app/apis/controller/AgreementTranslationController.php new file mode 100644 index 0000000..28ddd82 --- /dev/null +++ b/app/apis/controller/AgreementTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/BrandController.php b/app/apis/controller/BrandController.php new file mode 100644 index 0000000..6b0c058 --- /dev/null +++ b/app/apis/controller/BrandController.php @@ -0,0 +1,77 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/BrandTranslationController.php b/app/apis/controller/BrandTranslationController.php new file mode 100644 index 0000000..1915397 --- /dev/null +++ b/app/apis/controller/BrandTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/ContactsController.php b/app/apis/controller/ContactsController.php new file mode 100644 index 0000000..4641b7f --- /dev/null +++ b/app/apis/controller/ContactsController.php @@ -0,0 +1,92 @@ +errorResponse($th->getMessage()); + } + } + + /** + * 创建 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function store(Request $request): Response + { + try { + return parent::store($request); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/DictItemController.php b/app/apis/controller/DictItemController.php new file mode 100644 index 0000000..186b9f4 --- /dev/null +++ b/app/apis/controller/DictItemController.php @@ -0,0 +1,122 @@ +errorResponse($th->getMessage()); + } + } + + /** + * 全部 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function all(Request $request, string $scene = 'default'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 单条 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function show(Request $request, int $id, string $scene = 'default'): Response + { + try { + return parent::show($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 单条 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function one(Request $request, string $scene = 'default'): Response + { + try { + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/DictTypeController.php b/app/apis/controller/DictTypeController.php new file mode 100644 index 0000000..c22e729 --- /dev/null +++ b/app/apis/controller/DictTypeController.php @@ -0,0 +1,98 @@ +errorResponse($th->getMessage()); + } + } + + + public function options(Request $request): Response + { + try { + $data = $this->getService()->options(); + return $this->successResponse($data); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function getModels(Request $request): Response + { + try { + $data = $this->getService()->getModels($request->get('brand_id')); + return $this->successResponse($data); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/EnergyController.php b/app/apis/controller/EnergyController.php new file mode 100644 index 0000000..05746d4 --- /dev/null +++ b/app/apis/controller/EnergyController.php @@ -0,0 +1,77 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/EnergyTranslationController.php b/app/apis/controller/EnergyTranslationController.php new file mode 100644 index 0000000..0b2461d --- /dev/null +++ b/app/apis/controller/EnergyTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/LocationController.php b/app/apis/controller/LocationController.php new file mode 100644 index 0000000..90d7421 --- /dev/null +++ b/app/apis/controller/LocationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/LocationTranslationController.php b/app/apis/controller/LocationTranslationController.php new file mode 100644 index 0000000..555c966 --- /dev/null +++ b/app/apis/controller/LocationTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/ModelController.php b/app/apis/controller/ModelController.php new file mode 100644 index 0000000..ee3b34b --- /dev/null +++ b/app/apis/controller/ModelController.php @@ -0,0 +1,122 @@ +errorResponse($th->getMessage()); + } + } + + /** + * 全部 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function all(Request $request, string $scene = 'default'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 单条 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function show(Request $request, int $id, string $scene = 'default'): Response + { + try { + return parent::show($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 单条 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function one(Request $request, string $scene = 'default'): Response + { + try { + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/ModelTranslationController.php b/app/apis/controller/ModelTranslationController.php new file mode 100644 index 0000000..7665701 --- /dev/null +++ b/app/apis/controller/ModelTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/SubscribeController.php b/app/apis/controller/SubscribeController.php new file mode 100644 index 0000000..9febe92 --- /dev/null +++ b/app/apis/controller/SubscribeController.php @@ -0,0 +1,90 @@ +errorResponse($th->getMessage()); + } + } + + + public function store(Request $request): Response + { + try { + $params = $this->validateRequest($request->all(), 'store'); + $params['ip'] = $request->getRealIp(); + $data = $this->getService()->store($params); + return $this->successResponse($data); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/TypesController.php b/app/apis/controller/TypesController.php new file mode 100644 index 0000000..c025dc6 --- /dev/null +++ b/app/apis/controller/TypesController.php @@ -0,0 +1,81 @@ +generatePrivateUrl($item['img']) : ''; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/TypesTranslationController.php b/app/apis/controller/TypesTranslationController.php new file mode 100644 index 0000000..6228fca --- /dev/null +++ b/app/apis/controller/TypesTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/UsersController.php b/app/apis/controller/UsersController.php new file mode 100644 index 0000000..d0feddc --- /dev/null +++ b/app/apis/controller/UsersController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/VehicleAlbumController.php b/app/apis/controller/VehicleAlbumController.php new file mode 100644 index 0000000..ad763da --- /dev/null +++ b/app/apis/controller/VehicleAlbumController.php @@ -0,0 +1,77 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/VehicleDataController.php b/app/apis/controller/VehicleDataController.php new file mode 100644 index 0000000..cd312ee --- /dev/null +++ b/app/apis/controller/VehicleDataController.php @@ -0,0 +1,237 @@ +where('info_status', 1)->first(); + + if ($item['cover']) { + $item['cover'] = (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['cover']); + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + 'brand' => is_array($item['brand']) && $item['brand'] ? $item['brand']['translation']['name'] : '', + 'energy' => is_array($item['energy']) && $item['energy'] ? $item['energy']['translation']['name'] : '', + 'type' => isset($item['types']) && $item['types'] ? $item['types']['translation']['name'] : '', + 'model' => isset($item['models']) && $item['models'] ? $item['models']['translation']['name'] : '', + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + 'location' => $item['location'] ? $item['location']['translation']['name'] : '', + // 'is_random' => $isRandom ? $isRandom->id : 0 + ]; + } + + if ($scene === 'show') { + if ($item['cover']) { + $item['cover'] = (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['cover']); + } + $arr = []; + if (isset($item['album'])) { + $albumFields = \app\console\model\VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + $arr = []; + foreach ($albumFields as $field) { + $arr[] = [ + 'field' => $field, + 'name' => trans($field), + 'value_full' => $item['album'][$field] + ? htmlspecialchars((new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['album'][$field]), ENT_NOQUOTES) + : '', + 'value' => $item['album'][$field] + ]; + } + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + + 'brand_name' => is_array($item['brand']) ? $item['brand']['translation']['name'] : '', + 'energy_name' => is_array($item['energy']) ? $item['energy']['translation']['name'] : '', + 'type_name' => isset($item['types']) ? $item['types']['translation']['name'] : '', + 'model_name' => isset($item['models']) ? $item['models']['translation']['name'] : '', + + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'store_status' => $item['store_status'], + 'store_status_name' => trans(VehicleDataModel::STORE_STATUS_VALUE[$item['store_status']]), + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'location' => isset($item['translation']) ? $item['translation']['location'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'albums' => $arr, + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + + 'brand' => is_array($item['brand']) ? $item['brand']['translation']['brand_id'] : $item['brand'], + 'energy' => is_array($item['energy']) ? $item['energy']['translation']['energy_id'] : $item['energy'], + 'type' => isset($item['types']) ? $item['types']['translation']['type_id'] : $item['type'], + 'model' => isset($item['models']) ? $item['models']['translation']['model_id'] : $item['model'], + 'location_name' => $item['location'] ? $item['location']['translation']['name'] : '', + 'location' => is_array($item['location']) ? $item['location']['translation']['location_id'] : $item['location'], + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + $params['filters'] = []; + $params['filters'][] = ['field' => 'info_status', 'operator' => '=', 'value' => 1]; + // 是否热门 + if (isset($params['is_hot']) && $params['is_hot']) { + $params['page'] = 1; + $params['per_page'] = 4; + $params['filters'][] = ['field' => 'is_hot', 'operator' => '=', 'value' => $params['is_hot']]; + return $params; + } else if (isset($params['vehicle_id']) && $params['vehicle_id']) { + $params['page'] = 1; + $params['per_page'] = 3; + $xgInfo = $this->getService()->find($params['vehicle_id']); + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $xgInfo->brand]; + return $params; + } else { + // 品牌 + if (isset($params['brand']) && $params['brand']) { + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $params['brand']]; + } + // 车型 + if (isset($params['model']) && $params['model']) { + $params['filters'][] = ['field' => 'model', 'operator' => '=', 'value' => $params['model']]; + } + // 能源 + if (isset($params['energy']) && $params['energy']) { + $params['filters'][] = ['field' => 'energy', 'operator' => '=', 'value' => $params['energy']]; + } + // 状态 + if (isset($params['store_status']) && $params['store_status']) { + $params['filters'][] = ['field' => 'store_status', 'operator' => '=', 'value' => $params['store_status']]; + } + // 类型 + if (isset($params['type']) && $params['type']) { + $params['filters'][] = ['field' => 'type', 'operator' => '=', 'value' => $params['type']]; + } + // 价格 + if ((isset($params['min_price']) && $params['min_price']) && (isset($params['max_price']) && $params['max_price'])) { + $params['filters'][] = ['field' => 'price', 'operator' => '>', 'value' => $params['min_price']]; + $params['filters'][] = ['field' => 'price', 'operator' => '<', 'value' => $params['max_price']]; + } + // 里程 + if ((isset($params['min_mileage']) && $params['min_mileage']) && (isset($params['max_mileage']) && $params['max_mileage'])) { + $params['filters'][] = ['field' => 'mileage', 'operator' => '>', 'value' => $params['min_mileage']]; + $params['filters'][] = ['field' => 'mileage', 'operator' => '<', 'value' => $params['max_mileage']]; + } + // 年份 + if ((isset($params['start_year']) && $params['start_year']) && (isset($params['end_year']) && $params['end_year'])) { + $params['filters'][] = ['field' => 'start_use', 'operator' => '=', 'value' => $params['start_year']]; + $params['filters'][] = ['field' => 'end_use', 'operator' => '=', 'value' => $params['end_year']]; + } + } + } + + return $params; + } + + public function postProcessItems(array $items, string $scene): array + { + + if ($scene === 'detail') { + + } + + return $items; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function show(Request $request, int $id, string $scene = 'show'): Response + { + try { + return parent::show($request, $id, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function random(Request $request, string $scene = 'random'): Response + { + try { + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/controller/VehicleDataTranslationController.php b/app/apis/controller/VehicleDataTranslationController.php new file mode 100644 index 0000000..dc3094a --- /dev/null +++ b/app/apis/controller/VehicleDataTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/apis/model/AgreementModel.php b/app/apis/model/AgreementModel.php new file mode 100644 index 0000000..8e81d18 --- /dev/null +++ b/app/apis/model/AgreementModel.php @@ -0,0 +1,62 @@ +hasOne(AgreementTranslationModel::class, 'agreement_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/apis/model/AgreementTranslationModel.php b/app/apis/model/AgreementTranslationModel.php new file mode 100644 index 0000000..d9957f1 --- /dev/null +++ b/app/apis/model/AgreementTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(BrandTranslationModel::class, 'brand_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/apis/model/BrandTranslationModel.php b/app/apis/model/BrandTranslationModel.php new file mode 100644 index 0000000..2169390 --- /dev/null +++ b/app/apis/model/BrandTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(EnergyTranslationModel::class, 'energy_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/apis/model/EnergyTranslationModel.php b/app/apis/model/EnergyTranslationModel.php new file mode 100644 index 0000000..6f2e7ad --- /dev/null +++ b/app/apis/model/EnergyTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(LocationTranslationModel::class, 'location_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/apis/model/LocationTranslationModel.php b/app/apis/model/LocationTranslationModel.php new file mode 100644 index 0000000..a4c0c68 --- /dev/null +++ b/app/apis/model/LocationTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(ModelTranslationModel::class, 'model_id')->where('locale', locale()); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand_id', 'id'); + } +} \ No newline at end of file diff --git a/app/apis/model/ModelTranslationModel.php b/app/apis/model/ModelTranslationModel.php new file mode 100644 index 0000000..b942899 --- /dev/null +++ b/app/apis/model/ModelTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(TypesTranslationModel::class, 'type_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/apis/model/TypesTranslationModel.php b/app/apis/model/TypesTranslationModel.php new file mode 100644 index 0000000..513e923 --- /dev/null +++ b/app/apis/model/TypesTranslationModel.php @@ -0,0 +1,56 @@ +belongsTo(UsersModel::class, 'user_id', 'id'); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand', 'id'); + } + + public function types() + { + return $this->belongsTo(TypesModel::class, 'type', 'id'); + } + + public function energy() + { + return $this->belongsTo(EnergyModel::class, 'energy', 'id'); + } + + public function models() + { + return $this->belongsTo(ModelModel::class, 'model', 'id'); + } + + public function album() + { + return $this->hasOne(VehicleAlbumModel::class, 'vehicle_data_id', 'id'); + } + + public function thumb() + { + return $this->belongsTo(VehicleAlbumModel::class, 'vehicle_id', 'id')->select('front_45_degree'); + } + + public function location() + { + return $this->belongsTo(LocationModel::class, 'location'); + } + + /** + * 单条翻译 + */ + public function translation() + { + return $this->hasOne(VehicleDataTranslationModel::class, 'vehicle_id')->where('locale', locale()); + } + + /** + * 多条翻译 + */ + public function translations() + { + return $this->hasMany(VehicleDataTranslationModel::class, 'vehicle_id'); + } + + + const TRANS_FIELDS = [ + 'name', 'condition_desc' + ]; + + + const STORE_STATUS = [ + 1 => '现货', + 2 => '在途', + 3 => '订购', + ]; + const STORE_STATUS_VALUE = [ + 1 => 'in_stock', + 2 => 'in_transit', + 3 => 'in_order', + ]; +} \ No newline at end of file diff --git a/app/apis/model/VehicleDataTranslationModel.php b/app/apis/model/VehicleDataTranslationModel.php new file mode 100644 index 0000000..e5c3f7b --- /dev/null +++ b/app/apis/model/VehicleDataTranslationModel.php @@ -0,0 +1,56 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'key', 'status', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['agreement_id', 'title', 'content'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'one' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'], + ['field' => 'key', 'operator' => '=', 'value' => 'privacy_web'] + ], + 'fields' => ['id', 'key', 'status', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['agreement_id', 'title', 'content'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/AgreementTranslationService.php b/app/apis/service/AgreementTranslationService.php new file mode 100644 index 0000000..12898ff --- /dev/null +++ b/app/apis/service/AgreementTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/BrandService.php b/app/apis/service/BrandService.php new file mode 100644 index 0000000..8f7aaee --- /dev/null +++ b/app/apis/service/BrandService.php @@ -0,0 +1,48 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/BrandTranslationService.php b/app/apis/service/BrandTranslationService.php new file mode 100644 index 0000000..5f66709 --- /dev/null +++ b/app/apis/service/BrandTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/ContactsService.php b/app/apis/service/ContactsService.php new file mode 100644 index 0000000..ac9d16b --- /dev/null +++ b/app/apis/service/ContactsService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/DictItemService.php b/app/apis/service/DictItemService.php new file mode 100644 index 0000000..113da5c --- /dev/null +++ b/app/apis/service/DictItemService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/DictTypeService.php b/app/apis/service/DictTypeService.php new file mode 100644 index 0000000..6b8e80b --- /dev/null +++ b/app/apis/service/DictTypeService.php @@ -0,0 +1,205 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } + + + public function options() + { + $brand = BrandModel::with('translation')->where('status', 1)->get()->toArray(); + $brands = []; + foreach ($brand as $key => $val) { + $brands[] = [ + 'id' => $val['id'], + 'name' => isset($val['translation']) ? $val['translation']['name'] : '' + ]; + } + + $energy = EnergyModel::with('translation')->where('status', 1)->get()->toArray(); + $energys = []; + foreach ($energy as $key => $val) { + $energys[] = [ + 'id' => $val['id'], + 'name' => isset($val['translation']) ? $val['translation']['name'] : '' + ]; + } + + $type = TypesModel::with('translation')->where('status', 1)->get()->toArray(); + $types = []; + foreach ($type as $key => $val) { + $types[] = [ + 'id' => $val['id'], + 'name' => isset($val['translation']) ? $val['translation']['name'] : '' + ]; + } + + + $min = []; + $max = []; + for ($i = 0; $i <= 20; $i++) { + $min[] = [ + 'value' => $i * 100, + 'name' => $i * 200 . 'km' + ]; + $max[] = [ + 'value' => ($i+1) * 100, + 'name' => ($i+1) * 200 . 'km' + ]; + } + + $data = [ + 'brand' => $brands, + 'energy' => $energys, + 'type' => $types, + 'score_status' => [ + [ + 'id' => 1, + 'name' => trans('in_stock') + ], + [ + 'id' => 2, + 'name' => trans('in_transit') + ], + [ + 'id' => 3, + 'name' => trans('in_order') + + ] + ], + 'price' => [ + [ + 'name' => '0-10w', + 'value' => '0-10', + ], + [ + 'name' => '10-15w', + 'value' => '10-15', + ], + [ + 'name' => '15-20w', + 'value' => '15-20', + ], + [ + 'name' => '20-25w', + 'value' => '20-25', + ], + [ + 'name' => '25-30w', + 'value' => '25-30', + ], + [ + 'name' => '35-40w', + 'value' => '35-40', + ], + [ + 'name' => '45-50w', + 'value' => '45-50', + ], + [ + 'name' => '50+w', + 'value' => '50-9000', + ], + ], + 'mileage' => [ + [ + 'name' => '0-100km', + 'value' => '0-100', + ], + [ + 'name' => '100-300km', + 'value' => '100-300', + ], + [ + 'name' => '300-500km', + 'value' => '300-500', + ], + [ + 'name' => '500-700km', + 'value' => '500-700', + ], + [ + 'name' => '700-900km', + 'value' => '700-900', + ], + [ + 'name' => '1000+km', + 'value' => '1000-90000000', + ], + ] + ]; + + return $data; + } + + + public function getModels(int $brand_id) + { + $model = ModelModel::with('translation')->where('status', 1)->where('brand_id', $brand_id)->get()->toArray(); + $models = []; + foreach ($model as $key => $val) { + $models[] = [ + 'id' => $val['id'], + 'name' => isset($val['translation']) ? $val['translation']['name'] : '' + ]; + } + return $models; + } +} \ No newline at end of file diff --git a/app/apis/service/EnergyService.php b/app/apis/service/EnergyService.php new file mode 100644 index 0000000..4ffc29d --- /dev/null +++ b/app/apis/service/EnergyService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/EnergyTranslationService.php b/app/apis/service/EnergyTranslationService.php new file mode 100644 index 0000000..4929468 --- /dev/null +++ b/app/apis/service/EnergyTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/LocationService.php b/app/apis/service/LocationService.php new file mode 100644 index 0000000..ac2c3a2 --- /dev/null +++ b/app/apis/service/LocationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/LocationTranslationService.php b/app/apis/service/LocationTranslationService.php new file mode 100644 index 0000000..604787c --- /dev/null +++ b/app/apis/service/LocationTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/ModelService.php b/app/apis/service/ModelService.php new file mode 100644 index 0000000..dc20d6d --- /dev/null +++ b/app/apis/service/ModelService.php @@ -0,0 +1,49 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'brand_id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['model_id', 'name'], + 'brand.translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/ModelTranslationService.php b/app/apis/service/ModelTranslationService.php new file mode 100644 index 0000000..8615a69 --- /dev/null +++ b/app/apis/service/ModelTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/SubscribeService.php b/app/apis/service/SubscribeService.php new file mode 100644 index 0000000..9aa97e8 --- /dev/null +++ b/app/apis/service/SubscribeService.php @@ -0,0 +1,72 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } + + + public function store(array $data): Model + { + $info = $this->getModel()::where('email', strtolower($data['email']))->first(); + if ($info) { + return $info; + } + $data['email'] = strtolower($data['email']); + $data['created_at'] = date('Y-m-d H:i:s'); + $data['updated_at'] = date('Y-m-d H:i:s'); + return $this->create($data); + } +} \ No newline at end of file diff --git a/app/apis/service/TypesService.php b/app/apis/service/TypesService.php new file mode 100644 index 0000000..7fca7af --- /dev/null +++ b/app/apis/service/TypesService.php @@ -0,0 +1,49 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + // 'fields' => ['id', 'name', 'image'], + 'fields' => ['id', 'img', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['type_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 6, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/TypesTranslationService.php b/app/apis/service/TypesTranslationService.php new file mode 100644 index 0000000..39a91f6 --- /dev/null +++ b/app/apis/service/TypesTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/UsersService.php b/app/apis/service/UsersService.php new file mode 100644 index 0000000..f9a72ff --- /dev/null +++ b/app/apis/service/UsersService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/VehicleAlbumService.php b/app/apis/service/VehicleAlbumService.php new file mode 100644 index 0000000..5af88e1 --- /dev/null +++ b/app/apis/service/VehicleAlbumService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/VehicleDataService.php b/app/apis/service/VehicleDataService.php new file mode 100644 index 0000000..bbd9220 --- /dev/null +++ b/app/apis/service/VehicleDataService.php @@ -0,0 +1,150 @@ + function(array $params) { + return [ + 'filters' => $params['filters'], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc', 'location'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + 'show' => function(array $params) { + return [ + 'filters' => [], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'store_status', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc', 'location'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + // 新增一个 random 场景 + 'random' => function(array $params) { + return [ + 'filters' => [], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'store_status', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc', 'location'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'custom' => function(\Illuminate\Database\Eloquent\Builder $q) { + $q->inRandomOrder(); + }, + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/apis/service/VehicleDataTranslationService.php b/app/apis/service/VehicleDataTranslationService.php new file mode 100644 index 0000000..111856b --- /dev/null +++ b/app/apis/service/VehicleDataTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/apis/validate/AgreementTranslationValidator.php b/app/apis/validate/AgreementTranslationValidator.php new file mode 100644 index 0000000..b9da402 --- /dev/null +++ b/app/apis/validate/AgreementTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/AgreementValidator.php b/app/apis/validate/AgreementValidator.php new file mode 100644 index 0000000..3983b51 --- /dev/null +++ b/app/apis/validate/AgreementValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/BrandTranslationValidator.php b/app/apis/validate/BrandTranslationValidator.php new file mode 100644 index 0000000..cf6f0ea --- /dev/null +++ b/app/apis/validate/BrandTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/BrandValidator.php b/app/apis/validate/BrandValidator.php new file mode 100644 index 0000000..f2832c3 --- /dev/null +++ b/app/apis/validate/BrandValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/ContactsValidator.php b/app/apis/validate/ContactsValidator.php new file mode 100644 index 0000000..3940352 --- /dev/null +++ b/app/apis/validate/ContactsValidator.php @@ -0,0 +1,65 @@ + 'require|max:255|checkEmptyString', + 'phone' => 'require|checkInternationalPhone', + 'content' => 'require|min:10|max:65535', + 'vehicle_id' => 'integer', + 'email' => 'email' + ]; + + // 错误提示信息 + protected $message = [ + 'name.require' => '姓名不能为空', + 'name.max' => '姓名长度不能超过255个字符', + 'name.checkEmptyString' => '姓名不能只包含空格', + 'phone.require' => '联系电话不能为空', + 'phone.checkInternationalPhone' => '请使用国际格式', + 'content.require' => '内容不能为空', + 'content.min' => '内容至少需要10个字符', + 'content.max' => '内容长度超过最大限制', + 'vehicle_id.integer' => '车辆ID必须为数字', + 'email.email' => '邮箱格式不正确' + ]; + + // 验证场景 + protected $scene = [ + 'store' => ['name', 'phone', 'content', 'email', 'vehicle_id'] + ]; + + // 自定义验证规则 - 检查国际电话号码 + protected function checkInternationalPhone($value) + { + /** + * 国际电话验证规则: + * 1. 必须以 + 开头 + * 2. 国家代码1-4位数字 + * 3. 后续号码总长度8-15位 + * 4. 允许的格式: + * - +8613812345678 + * - +44 20 7123 4567 + * - +1 (800) 555-1234 + */ + $pattern = '/^\+\d{1,4}[\d\s\(\)-]{8,}$/'; + + // 移除所有非必要字符后验证基础长度 + $cleanNumber = preg_replace('/[^\d+]/', '', $value); + + return preg_match($pattern, $value) && + strlen($cleanNumber) >= 9 && // +号 + 至少8位数字 + strlen($cleanNumber) <= 16; // E.164 最大长度15位 + +号 + } + + // 自定义验证规则 - 检查真实有效姓名 + protected function checkEmptyString($value) + { + return trim($value) !== ''; + } +} \ No newline at end of file diff --git a/app/apis/validate/DictItemValidator.php b/app/apis/validate/DictItemValidator.php new file mode 100644 index 0000000..da3db87 --- /dev/null +++ b/app/apis/validate/DictItemValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/DictTypeValidator.php b/app/apis/validate/DictTypeValidator.php new file mode 100644 index 0000000..aad9067 --- /dev/null +++ b/app/apis/validate/DictTypeValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/EnergyTranslationValidator.php b/app/apis/validate/EnergyTranslationValidator.php new file mode 100644 index 0000000..ae9cf00 --- /dev/null +++ b/app/apis/validate/EnergyTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/EnergyValidator.php b/app/apis/validate/EnergyValidator.php new file mode 100644 index 0000000..2664a8b --- /dev/null +++ b/app/apis/validate/EnergyValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/LocationTranslationValidator.php b/app/apis/validate/LocationTranslationValidator.php new file mode 100644 index 0000000..62f306d --- /dev/null +++ b/app/apis/validate/LocationTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/LocationValidator.php b/app/apis/validate/LocationValidator.php new file mode 100644 index 0000000..9dfa03f --- /dev/null +++ b/app/apis/validate/LocationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/ModelTranslationValidator.php b/app/apis/validate/ModelTranslationValidator.php new file mode 100644 index 0000000..d607cb2 --- /dev/null +++ b/app/apis/validate/ModelTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/ModelValidator.php b/app/apis/validate/ModelValidator.php new file mode 100644 index 0000000..dcb2403 --- /dev/null +++ b/app/apis/validate/ModelValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/SubscribeValidator.php b/app/apis/validate/SubscribeValidator.php new file mode 100644 index 0000000..e73faea --- /dev/null +++ b/app/apis/validate/SubscribeValidator.php @@ -0,0 +1,30 @@ + 'require|email' + ]; + + // 提示 + protected $message = [ + 'name.require' => '姓名不能为空', + 'email.email' => '邮箱格式不正确' + ]; + + // 场景 + protected $scene = [ + 'store' => [ + 'email' + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/TypesTranslationValidator.php b/app/apis/validate/TypesTranslationValidator.php new file mode 100644 index 0000000..2c36b75 --- /dev/null +++ b/app/apis/validate/TypesTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/TypesValidator.php b/app/apis/validate/TypesValidator.php new file mode 100644 index 0000000..9593e51 --- /dev/null +++ b/app/apis/validate/TypesValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/UsersValidator.php b/app/apis/validate/UsersValidator.php new file mode 100644 index 0000000..76f51f1 --- /dev/null +++ b/app/apis/validate/UsersValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/VehicleAlbumValidator.php b/app/apis/validate/VehicleAlbumValidator.php new file mode 100644 index 0000000..ddfa3bb --- /dev/null +++ b/app/apis/validate/VehicleAlbumValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/VehicleDataTranslationValidator.php b/app/apis/validate/VehicleDataTranslationValidator.php new file mode 100644 index 0000000..632f016 --- /dev/null +++ b/app/apis/validate/VehicleDataTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/apis/validate/VehicleDataValidator.php b/app/apis/validate/VehicleDataValidator.php new file mode 100644 index 0000000..8b87a2a --- /dev/null +++ b/app/apis/validate/VehicleDataValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/controller/AgreementController.php b/app/applet/controller/AgreementController.php new file mode 100644 index 0000000..69d36b5 --- /dev/null +++ b/app/applet/controller/AgreementController.php @@ -0,0 +1,85 @@ + $item['id'], + 'key' => $item['key'], + 'status' => $item['status'], + 'title' => isset($item['translation']) ? $item['translation']['title'] : '', + 'content' => isset($item['translation']) ? $item['translation']['content'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'one') { + + } + + return $params; + } + + + public function getOne(Request $request, string $agt, string $scene = 'one'): Response + { + try { + $request->setGet(['key' => $agt]); + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/AgreementTranslationController.php b/app/applet/controller/AgreementTranslationController.php new file mode 100644 index 0000000..cc360ec --- /dev/null +++ b/app/applet/controller/AgreementTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/BrandController.php b/app/applet/controller/BrandController.php new file mode 100644 index 0000000..1a92965 --- /dev/null +++ b/app/applet/controller/BrandController.php @@ -0,0 +1,73 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } +} \ No newline at end of file diff --git a/app/applet/controller/BrandTranslationController.php b/app/applet/controller/BrandTranslationController.php new file mode 100644 index 0000000..d1b4b06 --- /dev/null +++ b/app/applet/controller/BrandTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/DraftsController.php b/app/applet/controller/DraftsController.php new file mode 100644 index 0000000..8617d40 --- /dev/null +++ b/app/applet/controller/DraftsController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/EnergyController.php b/app/applet/controller/EnergyController.php new file mode 100644 index 0000000..b984276 --- /dev/null +++ b/app/applet/controller/EnergyController.php @@ -0,0 +1,73 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } +} \ No newline at end of file diff --git a/app/applet/controller/EnergyTranslationController.php b/app/applet/controller/EnergyTranslationController.php new file mode 100644 index 0000000..23af043 --- /dev/null +++ b/app/applet/controller/EnergyTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/LocationController.php b/app/applet/controller/LocationController.php new file mode 100644 index 0000000..3eb9fe3 --- /dev/null +++ b/app/applet/controller/LocationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/LocationTranslationController.php b/app/applet/controller/LocationTranslationController.php new file mode 100644 index 0000000..55f6ed9 --- /dev/null +++ b/app/applet/controller/LocationTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/ModelController.php b/app/applet/controller/ModelController.php new file mode 100644 index 0000000..2643c2e --- /dev/null +++ b/app/applet/controller/ModelController.php @@ -0,0 +1,90 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'brand_id' => $item['brand_id'], + 'brand' => isset($item['brand']) ? $item['brand']['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/ModelTranslationController.php b/app/applet/controller/ModelTranslationController.php new file mode 100644 index 0000000..f02255e --- /dev/null +++ b/app/applet/controller/ModelTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/TypesController.php b/app/applet/controller/TypesController.php new file mode 100644 index 0000000..1841972 --- /dev/null +++ b/app/applet/controller/TypesController.php @@ -0,0 +1,75 @@ + $item['id'], + 'img_full' => $item['img'] ? (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['img']) : '', + 'img' => $item['img'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } +} \ No newline at end of file diff --git a/app/applet/controller/TypesTranslationController.php b/app/applet/controller/TypesTranslationController.php new file mode 100644 index 0000000..1436076 --- /dev/null +++ b/app/applet/controller/TypesTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/UsersController.php b/app/applet/controller/UsersController.php new file mode 100644 index 0000000..bfef499 --- /dev/null +++ b/app/applet/controller/UsersController.php @@ -0,0 +1,126 @@ +errorResponse($th->getMessage()); + } + } + + + /** + * 小程序登录 + */ + public function miniappLogin(Request $request): Response + { + try { + $result = $this->getService()->miniappLogin($request->all()); + return $this->successResponse($result); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + + /** + * 获取手机号 + */ + public function miniappPhoneNumber(Request $request): Response + { + try { + $result = $this->getService()->miniappPhoneNumber($request->all()); + return $this->successResponse($result); + } catch (\Throwable $th) { + var_dump($th->getFile().$th->getLine()); + return $this->errorResponse($th->getMessage()); + } + } + + + public function one(Request $request, string $scene = 'one'): Response + { + try { + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + + public function updateData(Request $request): Response + { + $data = $this->validateRequest($request->all(), 'update'); + $result = $this->getService()->update($request->user->user_id, $data); + return $result + ? $this->successResponse([], '更新成功') + : $this->errorResponse('更新失败'); + } +} \ No newline at end of file diff --git a/app/applet/controller/VehicleAlbumController.php b/app/applet/controller/VehicleAlbumController.php new file mode 100644 index 0000000..650872e --- /dev/null +++ b/app/applet/controller/VehicleAlbumController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/VehicleApplyController.php b/app/applet/controller/VehicleApplyController.php new file mode 100644 index 0000000..c2eb258 --- /dev/null +++ b/app/applet/controller/VehicleApplyController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/VehicleDataController.php b/app/applet/controller/VehicleDataController.php new file mode 100644 index 0000000..c24dbec --- /dev/null +++ b/app/applet/controller/VehicleDataController.php @@ -0,0 +1,227 @@ +generatePrivateUrl($item['cover']); + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + 'brand' => is_array($item['brand']) ? $item['brand']['translation']['name'] : '', + 'energy' => is_array($item['energy']) ? $item['energy']['translation']['name'] : '', + 'type' => isset($item['types']) ? $item['types']['translation']['name'] : '', + 'model' => isset($item['models']) ? $item['models']['translation']['name'] : '', + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + 'location' => $item['location'] ? $item['location']['translation']['name'] : '', + ]; + } + + if ($scene === 'show') { + if ($item['cover']) { + $item['cover_full'] = (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['cover']); + } + $arr = []; + if (isset($item['album'])) { + $albumFields = \app\console\model\VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + $arr = []; + foreach ($albumFields as $field) { + $arr[] = [ + 'field' => $field, + 'name' => trans($field), + 'value_full' => $item['album'][$field] + ? htmlspecialchars((new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['album'][$field]), ENT_NOQUOTES) + : '', + 'value' => $item['album'][$field] + ]; + } + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + + 'brand_name' => is_array($item['brand']) ? $item['brand']['translation']['name'] : '', + 'energy_name' => is_array($item['energy']) ? $item['energy']['translation']['name'] : '', + 'type_name' => isset($item['types']) ? $item['types']['translation']['name'] : '', + 'model_name' => isset($item['models']) ? $item['models']['translation']['name'] : '', + + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'cover_full' => isset($item['cover']) ? $item['cover_full'] : '', + 'albums' => $arr, + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + + 'brand' => is_array($item['brand']) ? $item['brand']['translation']['brand_id'] : $item['brand'], + 'energy' => is_array($item['energy']) ? $item['energy']['translation']['energy_id'] : $item['energy'], + 'type' => isset($item['types']) ? $item['types']['translation']['type_id'] : $item['type'], + 'model' => isset($item['models']) ? $item['models']['translation']['model_id'] : $item['model'], + 'location_name' => $item['location'] ? $item['location']['translation']['name'] : '', + 'location' => is_array($item['location']) ? $item['location']['translation']['location_id'] : $item['location'], + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + $params['filters'] = []; + $params['filters'][] = ['field' => 'info_status', 'operator' => '=', 'value' => 1]; + // 是否热门 + if (isset($params['is_hot']) && $params['is_hot']) { + $params['page'] = 1; + $params['per_page'] = 4; + $params['filters'][] = ['field' => 'is_hot', 'operator' => '=', 'value' => $params['is_hot']]; + return $params; + } else if (isset($params['vehicle_id']) && $params['vehicle_id']) { + $params['page'] = 1; + $params['per_page'] = 3; + $xgInfo = $this->getService()->find($params['vehicle_id']); + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $xgInfo->brand]; + return $params; + } else { + // 品牌 + if (isset($params['brand']) && $params['brand']) { + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $params['brand']]; + } + // 车型 + if (isset($params['model']) && $params['model']) { + $params['filters'][] = ['field' => 'model', 'operator' => '=', 'value' => $params['model']]; + } + // 能源 + if (isset($params['energy']) && $params['energy']) { + $params['filters'][] = ['field' => 'energy', 'operator' => '=', 'value' => $params['energy']]; + } + // 状态 + if (isset($params['store_status']) && $params['store_status']) { + $params['filters'][] = ['field' => 'store_status', 'operator' => '=', 'value' => $params['store_status']]; + } + // 类型 + if (isset($params['type']) && $params['type']) { + $params['filters'][] = ['field' => 'type', 'operator' => '=', 'value' => $params['type']]; + } + // 价格 + if ((isset($params['min_price']) && $params['min_price']) && (isset($params['max_price']) && $params['max_price'])) { + $params['filters'][] = ['field' => 'price', 'operator' => '>', 'value' => $params['min_price']]; + $params['filters'][] = ['field' => 'price', 'operator' => '<', 'value' => $params['max_price']]; + } + // 里程 + if ((isset($params['min_mileage']) && $params['min_mileage']) && (isset($params['max_mileage']) && $params['max_mileage'])) { + $params['filters'][] = ['field' => 'mileage', 'operator' => '>', 'value' => $params['min_mileage']]; + $params['filters'][] = ['field' => 'mileage', 'operator' => '<', 'value' => $params['max_mileage']]; + } + // 年份 + if ((isset($params['start_year']) && $params['start_year']) && (isset($params['end_year']) && $params['end_year'])) { + $params['filters'][] = ['field' => 'start_use', 'operator' => '=', 'value' => $params['start_year']]; + $params['filters'][] = ['field' => 'end_use', 'operator' => '=', 'value' => $params['end_year']]; + } + } + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + $data = $this->injectAdd($request->all()); + return $this->getService()->add($data) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/controller/VehicleDataTranslationController.php b/app/applet/controller/VehicleDataTranslationController.php new file mode 100644 index 0000000..e1b278c --- /dev/null +++ b/app/applet/controller/VehicleDataTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/applet/model/AgreementModel.php b/app/applet/model/AgreementModel.php new file mode 100644 index 0000000..77c6fc7 --- /dev/null +++ b/app/applet/model/AgreementModel.php @@ -0,0 +1,62 @@ +hasOne(AgreementTranslationModel::class, 'agreement_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/applet/model/AgreementTranslationModel.php b/app/applet/model/AgreementTranslationModel.php new file mode 100644 index 0000000..5d45a79 --- /dev/null +++ b/app/applet/model/AgreementTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(BrandTranslationModel::class, 'brand_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/applet/model/BrandTranslationModel.php b/app/applet/model/BrandTranslationModel.php new file mode 100644 index 0000000..cfc8ccd --- /dev/null +++ b/app/applet/model/BrandTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(EnergyTranslationModel::class, 'energy_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/applet/model/EnergyTranslationModel.php b/app/applet/model/EnergyTranslationModel.php new file mode 100644 index 0000000..7c9fb81 --- /dev/null +++ b/app/applet/model/EnergyTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(LocationTranslationModel::class, 'location_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/applet/model/LocationTranslationModel.php b/app/applet/model/LocationTranslationModel.php new file mode 100644 index 0000000..2dfdbc0 --- /dev/null +++ b/app/applet/model/LocationTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(ModelTranslationModel::class, 'model_id')->where('locale', locale()); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand_id', 'id'); + } +} \ No newline at end of file diff --git a/app/applet/model/ModelTranslationModel.php b/app/applet/model/ModelTranslationModel.php new file mode 100644 index 0000000..4a35032 --- /dev/null +++ b/app/applet/model/ModelTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(TypesTranslationModel::class, 'type_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/applet/model/TypesTranslationModel.php b/app/applet/model/TypesTranslationModel.php new file mode 100644 index 0000000..85265ff --- /dev/null +++ b/app/applet/model/TypesTranslationModel.php @@ -0,0 +1,56 @@ +belongsTo(UsersModel::class, 'user_id', 'id'); + } + + public function types() + { + return $this->belongsTo(TypesModel::class, 'type', 'id'); + } + + public function energy() + { + return $this->belongsTo(EnergyModel::class, 'energy', 'id'); + } + + public function album() + { + return $this->hasOne(VehicleAlbumModel::class, 'vehicle_data_id', 'id'); + } + + public function apply() + { + return $this->hasOne(VehicleApplyModel::class, 'vehicle_data_id', 'id'); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand'); + } + + public function models() + { + return $this->belongsTo(ModelModel::class, 'model'); + } + + public function location() + { + return $this->belongsTo(LocationModel::class, 'location'); + } + + /** + * 单条翻译 + */ + public function translation() + { + return $this->hasOne(VehicleDataTranslationModel::class, 'vehicle_id')->where('locale', locale()); + } + + /** + * 多条翻译 + */ + public function translations() + { + return $this->hasMany(VehicleDataTranslationModel::class, 'vehicle_id'); + } +} \ No newline at end of file diff --git a/app/applet/model/VehicleDataTranslationModel.php b/app/applet/model/VehicleDataTranslationModel.php new file mode 100644 index 0000000..4e0c9d8 --- /dev/null +++ b/app/applet/model/VehicleDataTranslationModel.php @@ -0,0 +1,56 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'key', 'operator' => '=', 'value' => $params['key']], + ['field' => 'status', 'operator' => '=', 'value' => 1] + ], + 'fields' => ['id', 'key', 'status', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['agreement_id', 'title', 'content'] + ], + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/AgreementTranslationService.php b/app/applet/service/AgreementTranslationService.php new file mode 100644 index 0000000..02b1a35 --- /dev/null +++ b/app/applet/service/AgreementTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/BrandService.php b/app/applet/service/BrandService.php new file mode 100644 index 0000000..80b7f29 --- /dev/null +++ b/app/applet/service/BrandService.php @@ -0,0 +1,48 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/BrandTranslationService.php b/app/applet/service/BrandTranslationService.php new file mode 100644 index 0000000..dcc6e6c --- /dev/null +++ b/app/applet/service/BrandTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/DraftsService.php b/app/applet/service/DraftsService.php new file mode 100644 index 0000000..31edc6b --- /dev/null +++ b/app/applet/service/DraftsService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/EnergyService.php b/app/applet/service/EnergyService.php new file mode 100644 index 0000000..4f0cbc6 --- /dev/null +++ b/app/applet/service/EnergyService.php @@ -0,0 +1,48 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['energy_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/EnergyTranslationService.php b/app/applet/service/EnergyTranslationService.php new file mode 100644 index 0000000..c002fc6 --- /dev/null +++ b/app/applet/service/EnergyTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/LocationService.php b/app/applet/service/LocationService.php new file mode 100644 index 0000000..07645b3 --- /dev/null +++ b/app/applet/service/LocationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/LocationTranslationService.php b/app/applet/service/LocationTranslationService.php new file mode 100644 index 0000000..9e3d890 --- /dev/null +++ b/app/applet/service/LocationTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/ModelService.php b/app/applet/service/ModelService.php new file mode 100644 index 0000000..c7b59eb --- /dev/null +++ b/app/applet/service/ModelService.php @@ -0,0 +1,50 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'], + ['field' => 'brand_id', 'operator' => '=', 'value' => $params['brand_id']] + ], + 'fields' => ['id', 'brand_id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['model_id', 'name'], + 'brand.translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/ModelTranslationService.php b/app/applet/service/ModelTranslationService.php new file mode 100644 index 0000000..d1e829e --- /dev/null +++ b/app/applet/service/ModelTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/TypesService.php b/app/applet/service/TypesService.php new file mode 100644 index 0000000..5202caa --- /dev/null +++ b/app/applet/service/TypesService.php @@ -0,0 +1,48 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'img', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['type_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/TypesTranslationService.php b/app/applet/service/TypesTranslationService.php new file mode 100644 index 0000000..3f70252 --- /dev/null +++ b/app/applet/service/TypesTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/UsersService.php b/app/applet/service/UsersService.php new file mode 100644 index 0000000..f7b35c2 --- /dev/null +++ b/app/applet/service/UsersService.php @@ -0,0 +1,126 @@ + function ($params) { + $keyword = $params['keyword'] ?? ''; + return [ + 'filters' => [ + ['field' => '***', 'operator' => '=', 'value' => '***'] + ], + 'fields' => ['id'], + 'relations' => [], + 'custom' => function(Builder $q) use ($keyword) { + // 示例 + if ($keyword) { + $q->where(function($q2) use($keyword) { + $q2->whereHas('***', function($q3) use($keyword) { + $q3->where('***', 'like', "%{$keyword}%"); + })->orWhereHas('***', function($q4) use($keyword) { + $q4->where('***', 'like', "%{$keyword}%"); + }); + }); + } + }, + 'sorts' => [ + ['field' => 'created_at', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'one' => function ($params) { + return [ + 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + ], + 'fields' => ['id as user_id', 'user_sn', 'nickname', 'avatar', 'realname', 'phone', 'company', 'openid'], + 'sorts' => [ + ['field' => 'created_at', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } + + /** + * 小程序登录 + */ + public function miniappLogin(array $data) + { + // $wechatService = new WechatService('MiniApp'); + // $wxinfo = $wechatService->xcxOpenid($data['code']); + + $wxinfo['openid'] = md5('test1234567'); + + // 编号 + $userSn = (new SnGenerator())->generateUniqueUserId(); + + // 用户信息 + $user = $this->model->firstOrCreate( + ['openid' => $wxinfo['openid']], + ['nickname' => '微信用户_'.$userSn, 'gender' => 0, 'user_sn' => $userSn, 'avatar' => getenv('MAIN_DOMAIN').'/avatar.png'] + ); + + // 更新编号 + if (!$user->user_sn) { + $user->user_sn = $userSn; + $user->save(); + } + + $user->user_id = $user->id; + return [ + 'openid' => $user->openid, + 'phone' => $user->phone, + 'token' => (new JwtService())->generateToken($user->toArray()) + ]; + } + + /** + * 获取手机号 + */ + public function miniappPhoneNumber(array $data) + { + // 手机号 + $wechatService = new WechatService('MiniApp'); + $wxinfo = $wechatService->xcxPhoneNumber($data['phone_code']); + + $uinfo = $this->model->where('id', $data['user_id'])->first(); + $uinfo->phone = $wxinfo['phone_info']['phoneNumber']; + $uinfo->save(); + + return $wxinfo; + } +} \ No newline at end of file diff --git a/app/applet/service/VehicleAlbumService.php b/app/applet/service/VehicleAlbumService.php new file mode 100644 index 0000000..8bc1bfb --- /dev/null +++ b/app/applet/service/VehicleAlbumService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/VehicleApplyService.php b/app/applet/service/VehicleApplyService.php new file mode 100644 index 0000000..1ca346e --- /dev/null +++ b/app/applet/service/VehicleApplyService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/service/VehicleDataService.php b/app/applet/service/VehicleDataService.php new file mode 100644 index 0000000..77b68e9 --- /dev/null +++ b/app/applet/service/VehicleDataService.php @@ -0,0 +1,262 @@ + function(array $params) { + return [ + 'filters' => $params['filters'], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + 'show' => function(array $params) { + return [ + 'filters' => [ + ['field'=>'id', 'operator'=>'=', 'value'=> $params['id']], + ], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + ]; + } + + + // 声明需要多语言翻译 + protected array $translatable = [ + 'name', + 'condition_desc', + // 'location' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + $albums = VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + foreach ($params as $ko => $vo) { + if (in_array($ko, $albums)) { + $album[$ko] = $vo ?? ''; + } else { + if (in_array($ko, VehicleDataModel::TRANS_FIELDS)) { + $trans[$ko] = $vo ?? ''; + } else { + $data[$ko] = $vo ?? ''; + } + } + } + + Db::beginTransaction(); + try { + $info = $this->create($data); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'vehicle', + $info->id, + $sourceLocale, + $sourceFields + ); + + $album['vehicle_data_id'] = $info->id; + $albumModel = new VehicleAlbumModel(); + if (!$albumModel->create($album)) { + throw new BusinessException('提交失败'); + } + + $applyModel = new VehicleApplyModel(); + if (!$applyModel->create([ + 'vehicle_data_id' => $info->id, + 'desc' => '提交审核' + ])) { + throw new BusinessException('提交失败'); + } + + if (isset($params['draft_id']) && $params['draft_id']) { + if (!DraftsModel::where('id', $params['draft_id'])->delete()) { + throw new BusinessException('草稿移除失败'); + } + } + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + $album = []; + $trans = []; + $albums = VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + foreach ($params as $ko => $vo) { + if (in_array($ko, $albums)) { + $album[$ko] = $vo ?? ''; + } else { + if (in_array($ko, VehicleDataModel::TRANS_FIELDS)) { + $trans[$ko] = $vo ?? ''; + } else { + $data[$ko] = $vo ?? ''; + } + } + } + + Db::beginTransaction(); + try { + $params['info_status'] = 0; + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'vehicle', + $id, + $sourceLocale, + $sourceFields + ); + + if ($album) { + // $album['vehicle_data_id'] = $info->id; + $albumModel = new VehicleAlbumModel(); + if (!$albumModel->updateOrCreate( + ['vehicle_data_id' => $id], + $album + )) { + throw new BusinessException('提交失败'); + } + } + + $applyModel = new VehicleApplyModel(); + if (!$applyModel->create([ + 'vehicle_data_id' => $id, + 'desc' => '提交审核' + ])) { + throw new BusinessException('提交失败'); + } + + if (isset($params['draft_id']) && $params['draft_id']) { + if (!DraftsModel::where('id', $params['draft_id'])->delete()) { + throw new BusinessException('草稿移除失败'); + } + } + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/applet/service/VehicleDataTranslationService.php b/app/applet/service/VehicleDataTranslationService.php new file mode 100644 index 0000000..a1f9e6b --- /dev/null +++ b/app/applet/service/VehicleDataTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/applet/validate/AgreementTranslationValidator.php b/app/applet/validate/AgreementTranslationValidator.php new file mode 100644 index 0000000..d1518da --- /dev/null +++ b/app/applet/validate/AgreementTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/AgreementValidator.php b/app/applet/validate/AgreementValidator.php new file mode 100644 index 0000000..0df8306 --- /dev/null +++ b/app/applet/validate/AgreementValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/BrandTranslationValidator.php b/app/applet/validate/BrandTranslationValidator.php new file mode 100644 index 0000000..2924bbe --- /dev/null +++ b/app/applet/validate/BrandTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/BrandValidator.php b/app/applet/validate/BrandValidator.php new file mode 100644 index 0000000..a4cf08d --- /dev/null +++ b/app/applet/validate/BrandValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/DraftsValidator.php b/app/applet/validate/DraftsValidator.php new file mode 100644 index 0000000..493ceb6 --- /dev/null +++ b/app/applet/validate/DraftsValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/EnergyTranslationValidator.php b/app/applet/validate/EnergyTranslationValidator.php new file mode 100644 index 0000000..2b8a5c5 --- /dev/null +++ b/app/applet/validate/EnergyTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/EnergyValidator.php b/app/applet/validate/EnergyValidator.php new file mode 100644 index 0000000..f894d6d --- /dev/null +++ b/app/applet/validate/EnergyValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/LocationTranslationValidator.php b/app/applet/validate/LocationTranslationValidator.php new file mode 100644 index 0000000..292d104 --- /dev/null +++ b/app/applet/validate/LocationTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/LocationValidator.php b/app/applet/validate/LocationValidator.php new file mode 100644 index 0000000..76a6c59 --- /dev/null +++ b/app/applet/validate/LocationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/ModelTranslationValidator.php b/app/applet/validate/ModelTranslationValidator.php new file mode 100644 index 0000000..e25fc3f --- /dev/null +++ b/app/applet/validate/ModelTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/ModelValidator.php b/app/applet/validate/ModelValidator.php new file mode 100644 index 0000000..6fdcf48 --- /dev/null +++ b/app/applet/validate/ModelValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/TypesTranslationValidator.php b/app/applet/validate/TypesTranslationValidator.php new file mode 100644 index 0000000..bb67da3 --- /dev/null +++ b/app/applet/validate/TypesTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/TypesValidator.php b/app/applet/validate/TypesValidator.php new file mode 100644 index 0000000..603b0ca --- /dev/null +++ b/app/applet/validate/TypesValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/UsersValidator.php b/app/applet/validate/UsersValidator.php new file mode 100644 index 0000000..a3a0df6 --- /dev/null +++ b/app/applet/validate/UsersValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/VehicleAlbumValidator.php b/app/applet/validate/VehicleAlbumValidator.php new file mode 100644 index 0000000..99f5524 --- /dev/null +++ b/app/applet/validate/VehicleAlbumValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/VehicleApplyValidator.php b/app/applet/validate/VehicleApplyValidator.php new file mode 100644 index 0000000..7c5d71f --- /dev/null +++ b/app/applet/validate/VehicleApplyValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/VehicleDataTranslationValidator.php b/app/applet/validate/VehicleDataTranslationValidator.php new file mode 100644 index 0000000..74c4bc5 --- /dev/null +++ b/app/applet/validate/VehicleDataTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/applet/validate/VehicleDataValidator.php b/app/applet/validate/VehicleDataValidator.php new file mode 100644 index 0000000..738e07a --- /dev/null +++ b/app/applet/validate/VehicleDataValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/command/SyncCommand.php b/app/command/SyncCommand.php new file mode 100644 index 0000000..f2f96cb --- /dev/null +++ b/app/command/SyncCommand.php @@ -0,0 +1,100 @@ +energy(); + + return self::SUCCESS; + } + + public function energy() + { + try { + $lis = Db::table('tb_energy')->get(); + foreach ($lis as $key => $val) { + $getId = Db::table('hs_energy')->insertGetId([ + 'status' => 1, + 'sort' => 0, + 'created_at' => time(), + 'updated_at' => time() + ]); + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'energy', + $getId, + 'zh', + ['name'=>$val->name] + ); + } + } catch (\Throwable $th) { + var_dump($th->getFile().'-'.$th->getLine().'-'.$th->getMessage()); + } + } + + public function types() + { + try { + $lis = Db::table('tb_types')->get(); + foreach ($lis as $key => $val) { + $getId = Db::table('hs_types')->insertGetId([ + 'img' => '', + 'status' => 1, + 'sort' => 0, + 'created_at' => time(), + 'updated_at' => time() + ]); + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'type', + $getId, + 'zh', + ['name'=>$val->name] + ); + } + } catch (\Throwable $th) { + var_dump($th->getFile().'-'.$th->getLine().'-'.$th->getMessage()); + } + } + + public function brand() + { + try { + $lis = Db::table('tb_brand')->get(); + foreach ($lis as $key => $val) { + $getId = Db::table('hs_brand')->insertGetId([ + 'status' => 1, + 'sort' => 0, + 'created_at' => time(), + 'updated_at' => time() + ]); + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'brand', + $getId, + 'zh', + ['name'=>$val->name] + ); + } + } catch (\Throwable $th) { + var_dump($th->getFile().'-'.$th->getLine().'-'.$th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/AgreementController.php b/app/console/controller/AgreementController.php new file mode 100644 index 0000000..6169b63 --- /dev/null +++ b/app/console/controller/AgreementController.php @@ -0,0 +1,111 @@ + $item['id'], + 'key' => $item['key'], + 'status' => $item['status'], + 'title' => isset($item['translation']) ? $item['translation']['title'] : '', + 'content' => isset($item['translation']) ? $item['translation']['content'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/AgreementTranslationController.php b/app/console/controller/AgreementTranslationController.php new file mode 100644 index 0000000..f7727b3 --- /dev/null +++ b/app/console/controller/AgreementTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/BrandController.php b/app/console/controller/BrandController.php new file mode 100644 index 0000000..c6c5307 --- /dev/null +++ b/app/console/controller/BrandController.php @@ -0,0 +1,129 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'preset') { + return [ + 'id' => $item['id'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 预设 + */ + public function presets(Request $request, string $scene = 'preset'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/BrandTranslationController.php b/app/console/controller/BrandTranslationController.php new file mode 100644 index 0000000..32bf524 --- /dev/null +++ b/app/console/controller/BrandTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/DraftsController.php b/app/console/controller/DraftsController.php new file mode 100644 index 0000000..f5083a5 --- /dev/null +++ b/app/console/controller/DraftsController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/EnergyController.php b/app/console/controller/EnergyController.php new file mode 100644 index 0000000..dd52cdc --- /dev/null +++ b/app/console/controller/EnergyController.php @@ -0,0 +1,129 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'preset') { + return [ + 'id' => $item['id'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 预设 + */ + public function presets(Request $request, string $scene = 'preset'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/EnergyTranslationController.php b/app/console/controller/EnergyTranslationController.php new file mode 100644 index 0000000..e17531d --- /dev/null +++ b/app/console/controller/EnergyTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/HomeController.php b/app/console/controller/HomeController.php new file mode 100644 index 0000000..e1e6e35 --- /dev/null +++ b/app/console/controller/HomeController.php @@ -0,0 +1,62 @@ +count('id'); + $inOrderCounts = VehicleDataModel::where('store_status', 3)->count('id'); + $brandCounts = BrandModel::count('id'); + + $data = [ + 'vehicleCounts' => $vehicleCounts, + 'inTransitCounts' => $inTransitCounts, + 'inOrderCounts' => $inOrderCounts, + 'brandCounts' => $brandCounts + ]; + return $this->successResponse($data); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/LocationController.php b/app/console/controller/LocationController.php new file mode 100644 index 0000000..5d77939 --- /dev/null +++ b/app/console/controller/LocationController.php @@ -0,0 +1,126 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'preset') { + return [ + 'id' => $item['id'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function presets(Request $request, string $scene = 'preset'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/LocationTranslationController.php b/app/console/controller/LocationTranslationController.php new file mode 100644 index 0000000..dd173ac --- /dev/null +++ b/app/console/controller/LocationTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/ModelController.php b/app/console/controller/ModelController.php new file mode 100644 index 0000000..6e7b271 --- /dev/null +++ b/app/console/controller/ModelController.php @@ -0,0 +1,131 @@ + $item['id'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'brand_id' => $item['brand_id'], + 'brand' => isset($item['brand']) ? $item['brand']['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'preset') { + return [ + 'id' => $item['id'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 预设 + */ + public function presets(Request $request, string $scene = 'preset'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/ModelTranslationController.php b/app/console/controller/ModelTranslationController.php new file mode 100644 index 0000000..72588f4 --- /dev/null +++ b/app/console/controller/ModelTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/TypesController.php b/app/console/controller/TypesController.php new file mode 100644 index 0000000..e21caba --- /dev/null +++ b/app/console/controller/TypesController.php @@ -0,0 +1,131 @@ + $item['id'], + 'img_full' => $item['img'] ? (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['img']) : '', + 'img' => $item['img'], + 'status' => $item['status'], + 'sort' => $item['sort'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + if ($scene === 'preset') { + return [ + 'id' => $item['id'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '' + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 预设 + */ + public function presets(Request $request, string $scene = 'preset'): Response + { + try { + return parent::all($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/TypesTranslationController.php b/app/console/controller/TypesTranslationController.php new file mode 100644 index 0000000..e276ed5 --- /dev/null +++ b/app/console/controller/TypesTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/UsersController.php b/app/console/controller/UsersController.php new file mode 100644 index 0000000..b97c42c --- /dev/null +++ b/app/console/controller/UsersController.php @@ -0,0 +1,77 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/VehicleAlbumController.php b/app/console/controller/VehicleAlbumController.php new file mode 100644 index 0000000..2717417 --- /dev/null +++ b/app/console/controller/VehicleAlbumController.php @@ -0,0 +1,77 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/VehicleApplyController.php b/app/console/controller/VehicleApplyController.php new file mode 100644 index 0000000..b478c1f --- /dev/null +++ b/app/console/controller/VehicleApplyController.php @@ -0,0 +1,101 @@ + $item['id'], + 'vehicle_data_id' => $item['vehicle_data_id'], + 'apply_status' => $item['apply_status'], + 'desc' => $item['desc'], + 'name' => $item['vehicleDataInfo'] ? $item['vehicleDataInfo']['translation']['name'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']) + ]; + } + + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + + public function doApply(Request $request, int $id): Response + { + try { + return $this->getService()->doApply($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/VehicleDataController.php b/app/console/controller/VehicleDataController.php new file mode 100644 index 0000000..c48c631 --- /dev/null +++ b/app/console/controller/VehicleDataController.php @@ -0,0 +1,248 @@ +generatePrivateUrl($item['cover']); + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + 'brand' => is_array($item['brand']) ? $item['brand']['translation']['name'] : '', + 'energy' => is_array($item['energy']) ? $item['energy']['translation']['name'] : '', + 'type' => isset($item['types']) ? $item['types']['translation']['name'] : '', + 'model' => isset($item['models']) ? $item['models']['translation']['name'] : '', + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + 'location' => $item['location'] ? $item['location']['translation']['name'] : '', + ]; + } + + if ($scene === 'show') { + if ($item['cover']) { + $item['cover_full'] = (new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['cover']); + } + $arr = []; + if (isset($item['album'])) { + $albumFields = \app\console\model\VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + $arr = []; + foreach ($albumFields as $field) { + $arr[] = [ + 'field' => $field, + 'name' => trans($field), + 'value_full' => $item['album'][$field] + ? htmlspecialchars((new \app\mzl\modules\upload\service\OssService())->generatePrivateUrl($item['album'][$field]), ENT_NOQUOTES) + : '', + 'value' => $item['album'][$field] + ]; + } + } + return [ + 'id' => $item['id'], + 'vin' => $item['vin'], + 'user_nickname' => isset($item['user']) ? $item['user']['nickname'] : '', + 'price' => $item['price'], + + 'brand_name' => is_array($item['brand']) ? $item['brand']['translation']['name'] : '', + 'energy_name' => is_array($item['energy']) ? $item['energy']['translation']['name'] : '', + 'type_name' => isset($item['types']) ? $item['types']['translation']['name'] : '', + 'model_name' => isset($item['models']) ? $item['models']['translation']['name'] : '', + + 'mileage' => $item['mileage'], + 'vehicle_age' => $item['vehicle_age'], + 'views' => $item['views'], + 'is_hot' => $item['is_hot'], + 'name' => isset($item['translation']) ? $item['translation']['name'] : '', + 'condition_desc' => isset($item['translation']) ? $item['translation']['condition_desc'] : '', + 'cover' => isset($item['cover']) ? $item['cover'] : '', + 'cover_full' => isset($item['cover']) ? $item['cover_full'] : '', + 'albums' => $arr, + 'created_at' => date('Y-m-d H:i:s', $item['created_at']), + 'updated_at' => date('Y-m-d H:i:s', $item['updated_at']), + + 'brand' => is_array($item['brand']) ? $item['brand']['translation']['brand_id'] : $item['brand'], + 'energy' => is_array($item['energy']) ? $item['energy']['translation']['energy_id'] : $item['energy'], + 'type' => isset($item['types']) ? $item['types']['translation']['type_id'] : $item['type'], + 'model' => isset($item['models']) ? $item['models']['translation']['model_id'] : $item['model'], + 'location_name' => $item['location'] ? $item['location']['translation']['name'] : '', + 'location' => is_array($item['location']) ? $item['location']['translation']['location_id'] : $item['location'], + ]; + } + return $item; + } + + /** + * 参数预处理 (根据场景处理) + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + $params = parent::prepareParams($request, $scene); + + if ($scene === 'default') { + $params['filters'] = []; + $params['filters'][] = ['field' => 'info_status', 'operator' => '=', 'value' => 1]; + // 是否热门 + if (isset($params['is_hot']) && $params['is_hot']) { + $params['page'] = 1; + $params['per_page'] = 4; + $params['filters'][] = ['field' => 'is_hot', 'operator' => '=', 'value' => $params['is_hot']]; + return $params; + } else if (isset($params['vehicle_id']) && $params['vehicle_id']) { + $params['page'] = 1; + $params['per_page'] = 3; + $xgInfo = $this->getService()->find($params['vehicle_id']); + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $xgInfo->brand]; + return $params; + } else { + // 品牌 + if (isset($params['brand']) && $params['brand']) { + $params['filters'][] = ['field' => 'brand', 'operator' => '=', 'value' => $params['brand']]; + } + // 车型 + if (isset($params['model']) && $params['model']) { + $params['filters'][] = ['field' => 'model', 'operator' => '=', 'value' => $params['model']]; + } + // 能源 + if (isset($params['energy']) && $params['energy']) { + $params['filters'][] = ['field' => 'energy', 'operator' => '=', 'value' => $params['energy']]; + } + // 状态 + if (isset($params['store_status']) && $params['store_status']) { + $params['filters'][] = ['field' => 'store_status', 'operator' => '=', 'value' => $params['store_status']]; + } + // 类型 + if (isset($params['type']) && $params['type']) { + $params['filters'][] = ['field' => 'type', 'operator' => '=', 'value' => $params['type']]; + } + // 价格 + if ((isset($params['min_price']) && $params['min_price']) && (isset($params['max_price']) && $params['max_price'])) { + $params['filters'][] = ['field' => 'price', 'operator' => '>', 'value' => $params['min_price']]; + $params['filters'][] = ['field' => 'price', 'operator' => '<', 'value' => $params['max_price']]; + } + // 里程 + if ((isset($params['min_mileage']) && $params['min_mileage']) && (isset($params['max_mileage']) && $params['max_mileage'])) { + $params['filters'][] = ['field' => 'mileage', 'operator' => '>', 'value' => $params['min_mileage']]; + $params['filters'][] = ['field' => 'mileage', 'operator' => '<', 'value' => $params['max_mileage']]; + } + // 年份 + if ((isset($params['start_year']) && $params['start_year']) && (isset($params['end_year']) && $params['end_year'])) { + $params['filters'][] = ['field' => 'start_use', 'operator' => '=', 'value' => $params['start_year']]; + $params['filters'][] = ['field' => 'end_use', 'operator' => '=', 'value' => $params['end_year']]; + } + } + } + + return $params; + } + + /** + * 分页 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + try { + return parent::index($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function detail(Request $request, int $id, string $scene = 'show'): Response + { + try { + $request->setGet(['id'=>$id]); + return parent::one($request, $scene); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function add(Request $request): Response + { + try { + return $this->getService()->add($request->all()) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + public function edit(Request $request, int $id): Response + { + try { + return $this->getService()->edit($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } + + /** + * 操作销售状态 + */ + public function doSaled(Request $request, int $id): Response + { + try { + return $this->getService()->doSaled($request->all(), $id) + ? $this->successResponse() + : $this->errorResponse(); + } catch (\Throwable $th) { + return $this->errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/controller/VehicleDataTranslationController.php b/app/console/controller/VehicleDataTranslationController.php new file mode 100644 index 0000000..9cebd24 --- /dev/null +++ b/app/console/controller/VehicleDataTranslationController.php @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/console/model/AgreementModel.php b/app/console/model/AgreementModel.php new file mode 100644 index 0000000..d94ff92 --- /dev/null +++ b/app/console/model/AgreementModel.php @@ -0,0 +1,61 @@ +hasOne(AgreementTranslationModel::class, 'agreement_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/console/model/AgreementTranslationModel.php b/app/console/model/AgreementTranslationModel.php new file mode 100644 index 0000000..5f0e494 --- /dev/null +++ b/app/console/model/AgreementTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(BrandTranslationModel::class, 'brand_id')->where('locale', locale()); + } + + public function translations() + { + return $this->hasMany(BrandTranslationModel::class, 'brand_id'); + } +} \ No newline at end of file diff --git a/app/console/model/BrandTranslationModel.php b/app/console/model/BrandTranslationModel.php new file mode 100644 index 0000000..aff14ad --- /dev/null +++ b/app/console/model/BrandTranslationModel.php @@ -0,0 +1,62 @@ +belongsTo(BrandModel::class, 'brand_id'); + } +} \ No newline at end of file diff --git a/app/console/model/DraftsModel.php b/app/console/model/DraftsModel.php new file mode 100644 index 0000000..ad3214a --- /dev/null +++ b/app/console/model/DraftsModel.php @@ -0,0 +1,56 @@ +hasOne(EnergyTranslationModel::class, 'energy_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/console/model/EnergyTranslationModel.php b/app/console/model/EnergyTranslationModel.php new file mode 100644 index 0000000..df4a49b --- /dev/null +++ b/app/console/model/EnergyTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(LocationTranslationModel::class, 'location_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/console/model/LocationTranslationModel.php b/app/console/model/LocationTranslationModel.php new file mode 100644 index 0000000..5c5ba0d --- /dev/null +++ b/app/console/model/LocationTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(ModelTranslationModel::class, 'model_id')->where('locale', locale()); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand_id', 'id'); + } +} \ No newline at end of file diff --git a/app/console/model/ModelTranslationModel.php b/app/console/model/ModelTranslationModel.php new file mode 100644 index 0000000..4abc23f --- /dev/null +++ b/app/console/model/ModelTranslationModel.php @@ -0,0 +1,56 @@ +hasOne(TypesTranslationModel::class, 'type_id')->where('locale', locale()); + } +} \ No newline at end of file diff --git a/app/console/model/TypesTranslationModel.php b/app/console/model/TypesTranslationModel.php new file mode 100644 index 0000000..3139cae --- /dev/null +++ b/app/console/model/TypesTranslationModel.php @@ -0,0 +1,56 @@ +belongsTo(VehicleDataModel::class, 'vehicle_data_id', 'id'); + } +} \ No newline at end of file diff --git a/app/console/model/VehicleDataModel.php b/app/console/model/VehicleDataModel.php new file mode 100644 index 0000000..a307c41 --- /dev/null +++ b/app/console/model/VehicleDataModel.php @@ -0,0 +1,118 @@ +belongsTo(UsersModel::class, 'user_id', 'id'); + } + + public function types() + { + return $this->belongsTo(TypesModel::class, 'type', 'id'); + } + + public function energy() + { + return $this->belongsTo(EnergyModel::class, 'energy', 'id'); + } + + public function album() + { + return $this->hasOne(VehicleAlbumModel::class, 'vehicle_data_id', 'id'); + } + + public function apply() + { + return $this->hasOne(VehicleApplyModel::class, 'vehicle_data_id', 'id'); + } + + public function brand() + { + return $this->belongsTo(BrandModel::class, 'brand'); + } + + public function models() + { + return $this->belongsTo(ModelModel::class, 'model'); + } + + public function location() + { + return $this->belongsTo(LocationModel::class, 'location'); + } + + /** + * 单条翻译 + */ + public function translation() + { + return $this->hasOne(VehicleDataTranslationModel::class, 'vehicle_id')->where('locale', locale()); + } + + /** + * 多条翻译 + */ + public function translations() + { + return $this->hasMany(VehicleDataTranslationModel::class, 'vehicle_id'); + } + + + const TRANS_FIELDS = [ + 'name', 'condition_desc' + ]; +} \ No newline at end of file diff --git a/app/console/model/VehicleDataTranslationModel.php b/app/console/model/VehicleDataTranslationModel.php new file mode 100644 index 0000000..107dace --- /dev/null +++ b/app/console/model/VehicleDataTranslationModel.php @@ -0,0 +1,62 @@ +belongsTo(VehicleDataModel::class, 'vehicle_id'); + } +} \ No newline at end of file diff --git a/app/console/service/AgreementService.php b/app/console/service/AgreementService.php new file mode 100644 index 0000000..a6f06cc --- /dev/null +++ b/app/console/service/AgreementService.php @@ -0,0 +1,117 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'key', 'status', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['agreement_id', 'title', 'content'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } + + protected array $translatable = [ + 'title', 'content' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'agreement', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'agreement', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/AgreementTranslationService.php b/app/console/service/AgreementTranslationService.php new file mode 100644 index 0000000..ba0dd15 --- /dev/null +++ b/app/console/service/AgreementTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/BrandService.php b/app/console/service/BrandService.php new file mode 100644 index 0000000..7b0a4ff --- /dev/null +++ b/app/console/service/BrandService.php @@ -0,0 +1,132 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'preset' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id'], + 'relations' => [ + 'translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ] + ]; + } + ]; + } + + + protected array $translatable = [ + 'name' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'brand', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'brand', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/BrandTranslationService.php b/app/console/service/BrandTranslationService.php new file mode 100644 index 0000000..8993d65 --- /dev/null +++ b/app/console/service/BrandTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/DraftsService.php b/app/console/service/DraftsService.php new file mode 100644 index 0000000..a59f58a --- /dev/null +++ b/app/console/service/DraftsService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/EnergyService.php b/app/console/service/EnergyService.php new file mode 100644 index 0000000..80b63a0 --- /dev/null +++ b/app/console/service/EnergyService.php @@ -0,0 +1,131 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['energy_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'preset' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id'], + 'relations' => [ + 'translation' => ['energy_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ] + ]; + } + ]; + } + + protected array $translatable = [ + 'name' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'energy', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'energy', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/EnergyTranslationService.php b/app/console/service/EnergyTranslationService.php new file mode 100644 index 0000000..049fc70 --- /dev/null +++ b/app/console/service/EnergyTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/LocationService.php b/app/console/service/LocationService.php new file mode 100644 index 0000000..db2eb17 --- /dev/null +++ b/app/console/service/LocationService.php @@ -0,0 +1,131 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'preset' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id'], + 'relations' => [ + 'translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ] + ]; + } + ]; + } + + protected array $translatable = [ + 'name' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'location', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'location', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/LocationTranslationService.php b/app/console/service/LocationTranslationService.php new file mode 100644 index 0000000..30e5eb8 --- /dev/null +++ b/app/console/service/LocationTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/ModelService.php b/app/console/service/ModelService.php new file mode 100644 index 0000000..5469df9 --- /dev/null +++ b/app/console/service/ModelService.php @@ -0,0 +1,133 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'brand_id', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['model_id', 'name'], + 'brand.translation' => ['brand_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'preset' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'], + ['field' => 'brand_id', 'operator' => '=', 'value' => $params['brand_id']] + ], + 'fields' => ['id'], + 'relations' => [ + 'translation' => ['model_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ] + ]; + } + ]; + } + + protected array $translatable = [ + 'name' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'model', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'model', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/ModelTranslationService.php b/app/console/service/ModelTranslationService.php new file mode 100644 index 0000000..18b257a --- /dev/null +++ b/app/console/service/ModelTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/TypesService.php b/app/console/service/TypesService.php new file mode 100644 index 0000000..438fae4 --- /dev/null +++ b/app/console/service/TypesService.php @@ -0,0 +1,131 @@ + function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id', 'img', 'status', 'sort', 'created_at', 'updated_at'], + 'relations' => [ + 'translation' => ['type_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + }, + 'preset' => function ($params) { + return [ + 'filters' => [ + ['field' => 'status', 'operator' => '=', 'value' => '1'] + ], + 'fields' => ['id'], + 'relations' => [ + 'translation' => ['type_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ] + ]; + } + ]; + } + + protected array $translatable = [ + 'name' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->create($params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'type', + $info->id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'type', + $id, + $sourceLocale, + $sourceFields + ); + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/TypesTranslationService.php b/app/console/service/TypesTranslationService.php new file mode 100644 index 0000000..ed2ca3d --- /dev/null +++ b/app/console/service/TypesTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/UsersService.php b/app/console/service/UsersService.php new file mode 100644 index 0000000..4088c4d --- /dev/null +++ b/app/console/service/UsersService.php @@ -0,0 +1,45 @@ + function ($params) { + return [ + 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + ], + 'fields' => ['id', 'user_sn', 'nickname', 'avatar', 'realname', 'phone', 'company', 'created_at', 'updated_at'], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/VehicleAlbumService.php b/app/console/service/VehicleAlbumService.php new file mode 100644 index 0000000..f0cb077 --- /dev/null +++ b/app/console/service/VehicleAlbumService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/service/VehicleApplyService.php b/app/console/service/VehicleApplyService.php new file mode 100644 index 0000000..b255de4 --- /dev/null +++ b/app/console/service/VehicleApplyService.php @@ -0,0 +1,80 @@ + function ($params) { + return [ + 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + ], + 'fields' => ['id', 'vehicle_data_id', 'apply_status', 'desc', 'created_at', 'updated_at'], + 'relations' => [ + 'vehicleDataInfo.translation' => ['vehicle_id', 'name'] + ], + 'sorts' => [ + ['field' => 'id', 'direction' => 'desc'] + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ] + ]; + } + ]; + } + + /** + * 操作审核 + */ + public function doApply(array $data, int $id): bool + { + $info = $this->model->where('id', $id)->first(); + + if (!$info) { + throw new BusinessException('未找到待审核信息', 404); + } + + $info->apply_status = $data['apply_status']; + $info->desc = $data['desc']; + $info->admin_id = 1; + + $vDataInfo = VehicleDataModel::where('id', $info->vehicle_data_id)->first(); + + if (!$vDataInfo) { + throw new BusinessException('未找到审核信息关联的车辆信息', 404); + } + + $vDataInfo->info_status = $data['apply_status']; + + if (!$info->save() || !$vDataInfo->save()) { + throw new BusinessException('审核失败,请重试', 500); + } + + return true; + } +} \ No newline at end of file diff --git a/app/console/service/VehicleDataService.php b/app/console/service/VehicleDataService.php new file mode 100644 index 0000000..9e0698c --- /dev/null +++ b/app/console/service/VehicleDataService.php @@ -0,0 +1,285 @@ + function(array $params) { + return [ + 'filters' => $params['filters'], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + 'show' => function(array $params) { + return [ + 'filters' => [ + ['field'=>'id', 'operator'=>'=', 'value'=> $params['id']], + ], + 'fields' => [ + 'id','vin','price','cover', + 'type','energy','model','mileage','vehicle_age', + 'views','is_hot','created_at','updated_at', 'brand', 'model', 'user_id', 'location' + ], + 'relations' => [ + 'translation' => ['vehicle_id', 'name','condition_desc'], + 'brand.translation' => ['brand_id', 'name'], + 'album' => [ + 'id', 'vehicle_data_id', 'front_45_degree', + 'front_left_45_degree', 'front_left_headlight', + 'front_view', 'lower_front', 'wheel_hub', + 'front_right_seat', 'right_lower_side', + 'rear_right_taillight', 'rear_right_45_degree', + 'lower_rear', 'trunk', 'left_lower_side', + 'rear_seats', 'center_console', 'control_panel', + 'gear_shift', 'car_key', 'steering_wheel', + 'dashboard', 'odometer_reading', + 'front_left_door_panel', 'front_left_door_function_area', + 'front_left_seat', 'interior_ceiling' + ], + 'user' => ['realname', 'nickname'], + 'types.translation' => ['type_id', 'name'], + 'energy.translation' => ['energy_id', 'name'], + 'models.translation' => ['model_id', 'name'], + 'user' => ['nickname'], + 'location.translation' => ['location_id', 'name'] + ], + 'sorts' => [ + ['field'=>'id','direction'=>'desc'], + ], + 'pagination' => [ + 'page' => $params['page'] ?? 1, + 'per_page' => $params['per_page'] ?? 10, + ], + ]; + }, + ]; + } + + + /** + * 操作销售状态 + */ + public function doSaled(array $data, int $id): bool + { + $info = $this->model->where('id', $id)->first(); + + if (!$info) { + throw new BusinessException('未找到车辆信息', 404); + } + + $info->is_saled = $data['is_saled']; + + if (!$info->save()) { + throw new BusinessException('设置失败,请重试', 500); + } + + return true; + } + + + // 声明需要多语言翻译 + protected array $translatable = [ + 'name', + 'condition_desc', + // 'location' + ]; + + public function add(array $params) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + $albums = VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + foreach ($params as $ko => $vo) { + if (in_array($ko, $albums)) { + $album[$ko] = $vo ?? ''; + } else { + if (in_array($ko, VehicleDataModel::TRANS_FIELDS)) { + $trans[$ko] = $vo ?? ''; + } else { + $data[$ko] = $vo ?? ''; + } + } + } + + Db::beginTransaction(); + try { + $data['info_status'] = 1; + $info = $this->create($data); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'vehicle', + $info->id, + $sourceLocale, + $sourceFields + ); + + $album['vehicle_data_id'] = $info->id; + $albumModel = new VehicleAlbumModel(); + if (!$albumModel->create($album)) { + throw new BusinessException('提交失败'); + } + + // $applyModel = new VehicleApplyModel(); + // if (!$applyModel->create([ + // 'vehicle_data_id' => $info->id, + // 'desc' => '提交审核' + // ])) { + // throw new BusinessException('提交失败'); + // } + + // if (isset($params['draft_id']) && $params['draft_id']) { + // if (!DraftsModel::where('id', $params['draft_id'])->delete()) { + // throw new BusinessException('草稿移除失败'); + // } + // } + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } + + public function edit(array $params, int $id) + { + $sourceLocale = locale(); + $sourceFields = $this->extractSourceFields($params); + if (empty($sourceFields)) { + throw new \InvalidArgumentException("请传递至少一条 {$sourceLocale} 语言的翻译字段"); + }; + + $album = []; + $trans = []; + $albums = VehicleAlbumModel::VEHICLE_ALBUM_FIELDS; + foreach ($params as $ko => $vo) { + if (in_array($ko, $albums)) { + $album[$ko] = $vo ?? ''; + } else { + if (in_array($ko, VehicleDataModel::TRANS_FIELDS)) { + $trans[$ko] = $vo ?? ''; + } else { + $data[$ko] = $vo ?? ''; + } + } + } + + Db::beginTransaction(); + try { + $info = $this->update($id, $params); + if (!$info) { + throw new BusinessException('提交失败'); + } + + // 异步翻译并写入 hs_vehicle_data_translation 表 + (new TranslationService())->queueTranslations( + 'vehicle', + $id, + $sourceLocale, + $sourceFields + ); + + if ($album) { + // $album['vehicle_data_id'] = $info->id; + $albumModel = new VehicleAlbumModel(); + if (!$albumModel->updateOrCreate( + ['vehicle_data_id' => $id], + $album + )) { + throw new BusinessException('提交失败'); + } + } + + // $applyModel = new VehicleApplyModel(); + // if (!$applyModel->create([ + // 'vehicle_data_id' => $info->id, + // 'desc' => '提交审核' + // ])) { + // throw new BusinessException('提交失败'); + // } + + // if (isset($params['draft_id']) && $params['draft_id']) { + // if (!DraftsModel::where('id', $params['draft_id'])->delete()) { + // throw new BusinessException('草稿移除失败'); + // } + // } + + Db::commit(); + return true; + } catch (\Throwable $th) { + Db::rollBack(); + throw $th; + } + } +} \ No newline at end of file diff --git a/app/console/service/VehicleDataTranslationService.php b/app/console/service/VehicleDataTranslationService.php new file mode 100644 index 0000000..31a60fc --- /dev/null +++ b/app/console/service/VehicleDataTranslationService.php @@ -0,0 +1,59 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/console/validate/AgreementTranslationValidator.php b/app/console/validate/AgreementTranslationValidator.php new file mode 100644 index 0000000..4eb1d86 --- /dev/null +++ b/app/console/validate/AgreementTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/AgreementValidator.php b/app/console/validate/AgreementValidator.php new file mode 100644 index 0000000..a579969 --- /dev/null +++ b/app/console/validate/AgreementValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/BrandTranslationValidator.php b/app/console/validate/BrandTranslationValidator.php new file mode 100644 index 0000000..00c9c75 --- /dev/null +++ b/app/console/validate/BrandTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/BrandValidator.php b/app/console/validate/BrandValidator.php new file mode 100644 index 0000000..f798ce2 --- /dev/null +++ b/app/console/validate/BrandValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/DraftsValidator.php b/app/console/validate/DraftsValidator.php new file mode 100644 index 0000000..f110bcf --- /dev/null +++ b/app/console/validate/DraftsValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/EnergyTranslationValidator.php b/app/console/validate/EnergyTranslationValidator.php new file mode 100644 index 0000000..76ea50a --- /dev/null +++ b/app/console/validate/EnergyTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/EnergyValidator.php b/app/console/validate/EnergyValidator.php new file mode 100644 index 0000000..57e1442 --- /dev/null +++ b/app/console/validate/EnergyValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/LocationTranslationValidator.php b/app/console/validate/LocationTranslationValidator.php new file mode 100644 index 0000000..6def34b --- /dev/null +++ b/app/console/validate/LocationTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/LocationValidator.php b/app/console/validate/LocationValidator.php new file mode 100644 index 0000000..19eb635 --- /dev/null +++ b/app/console/validate/LocationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/ModelTranslationValidator.php b/app/console/validate/ModelTranslationValidator.php new file mode 100644 index 0000000..81c4b42 --- /dev/null +++ b/app/console/validate/ModelTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/ModelValidator.php b/app/console/validate/ModelValidator.php new file mode 100644 index 0000000..67fac40 --- /dev/null +++ b/app/console/validate/ModelValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/TypesTranslationValidator.php b/app/console/validate/TypesTranslationValidator.php new file mode 100644 index 0000000..5a6567c --- /dev/null +++ b/app/console/validate/TypesTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/TypesValidator.php b/app/console/validate/TypesValidator.php new file mode 100644 index 0000000..1ebf2f1 --- /dev/null +++ b/app/console/validate/TypesValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/UsersValidator.php b/app/console/validate/UsersValidator.php new file mode 100644 index 0000000..b2c19a0 --- /dev/null +++ b/app/console/validate/UsersValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/VehicleAlbumValidator.php b/app/console/validate/VehicleAlbumValidator.php new file mode 100644 index 0000000..9e28195 --- /dev/null +++ b/app/console/validate/VehicleAlbumValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/VehicleApplyValidator.php b/app/console/validate/VehicleApplyValidator.php new file mode 100644 index 0000000..f850219 --- /dev/null +++ b/app/console/validate/VehicleApplyValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/VehicleDataTranslationValidator.php b/app/console/validate/VehicleDataTranslationValidator.php new file mode 100644 index 0000000..c454ef2 --- /dev/null +++ b/app/console/validate/VehicleDataTranslationValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/console/validate/VehicleDataValidator.php b/app/console/validate/VehicleDataValidator.php new file mode 100644 index 0000000..ebc319c --- /dev/null +++ b/app/console/validate/VehicleDataValidator.php @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/controller/IndexController.php b/app/controller/IndexController.php new file mode 100644 index 0000000..192aa25 --- /dev/null +++ b/app/controller/IndexController.php @@ -0,0 +1,28 @@ + 网站api "; + } + + 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/functions.php b/app/functions.php new file mode 100644 index 0000000..c0c16fa --- /dev/null +++ b/app/functions.php @@ -0,0 +1,26 @@ +method() == 'OPTIONS' ? response('') : $handler($request); + + // 给响应添加跨域相关的http头 + $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; + } +} \ No newline at end of file diff --git a/app/middleware/Lang.php b/app/middleware/Lang.php new file mode 100644 index 0000000..ab4b4ca --- /dev/null +++ b/app/middleware/Lang.php @@ -0,0 +1,32 @@ +header('Lang'); + + if ($lang === NULL) { + $body = [ + 'status' => 'error', + 'code' => 403, + 'message' => 'Please bring your language.', + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 403, ['Content-Type' => 'application/json']); + } + + if ($lang) { + locale($lang); + } else { + locale('zh_CN'); + } + + return $handler($request); + } +} \ No newline at end of file diff --git a/app/middleware/StaticFile.php b/app/middleware/StaticFile.php new file mode 100644 index 0000000..eb3e33c --- /dev/null +++ b/app/middleware/StaticFile.php @@ -0,0 +1,42 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\middleware; + +use Webman\MiddlewareInterface; +use Webman\Http\Response; +use Webman\Http\Request; + +/** + * Class StaticFile + * @package app\middleware + */ +class StaticFile implements MiddlewareInterface +{ + public function process(Request $request, callable $next): Response + { + // Access to files beginning with. Is prohibited + if (strpos($request->path(), '/.') !== false) { + return response('

403 forbidden

', 403); + } + /** @var Response $response */ + $response = $next($request); + // Add cross domain HTTP header + /*$response->withHeaders([ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Credentials' => 'true', + ]);*/ + return $response; + } +} diff --git a/app/model/Test.php b/app/model/Test.php new file mode 100644 index 0000000..92d70e3 --- /dev/null +++ b/app/model/Test.php @@ -0,0 +1,29 @@ +setModule($module); + + $model->setName($name); + + return $model->doGenerate(); + } + +} \ No newline at end of file diff --git a/app/mzl/common/controller/BaseController.php b/app/mzl/common/controller/BaseController.php new file mode 100644 index 0000000..13c6e49 --- /dev/null +++ b/app/mzl/common/controller/BaseController.php @@ -0,0 +1,375 @@ +all(); + } + + /** + * 获取当前用户ID + * + * @return int|null 当前用户ID,如果未登录则返回null + */ + protected function getUserId(): ?int + { + return request()->user->user_id ?? null; + } + + /** + * 注入字段 + * 如果数据中不存在指定字段,则注入当前用户ID + * + * @param array $data 数据数组 + * @param string $field 字段名,默认为 'user_id' + * @return array 注入字段后的数据数组 + */ + protected function injectAdd(array $data, string $field = 'user_id'): array + { + return !isset($data[$field]) ? $data + [$field => $this->getUserId()] : $data; + } + + /** + * 参数预处理入口,按场景分发 + * + * @param Request $request + * @param string $scene + * @return array + */ + protected function prepareParams(Request $request, string $scene): array + { + // 默认所有场景都注入 user_id + return $this->injectAdd($request->all()); + } + + /** + * 后置处理单条记录:默认不变 + * + * 子类可以重写这个方法来做额外字段加工, + * 比如加 computed 字段、日期格式化、脱敏等等。 + * + * @param array $item 单条记录(pruned 后的数组) + * @return array 处理后的数组 + */ + protected function postProcessItem(array $item, string $scene): array + { + return $item; + } + + /** + * 根据 queryScenes 配置构造 fieldsMap + * + * @param array $config 必须包含 keys: fields, relations + * @return array + */ + // protected function buildFieldsMap2(array $config): array + // { + // $relations = $config['relations'] ?? []; + // $fieldsMap = []; + + // // 根层:主表字段 + 所有一级关联名 + // $rootFields = $config['fields'] ?? []; + // foreach (array_keys($relations) as $path) { + // $rootFields[] = explode('.', $path, 2)[0]; + // } + // $fieldsMap[''] = array_values(array_unique($rootFields)); + + // // 每个关联层:自己字段 + 直接子关联名 + // foreach ($relations as $path => $ownFields) { + // $fieldsMap[$path] = $ownFields; + // $segments = explode('.', $path); + // $depth = count($segments); + // if ($depth > 1) { + // for ($i = 1; $i < $depth; $i++) { + // $parent = implode('.', array_slice($segments, 0, $i)); + // $child = $segments[$i]; + // $fieldsMap[$parent][] = $child; + // } + // } + // } + + // // 去重每一层 + // foreach ($fieldsMap as $k => $arr) { + // $fieldsMap[$k] = array_values(array_unique($arr)); + // } + + // return $fieldsMap; + // } + // protected function buildFieldsMap(array $config): array + // { + // $relations = $config['relations'] ?? []; + + // // 1) 处理主表层字段:把 "col as alias" -> alias + // $rawFields = $config['fields'] ?? []; + // $root = []; + // foreach ($rawFields as $f) { + // if (preg_match('/\s+as\s+/i', $f)) { + // // alias 是 as 之后的部分 + // [$_, $alias] = preg_split('/\s+as\s+/i', $f); + // $root[] = $alias; + // } else { + // $root[] = $f; + // } + // } + // // 再把顶级关联名也加进来 + // foreach (array_keys($relations) as $path) { + // $root[] = explode('.', $path, 2)[0]; + // } + // $fieldsMap = ['' => array_values(array_unique($root))]; + + // // 2) 每个关联路径,自身字段 & 子关联名 + // foreach ($relations as $path => $rawCols) { + // $arr = []; + // // 同样把 alias 拆出来 + // foreach ($rawCols as $col) { + // if (preg_match('/\s+as\s+/i', $col)) { + // [, $alias] = preg_split('/\s+as\s+/i', $col); + // $arr[] = $alias; + // } else { + // $arr[] = $col; + // } + // } + // // 再把它的直接子关联名也加上 + // $segments = explode('.', $path); + // $depth = count($segments); + // if ($depth > 1) { + // for ($i = 1; $i < $depth; $i++) { + // $parent = implode('.', array_slice($segments, 0, $i)); + // $child = $segments[$i]; + // $fieldsMap[$parent][] = $child; + // } + // } + // $fieldsMap[$path] = array_values(array_unique($arr)); + // } + + // return $fieldsMap; + // } + + /** + * 验证请求 + * + * @param array $data 请求数据 + * @param string $scene 验证场景 + * @return array 验证后的数据 + * @throws BusinessException 验证失败时抛出业务异常 + */ + protected function validateRequest(array $data, string $scene = ''): array + { + if (!$this->validator) { + return $data; + } + $validator = is_callable($this->validator) + ? call_user_func($this->validator) + : (is_string($this->validator) ? new $this->validator : $this->validator); + + if ($scene && method_exists($validator, 'scene')) { + $validator->scene($scene); + } + if (!$validator->check($data)) { + throw new BusinessException($validator->getError()); + } + return $data; + } + + /** + * 列表 + * + * @param Request $request 请求对象 + * @param string $scene 场景名称,默认为 'default' + * @param callable|null $postProcessor 额外字段处理回调函数 + * @return Response 响应对象 + */ + protected function listWithTransform( + array $params, + string $scene = 'default', + bool $paginate = true + ): Response { + $collection = $paginate + ? $this->getService()->list($params, $scene) + : $this->getService()->all($params, $scene); + + $config = $this->getService()->queryScenes()[$scene]($params); + // $fieldsMap = $this->buildFieldsMap($config); + + // —— 如果 fields 为空,就不做任何裁剪,直接原样输出 —— + if (empty($config['fields'])) { + $data = $this->transformData( + $collection, + fn(array $item) => $this->postProcessItem($item, $scene) + ); + return $this->successResponse($data); + } + + $fieldsMap = SimpleQueryBuilder::buildFieldsMap($config); + + $result = $this->transformAndPrune( + $collection, + $fieldsMap, + null, + fn(array $item) => $this->postProcessItem($item, $scene) + ); + + return $this->successResponse($result); + } + + /** + * 获取资源列表 + * + * @param Request $request 请求对象 + * @param string $scene 场景名称,默认为 'default' + * @return Response 响应对象 + */ + public function index(Request $request, string $scene = 'default'): Response + { + return $this->listWithTransform( + $this->prepareParams($request, $scene), + $scene + ); + } + + /** + * 获取所有资源(不分页) + * + * @param Request $request 请求对象 + * @param string $scene 场景名称,默认为 'default' + * @return Response 响应对象 + */ + public function all(Request $request, string $scene = 'default'): Response + { + return $this->listWithTransform( + $this->prepareParams($request, $scene), + $scene, + false + ); + } + + public function oneWithTransform(array $params, string $scene = 'default', int $id = 0): Response + { + $model = $id + ? $this->getService()->getById($id, $params, $scene) + : $this->getService()->getOne($params, $scene); + + if (!$model) { + return $this->errorResponse('资源不存在', 1, 404); + } + + $config = $this->getService()->queryScenes()[$scene]($params); + // $fieldsMap = $this->buildFieldsMap($config); + + // —— 如果 fields 为空,就不做任何裁剪,直接原样输出 —— + if (empty($config['fields'])) { + $data = $this->transformData( + $model, + fn(array $item) => $this->postProcessItem($item, $scene) + ); + return $this->successResponse($data); + } + + $fieldsMap = SimpleQueryBuilder::buildFieldsMap($config); + + $data = $this->transformAndPrune( + $model, + $fieldsMap, + null, + fn(array $item) => $this->postProcessItem($item, $scene) + ); + + return $this->successResponse($data); + } + + /** + * GET /resource/{id} + */ + public function show(Request $request, int $id, string $scene = 'default'): Response + { + return $this->oneWithTransform( + $this->prepareParams($request, $scene), + $scene, + $id + ); + } + + /** + * GET /resource/one (按任意条件取单条) + */ + public function one(Request $request, string $scene = 'default'): Response + { + return $this->oneWithTransform( + $this->prepareParams($request, $scene), + $scene + ); + } + + /** + * 创建新资源 + * + * @param Request $request 请求对象 + * @return Response 响应对象 + */ + public function store(Request $request): Response + { + $data = $this->validateRequest($request->all(), 'store'); + $model = $this->getService()->create($data); + return $this->successResponse($this->transformData($model)); + } + + /** + * 更新现有资源 + * + * @param Request $request 请求对象 + * @param int $id 资源ID + * @return Response 响应对象 + */ + public function update(Request $request, int $id): Response + { + $data = $this->validateRequest($request->all(), 'update'); + $result = $this->getService()->update($id, $data); + return $result + ? $this->successResponse(['id' => $id]) + : $this->errorResponse('更新失败'); + } + + /** + * 删除资源 + * + * @param int $id 资源ID + * @return Response 响应对象 + */ + public function destroy(int $id): Response + { + $result = $this->getService()->delete($id); + return $result + ? $this->successResponse(['id' => $id]) + : $this->errorResponse('删除失败'); + } +} \ No newline at end of file diff --git a/app/mzl/common/org/XunfeiFanyi.php b/app/mzl/common/org/XunfeiFanyi.php new file mode 100644 index 0000000..3c27b57 --- /dev/null +++ b/app/mzl/common/org/XunfeiFanyi.php @@ -0,0 +1,134 @@ + $header,'body' => $response); + return $data; + } + + + public function xfyun($text = "中华人民共和国于1949年成立", $from = "cn", $to = "en") + { + //在控制台-我的应用-机器翻译获取 + $app_id = "3d46198c"; + //在控制台-我的应用-机器翻译获取 + $api_sec = "ZWY2MmUyNjJjYmFkOTFlYTVmYWVhMGYy"; + //在控制台-我的应用-机器翻译获取 + $api_key = "b6d805f240176ecaea85130cb5f2bca1"; + // 机器翻译接口地址 + $url = "https://ntrans.xfyun.cn/v2/ots"; + //body组装 + // $text = "中华人民共和国于1949年成立";//待翻译文本 + $body = json_encode($this->getBody($app_id, $from, $to, $text)); + + // 组装http请求头 + $date =gmdate('D, d M Y H:i:s') . ' GMT'; + + $digestBase64 = "SHA-256=".base64_encode(hash("sha256", $body, true)); + $builder = sprintf("host: %s +date: %s +POST /v2/ots HTTP/1.1 +digest: %s", "ntrans.xfyun.cn", $date, $digestBase64); + // echo($builder); + $sha = base64_encode(hash_hmac("sha256", $builder, $api_sec, true)); + + $authorization = sprintf("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", $api_key,"hmac-sha256", + "host date request-line digest", $sha); + + $header = [ + "Authorization: ".$authorization, + 'Content-Type: application/json', + 'Accept: application/json,version=1.0', + 'Host: ntrans.xfyun.cn', + 'Date: ' .$date, + 'Digest: '.$digestBase64 + ]; + $response = $this->tocurl($url, $header, $body); + + // var_dump($response['body']); + return $response; + } + + + public function getBody($app_id, $from, $to, $text) + { + $common_param = [ + 'app_id' => $app_id + ]; + + $business = [ + 'from' => $from, + 'to' => $to, + ]; + + $data = [ + "text" => base64_encode($text) + ]; + + return $body = [ + 'common' => $common_param, + 'business' => $business, + 'data' => $data + ]; + } + + // 翻译 + public function fanyi($text = "", $from = "", $to = "") + { + try { + $res = $this->xfyun($text, $from, $to); + $data = json_decode($res["body"], true); + if ($data["code"] === 0) { + return $data["data"]["result"]; + } else { + $this->error = $data["message"]; + return false; + } + } catch (\Throwable $th) { + $this->error = $th->getMessage(); + return false; + } + } + + // 异常 + public function error() + { + return $this->error; + } + +} \ No newline at end of file diff --git a/app/mzl/common/services/BaseService.php b/app/mzl/common/services/BaseService.php new file mode 100644 index 0000000..eb02bda --- /dev/null +++ b/app/mzl/common/services/BaseService.php @@ -0,0 +1,198 @@ +translatable as $field) { + if (array_key_exists($field, $data)) { + $source[$field] = $data[$field]; + unset($data[$field]); + } + } + return $source; + } + + /** + * 子类必须实现:返回模型类名 + * + * @return string 模型类名 + */ + abstract public function getModel(): string; + + /** + * 构造函数,初始化模型实例 + */ + public function __construct() + { + $this->model = new ($this->getModel()); + } + + /** + * 子类定义:场景化查询配置 + * 返回一个关联数组,键为场景名称,值为查询配置数组或闭包 + * + * @return array 查询配置数组或闭包 + */ + public function queryScenes(): array + { + return []; + } + + /** + * 根据场景和请求参数,取到完整的 Builder 配置 + * + * @param array $params 请求参数 + * @param string $scene 场景名称 + * @return array 查询配置数组 + */ + protected function getQueryConfig(array $params, string $scene): array + { + $scenes = $this->queryScenes(); + $cfg = $scenes[$scene] ?? []; + return is_callable($cfg) ? $cfg($params) : $cfg; + } + + /** + * 构造Builder + */ + protected function createQueryBuilder(array $params, string $scene): SimpleQueryBuilder + { + $config = $this->getQueryConfig($params, $scene); + return (new SimpleQueryBuilder($this->model))->applyQueryConfig($config); + } + + /** + * 分页列表 + * + * @param array $params 请求参数 + * @param string $scene 场景名称,默认为 'default' + * @return LengthAwarePaginator 分页结果 + */ + public function list(array $params = [], string $scene = 'default'): LengthAwarePaginator + { + return $this->createQueryBuilder($params, $scene)->paginate(); + } + + /** + * 不分页列表 + * + * @param array $params 请求参数 + * @param string $scene 场景名称,默认为 'default' + * @return Collection 查询结果集合 + */ + public function all(array $params = [], string $scene = 'default'): Collection + { + return $this->createQueryBuilder($params, $scene)->get(); + } + + /** + * 自定义传入配置查询 + * + * @param array $config 查询配置 + * @param bool $paginate 是否分页,默认为 true + * @return LengthAwarePaginator|Collection 查询结果,分页或集合 + */ + public function query(array $config, bool $paginate = true): LengthAwarePaginator|Collection + { + $builder = new SimpleQueryBuilder($this->model); + $builder->applyQueryConfig($config); + return $paginate ? $builder->paginate() : $builder->get(); + } + + /** + * 按主键取一条(带关联) + */ + public function getById(int $id, array $params = [], string $scene = 'default'): ?Model + { + return $this->createQueryBuilder($params, $scene) + ->build() + ->whereKey($id) + ->first(); + } + + /** + * 按过滤条件取一条(带关联,不限主键) + */ + public function getOne(array $params = [], string $scene = 'default'): ?Model + { + return $this->createQueryBuilder($params, $scene) + ->build() + ->first(); + } + + /** + * 查找单个模型实例 + * + * @param int $id 模型ID + * @return Model|null 模型实例或null + */ + public function find(int $id): ?Model + { + return $this->model->find($id); + } + + /** + * 创建新模型实例 + * + * @param array $data 创建数据 + * @return Model 新创建的模型实例 + */ + public function create(array $data): Model + { + return $this->model->create($data); + } + + /** + * 更新现有模型实例 + * + * @param int $id 模型ID + * @param array $data 更新数据 + * @return bool 更新是否成功 + */ + public function update(int $id, array $data): bool + { + $record = $this->find($id); + return $record ? $record->update($data) : false; + } + + /** + * 删除模型实例 + * + * @param int $id 模型ID + * @return bool 删除是否成功 + */ + public function delete(int $id): bool + { + $record = $this->find($id); + return $record ? $record->delete() : false; + } +} \ No newline at end of file diff --git a/app/mzl/common/traits/DataTransformer.php b/app/mzl/common/traits/DataTransformer.php new file mode 100644 index 0000000..170bfe4 --- /dev/null +++ b/app/mzl/common/traits/DataTransformer.php @@ -0,0 +1,257 @@ + $field; + + if ($rawData instanceof LengthAwarePaginator) { + $formatted = $this->formatPaginated($rawData, $postProcessor, $fieldFormatter); + } elseif ($rawData instanceof Model) { + $formatted = $this->formatModel($rawData, $fieldFormatter); + } elseif ($rawData instanceof Collection) { + $formatted = $this->formatCollection($rawData, $fieldFormatter); + } elseif (is_array($rawData)) { + $formatted = $this->formatArray($rawData, $fieldFormatter); + } else { + $formatted = (array) $rawData; + } + + if (is_callable($postProcessor)) { + $formatted = $postProcessor($formatted); + } + + return $formatted; + } catch (\Throwable $e) { + return [ + 'error' => true, + 'message' => $e->getMessage(), + 'code' => $e->getCode(), + ]; + } + } + + /** + * 格式化分页数据:将每条记录也进行 transformData 递归处理 + * + * @param LengthAwarePaginator $paginator + * @param callable|null $postProcessor + * @param callable $fieldFormatter + * @return array + */ + protected function formatPaginated( + LengthAwarePaginator $paginator, + ?callable $postProcessor, + callable $fieldFormatter + ): array { + $list = $paginator->getCollection()->map(function ($item) use ($postProcessor, $fieldFormatter) { + return $this->transformData($item, $postProcessor, $fieldFormatter); + })->toArray(); + + $meta = [ + 'current_page' => $paginator->currentPage(), + 'per_page' => $paginator->perPage(), + 'total' => $paginator->total(), + 'last_page' => $paginator->lastPage(), + 'first_page_url' => $paginator->url(1), + 'last_page_url' => $paginator->url($paginator->lastPage()), + 'next_page_url' => $paginator->nextPageUrl(), + 'prev_page_url' => $paginator->previousPageUrl(), + ]; + + return [ + 'list' => $list, + 'meta' => $meta, + ]; + } + + /** + * 格式化 Eloquent 模型:递归嵌套关联 + * + * @param Model $model + * @param callable $fieldFormatter + * @return array + */ + protected function formatModel(Model $model, callable $fieldFormatter): array + { + $formatted = []; + + // 主表字段 + foreach ($model->getAttributes() as $key => $value) { + $formatted[$fieldFormatter($key)] = $value; + } + + // 关联数据,递归嵌套 + foreach ($model->getRelations() as $relation => $relationData) { + if ($relationData instanceof Model) { + // 单个关联模型 + $formatted[$relation] = $this->formatModel($relationData, $fieldFormatter); + } elseif ($relationData instanceof Collection) { + // 关联集合 + $formatted[$relation] = $relationData + ->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + } + + return $formatted; + } + + /** + * 格式化集合 + * + * @param Collection $collection + * @param callable $fieldFormatter + * @return array + */ + protected function formatCollection(Collection $collection, callable $fieldFormatter): array + { + return $collection->map(fn($item) => $this->transformData($item, null, $fieldFormatter)) + ->toArray(); + } + + /** + * 格式化普通数组 + * + * @param array $array + * @param callable $fieldFormatter + * @return array + */ + protected function formatArray(array $array, callable $fieldFormatter): array + { + return array_map(fn($item) => $this->transformData($item, null, $fieldFormatter), $array); + } + + /** + * 递归裁剪数组,只保留指定字段 + * + * @param array $data 当前要裁剪的数据(可能是单条关联,也可能是 list) + * @param array $fieldsMap 白名单映射,key=>[allowed fields] + * @param string $path 当前路径('' / 'items' / 'items.sub' …) + * @return array + */ + protected function pruneFields(array $data, array $fieldsMap, string $path = ''): array + { + // —— 新增:如果这是一个纯列表(list),逐项递归 prune 并返回 + if ($this->isListArray($data)) { + return array_map( + fn($item) => is_array($item) + ? $this->pruneFields($item, $fieldsMap, $path) + : $item, + $data + ); + } + + // 如果当前层没有配置白名单,原样返回(或也可以 return [] 视业务) + if (! isset($fieldsMap[$path])) { + return $data; + } + + $allowed = $fieldsMap[$path]; + $out = []; + + foreach ($allowed as $key) { + if (! array_key_exists($key, $data)) { + continue; + } + $val = $data[$key]; + + // 如果下一层还是个数组,继续递归 + if (is_array($val)) { + $nextPath = ltrim($path . '.' . $key, '.'); + $out[$key] = $this->pruneFields($val, $fieldsMap, $nextPath); + } else { + $out[$key] = $val; + } + } + + return $out; + } + + /** + * 把 Paginator/Model/Collection/array 统一格式化,并按 fieldsMap 裁剪 + * + * @param mixed $rawData + * @param array|null $fieldsMap 顶层 '' + 各关联路径的白名单 + * @param callable|null $fieldFormatter 字段格式化回调 + * @param callable|null $postProcessor 单条记录后置处理回调 + * @return array + */ + protected function transformAndPrune( + mixed $rawData, + ?array $fieldsMap = null, + ?callable $fieldFormatter = null, + ?callable $postProcessor = null + ): array { + // 1) 统一转换成数组 + $data = $this->transformData($rawData, null, $fieldFormatter); + + // 2) 如果没有白名单配置,直接返回原始数据 + if (empty($fieldsMap) || !isset($fieldsMap[''])) { + return $data; + } + + // 3) 分页结构:['list'=>[...],'meta'=>[...]] + if (isset($data['list'], $data['meta'])) { + $data['list'] = array_map( + fn($item) => $this->processItem($item, $fieldsMap, $postProcessor), + $data['list'] + ); + return $data; + } + + // 4) 数字索引数组 => 当作“非分页列表”处理 + if ($this->isListArray($data)) { + return array_map( + fn($item) => $this->processItem($item, $fieldsMap, $postProcessor), + $data + ); + } + + // 5) 单条记录 + return $this->processItem($data, $fieldsMap, $postProcessor); + } + + /** + * 裁剪并做后置处理 + */ + private function processItem( + array $item, + array $fieldsMap, + ?callable $postProcessor + ): array { + $pruned = $this->pruneFields($item, $fieldsMap); + return $postProcessor ? $postProcessor($pruned) : $pruned; + } + + /** + * 判断一个数组是不是“索引从 0 开始的列表” + */ + private function isListArray(array $arr): bool + { + if (empty($arr)) { + return true; + } + return array_keys($arr) === range(0, count($arr) - 1); + } +} \ No newline at end of file diff --git a/app/mzl/common/traits/ResponseTrait.php b/app/mzl/common/traits/ResponseTrait.php new file mode 100644 index 0000000..b4f3b56 --- /dev/null +++ b/app/mzl/common/traits/ResponseTrait.php @@ -0,0 +1,76 @@ + 'application/json'] + ): Response { + return $this->result('success', $code, $httpStatus, $message, $data, $header); + } + + /** + * 返回错误响应 + * + * @param string $message 响应错误消息,默认“请求失败” + * @param int $code 内部状态码,默认 1 表示错误 + * @param int $httpStatus HTTP 状态码,默认 400 + * @param mixed $data 可选的错误数据 + * @param array $header 响应头,默认 JSON 格式 + * @return Response + */ + public function errorResponse( + string $message = '请求失败', + int $code = 1, + int $httpStatus = 400, + $data = [], + array $header = ['Content-Type' => 'application/json'] + ): Response { + return $this->result('error', $code, $httpStatus, $message, $data, $header); + } + + /** + * 统一响应格式方法 + * + * @param string $status 响应状态,例如 "success" 或 "error" + * @param int $code 内部状态码 + * @param int $httpStatus HTTP 状态码 + * @param string $message 响应消息 + * @param mixed $data 响应数据 + * @param array $header 响应头 + * @return Response + */ + protected function result( + string $status, + int $code, + int $httpStatus, + string $message, + $data, + array $header + ): Response { + $body = [ + 'status' => $status, + 'code' => $code, + 'message' => $message, + 'data' => $data, + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), $httpStatus, $header); + } +} \ No newline at end of file diff --git a/app/mzl/common/traits/ServiceCallTrait.php b/app/mzl/common/traits/ServiceCallTrait.php new file mode 100644 index 0000000..c8ba810 --- /dev/null +++ b/app/mzl/common/traits/ServiceCallTrait.php @@ -0,0 +1,47 @@ +serviceCache[$serviceClass])) { + $this->serviceCache[$serviceClass] = new $serviceClass(); + } + return $this->serviceCache[$serviceClass]; + } + + /** + * 快捷调用某服务下某个方法 + * + * @param string $serviceClass + * @param string $method + * @param array $args + * @return mixed + */ + protected function callServiceMethod(string $serviceClass, string $method, array $args = []): mixed + { + $service = $this->callService($serviceClass); + + if (!method_exists($service, $method)) { + throw new \BadMethodCallException("Method {$method} does not exist in {$serviceClass}"); + } + + return call_user_func_array([$service, $method], $args); + } +} \ No newline at end of file diff --git a/app/mzl/common/utils/SearchHelper.php b/app/mzl/common/utils/SearchHelper.php new file mode 100644 index 0000000..ddc58af --- /dev/null +++ b/app/mzl/common/utils/SearchHelper.php @@ -0,0 +1,65 @@ + $definition) { + $field = $definition['field']; + $operator = $definition['operator']; + $value = $params[$paramKey] ?? null; + + if (self::isValidValue($value, $operator)) { + $filters[] = compact('field', 'operator', 'value'); + if (str_contains($field, '.')) { + $relations[] = self::extractRelationPath($field); + } + } + } + + // 解析 whereHas 关联字段(如 profile.age) + foreach ($params as $key => $value) { + if (str_contains($key, '.')) { + $relation = explode('.', $key)[0]; // 获取关联名 + $hasRelations[$relation] = function ($query) use ($key, $value) { + $query->where($key, '=', $value); + }; + } + } + + return [ + 'filters' => $filters, + 'relations' => array_unique($relations), + 'hasRelations' => $hasRelations, + ]; + } + + private static function isValidValue(mixed $value, string $operator): bool + { + $operator = strtolower($operator); + + if (in_array($operator, ['in', 'not_in', 'between'])) { + return is_array($value) && !empty($value); + } + + if (in_array($operator, ['null', 'not_null'])) { + return true; + } + + return $value !== '' && $value !== null; + } + + private static function extractRelationPath(string $field): string + { + $parts = explode('.', $field); + array_pop($parts); + return implode('.', $parts); + } +} \ No newline at end of file diff --git a/app/mzl/common/utils/SimpleQueryBuilder.php b/app/mzl/common/utils/SimpleQueryBuilder.php new file mode 100644 index 0000000..d770f18 --- /dev/null +++ b/app/mzl/common/utils/SimpleQueryBuilder.php @@ -0,0 +1,584 @@ + 1, + 'per_page' => 10, + ]; + + /** @var string[] 要执行 withCount 的关联 */ + protected array $counts = []; + + public array $allowedFields = []; + + protected ?\Closure $customQueryCallback = null; + + public function __construct(?Model $model = null) + { + if ($model) { + $this->setModel($model); + } + } + + /** + * 设置模型 + */ + public function setModel(Model $model): self + { + $this->model = $model; + $this->query = $model->newQuery(); + return $this; + } + + /** + * 批量应用查询配置 + * + * @param array $config + * @return $this + */ + public function applyQueryConfig(array $config): self + { + foreach ($config['filters'] ?? [] as $filter) { + $this->addFilter($filter['field'], $filter['operator'], $filter['value']); + } + + foreach ($config['sorts'] ?? [] as $sort) { + $this->addSort($sort['field'], $sort['direction'] ?? 'asc'); + } + + if (!empty($config['fields'])) { + $this->selectFields($config['fields']); + } + + foreach ($config['relations'] ?? [] as $relation => $fields) { + $this->withRelation($relation, $fields); + } + + foreach ($config['hasRelations'] ?? [] as $relation => $callback) { + if ($callback instanceof \Closure) { + $this->addHasRelation($relation, $callback); + } + } + + // 新增:处理 counts + foreach ($config['counts'] ?? [] as $relation) { + $this->addCount($relation); + } + + if (!empty($config['pagination'])) { + $this->setPagination( + $config['pagination']['page'] ?? 1, + $config['pagination']['per_page'] ?? 15 + ); + } + + if (!empty($config['custom']) && $config['custom'] instanceof \Closure) { + $this->withCustomQuery($config['custom']); + } + + return $this; + } + + /** + * 添加单个过滤条件 + */ + public function addFilter(string $field, string $operator, mixed $value): self + { + $this->filters[] = compact('field', 'operator', 'value'); + return $this; + } + + /** + * 添加排序字段 + */ + public function addSort(string $field, string $direction = 'asc'): self + { + $this->sorts[$field] = strtolower($direction) === 'desc' ? 'desc' : 'asc'; + return $this; + } + + /** + * 设置查询字段(select),可配合 allowedFields 校验 + */ + public function selectFields(array $fields): self + { + $this->fields = array_unique($fields); + return $this; + } + + /** + * 加载关联(支持指定字段) + */ + public function withRelation2(string $relation, array $fields = []): self + { + $this->relations[$relation] = !empty($fields) ? $fields : '*'; + return $this; + } + /** + * 加载关联(支持指定字段 或 自定义闭包) + * + * @param string $relation 关联名,支持点式多级 + * @param array|\Closure $fields 字段数组 或 查询闭包 + */ + public function withRelation(string $relation, $fields = []): self + { + if ($fields instanceof \Closure) { + // 自定义关联查询闭包 + $this->relations[$relation] = $fields; + } else { + // 字段选择,空数组表示全部 + $this->relations[$relation] = !empty($fields) ? $fields : '*'; + } + return $this; + } + + + /** + * 添加 whereHas 条件 + */ + public function addHasRelation(string $relation, \Closure $callback): self + { + $this->hasRelations[] = compact('relation', 'callback'); + return $this; + } + + /** + * 设置分页 + */ + public function setPagination(int $page, int $perPage): self + { + $this->pagination = [ + 'page' => max(1, $page), + 'per_page' => min(max(1, $perPage), 100), + ]; + return $this; + } + + /** + * 注入复杂查询闭包(适合业务层扩展) + */ + public function withCustomQuery(\Closure $callback): self + { + $this->customQueryCallback = $callback; + return $this; + } + + /** + * 添加关联数量统计 (withCount) + * + * @param string $relation 关联名,支持点式多层,如 "orders.items" + * @return $this + */ + public function addCount(string $relation): self + { + $this->counts[] = $relation; + return $this; + } + + /** + * 构建完整查询(不执行) + */ + public function build(): Builder + { + if (!$this->query && $this->model) { + $this->query = $this->model->newQuery(); + } + + $this->applyFilters() + ->applySorts() + ->applyFields() + ->applyRelations() + ->applyWhereHas(); + + // ========== 新增:关联计数 ========== + if (!empty($this->counts)) { + // Laravel 支持 withCount(['rel1','rel2']) + $this->query->withCount($this->counts); + } + + if ($this->customQueryCallback) { + call_user_func($this->customQueryCallback, $this->query); + } + + return $this->query; + } + + /** + * 获取集合(不分页) + */ + public function get(): Collection + { + return $this->build()->get(); + } + + /** + * 获取分页结果 + */ + public function paginate(): LengthAwarePaginator + { + return $this->build()->paginate( + $this->pagination['per_page'], + ['*'], + 'page', + $this->pagination['page'] + ); + } + + /** + * 应用过滤 + */ + protected function applyFilters(): self + { + foreach ($this->filters as $filter) { + $field = $filter['field']; + $operator = $filter['operator']; + $value = $filter['value']; + + if (str_contains($field, '.')) { + $segments = explode('.', $field); + $relationPath = implode('.', array_slice($segments, 0, -1)); + $lastField = end($segments); + + $this->query->whereHas($relationPath, function (Builder $q) use ($lastField, $operator, $value) { + $this->applyCondition($q, $lastField, $operator, $value); + }); + } else { + $this->applyCondition($this->query, $field, $operator, $value); + } + } + + return $this; + } + + /** + * 应用排序 + */ + protected function applySorts(): self + { + foreach ($this->sorts as $field => $direction) { + $this->query->orderBy($field, $direction); + } + return $this; + } + + /** + * 应用字段选择 + */ + // protected function applyFields2(): self + // { + // if (!empty($this->fields)) { + // // 排除关联字段(如 class.teacher.realname) + // $fields = array_filter($this->fields, fn($field) => !str_contains($field, '.')); + + // // 白名单过滤 + // $fields = !empty($this->allowedFields) + // ? array_intersect($fields, $this->allowedFields) + // : $fields; + + // if (empty($fields)) { + // throw new \InvalidArgumentException('字段选择无效'); + // } + + // $this->query->select(array_unique($fields)); + // } + + // return $this; + // } + + /** + * 应用字段选择,支持普通列和带 alias 的 Raw 表达式 + */ + protected function applyFields(): self + { + if (empty($this->fields)) { + return $this; + } + + // 只保留主表字段(排除带点的关联字段) + $cols = array_filter($this->fields, fn($f) => ! str_contains($f, '.')); + + // 如果设置了 allowedFields,做一次白名单交集 + if (! empty($this->allowedFields)) { + $cols = array_intersect($cols, $this->allowedFields); + } + + if (empty($cols)) { + throw new \InvalidArgumentException('字段选择无效'); + } + + $plain = []; + $raws = []; + + foreach ($cols as $col) { + // 简单判断带 “ as ” 的就归到 Raw + if (preg_match('/\s+as\s+/i', $col)) { + $raws[] = $col; + } else { + $plain[] = $col; + } + } + + // 先 select 普通列 + if ($plain) { + $this->query->select($plain); + } + // 再 selectRaw alias + if ($raws) { + // 注意:selectRaw 接受一整个字符串 + $this->query->selectRaw(implode(', ', $raws)); + } + + return $this; + } + + /** + * 应用关联加载,支持 dot-notation 多级关联 + */ + // protected function applyRelations2(): self + // { + // if (empty($this->relations)) { + // return $this; + // } + + // $with = []; + // foreach ($this->relations as $path => $fields) { + // $with[$path] = function ($q) use ($fields) { + // if ($fields === '*' || empty($fields)) { + // return; + // } + // // 保证 id 在内 + // $model = $q->getModel(); + // $key = $model->getKeyName(); + // $q->select(array_merge([$key], $fields)); + // }; + // } + + // $this->query->with($with); + + // return $this; + // } + + /** + * 应用多级关联加载,支持给关联字段加 alias + */ + protected function applyRelations2(): self + { + if (empty($this->relations)) { + return $this; + } + + $with = []; + foreach ($this->relations as $path => $fields) { + $with[$path] = function ($q) use ($fields) { + if ($fields === '*' || empty($fields)) { + return; + } + + // 模型主键一定要带上 + $model = $q->getModel(); + $key = $model->getKeyName(); + + $plain = []; + $raws = []; + + foreach ($fields as $col) { + if (preg_match('/\s+as\s+/i', $col)) { + $raws[] = $col; + } else { + $plain[] = $col; + } + } + + // 确保主键至少出现一次 + if (! in_array($key, $plain, true)) { + array_unshift($plain, $key); + } + + // select 普通列 + if ($plain) { + $q->select($plain); + } + // selectRaw 带 alias 的列 + if ($raws) { + $q->selectRaw(implode(', ', $raws)); + } + }; + } + + $this->query->with($with); + return $this; + } + /** + * 应用多级关联加载,支持字段数组或直接传入闭包 + */ + protected function applyRelations(): self + { + if (empty($this->relations)) { + return $this; + } + + $with = []; + foreach ($this->relations as $path => $handler) { + if ($handler instanceof \Closure) { + // 直接使用用户传入的闭包 + $with[$path] = $handler; + } else { + // 按字段数组构造查询 + $fields = $handler; + $with[$path] = function ($q) use ($fields) { + if ($fields === '*' || empty($fields)) { + return; + } + // 确保主键被选中 + $model = $q->getModel(); + $key = $model->getKeyName(); + + $plain = []; + $raws = []; + foreach ($fields as $col) { + if (preg_match('/\s+as\s+/i', $col)) { + $raws[] = $col; + } else { + $plain[] = $col; + } + } + if (! in_array($key, $plain, true)) { + array_unshift($plain, $key); + } + + if ($plain) { + $q->select($plain); + } + if ($raws) { + $q->selectRaw(implode(', ', $raws)); + } + }; + } + } + + $this->query->with($with); + return $this; + } + + + /** + * 应用 whereHas 关联过滤 + */ + protected function applyWhereHas(): self + { + foreach ($this->hasRelations as $relationData) { + $this->query->whereHas($relationData['relation'], $relationData['callback']); + } + return $this; + } + + /** + * 应用单个过滤条件 + */ + protected function applyCondition(Builder $query, string $field, string $operator, mixed $value): void + { + switch (strtolower($operator)) { + case 'between': + $query->whereBetween($field, (array)$value); + break; + case 'like': + $query->where($field, 'LIKE', "%{$value}%"); + break; + case 'in': + $query->whereIn($field, (array)$value); + break; + case 'not_in': + $query->whereNotIn($field, (array)$value); + break; + case 'null': + $query->whereNull($field); + break; + case 'not_null': + $query->whereNotNull($field); + break; + default: + $query->where($field, $operator, $value); + } + } + + /** + * 根据 queryConfig 构造 fieldsMap + * - 兼容 "col as alias" + * - 自动把 counts 生成的 xxx_count 加入根字段 + * + * @param array $config 完整的 applyQueryConfig 输入 + * @return array [''=>[主表字段…], 'relation'=>[字段…], …] + */ + public static function buildFieldsMap(array $config): array + { + $relations = $config['relations'] ?? []; + $counts = $config['counts'] ?? []; + $rawFields = $config['fields'] ?? []; + + // 1) 根层字段:拆 alias + 自动加 count + 加顶级关联名 + $root = []; + foreach ($rawFields as $f) { + if (preg_match('/\s+as\s+/i', $f)) { + [, $alias] = preg_split('/\s+as\s+/i', $f); + $root[] = $alias; + } else { + $root[] = $f; + } + } + // 把 withCount 生成的字段也加进来 + foreach ($counts as $rel) { + $root[] = "{$rel}_count"; + } + // 顶级关联名 + foreach (array_keys($relations) as $path) { + $root[] = explode('.', $path, 2)[0]; + } + $fieldsMap = ['' => array_values(array_unique($root))]; + + // 2) 每个关联:拆 alias + 加直接子关联 + foreach ($relations as $path => $rawCols) { + $arr = []; + $segments = explode('.', $path); + $depth = count($segments); + // 拆 alias + foreach ($rawCols as $col) { + if (preg_match('/\s+as\s+/i', $col)) { + [, $alias] = preg_split('/\s+as\s+/i', $col); + $arr[] = $alias; + } else { + $arr[] = $col; + } + } + // 子关联名 + if ($depth > 1) { + for ($i = 1; $i < $depth; $i++) { + $parent = implode('.', array_slice($segments, 0, $i)); + $child = $segments[$i]; + $fieldsMap[$parent][] = $child; + } + } + $fieldsMap[$path] = array_values(array_unique($arr)); + } + + return $fieldsMap; + } +} \ No newline at end of file diff --git a/app/mzl/common/utils/SnGenerator.php b/app/mzl/common/utils/SnGenerator.php new file mode 100644 index 0000000..1d0220e --- /dev/null +++ b/app/mzl/common/utils/SnGenerator.php @@ -0,0 +1,59 @@ +generateRandomNumber($length); + $attempts++; + if ($attempts > $maxAttempts) { + throw new \Exception("无法生成唯一的用户编号,请重试"); + } + } while ($this->isUserIdExists($userId)); + + return $userId; + } + + /** + * 生成随机数字 + * + * @param int $length 数字长度 + * @return string + */ + private function generateRandomNumber($length) + { + $min = pow(10, $length - 1); // 最小值,如 6 位是 100000 + $max = pow(10, $length) - 1; // 最大值,如 6 位是 999999 + return (string) mt_rand($min, $max); + } + + /** + * 检查用户编号是否已存在 + * + * @param string $userId + * @return bool + */ + private function isUserIdExists($userId) + { + $userIds = \support\Db::table('hs_users')->pluck('user_sn')->toArray(); + return in_array($userId, $userIds); + } +} \ No newline at end of file diff --git a/app/mzl/common/utils/TableInspector.php b/app/mzl/common/utils/TableInspector.php new file mode 100644 index 0000000..0bbc18d --- /dev/null +++ b/app/mzl/common/utils/TableInspector.php @@ -0,0 +1,57 @@ +getDatabaseName(); + + $columns = Db::select(" + SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, + COLUMN_DEFAULT, COLUMN_COMMENT, EXTRA + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? + ORDER BY ORDINAL_POSITION + ", [$database, $table]); + + return array_map(function ($col) { + return [ + 'name' => $col->COLUMN_NAME, + 'type' => $col->DATA_TYPE, + 'column_type' => $col->COLUMN_TYPE, + 'nullable' => $col->IS_NULLABLE === 'YES', + 'default' => $col->COLUMN_DEFAULT, + 'comment' => $col->COLUMN_COMMENT, + 'extra' => $col->EXTRA, + ]; + }, $columns); + } + + /** + * 获取字段名列表 + */ + public static function getColumnNames(string $table, ?string $database = null): array + { + return array_column(self::getColumns($table, $database), 'name'); + } + + /** + * 获取字段注释 map:字段名 => 注释 + */ + public static function getColumnComments(string $table, ?string $database = null): array + { + $columns = self::getColumns($table, $database); + $map = []; + foreach ($columns as $col) { + $map[$col['name']] = $col['comment']; + } + return $map; + } +} \ No newline at end of file diff --git a/app/mzl/generate/actions/Generate.php b/app/mzl/generate/actions/Generate.php new file mode 100644 index 0000000..c650792 --- /dev/null +++ b/app/mzl/generate/actions/Generate.php @@ -0,0 +1,168 @@ +moduleName}/controller/{$this->convertName}Controller.php"; + $servicePath = __DIR__ . "/../../../{$this->moduleName}/service/{$this->convertName}Service.php"; + $modelPath = __DIR__ . "/../../../{$this->moduleName}/model/{$this->convertName}Model.php"; + $validatePath = __DIR__ . "/../../../{$this->moduleName}/validate/{$this->convertName}Validator.php"; + + // // 创建目录(如果不存在) + $fs->mkdir(__DIR__ . "/../../../{$this->moduleName}/controller"); + $fs->mkdir(__DIR__ . "/../../../{$this->moduleName}/service"); + $fs->mkdir(__DIR__ . "/../../../{$this->moduleName}/model"); + $fs->mkdir(__DIR__ . "/../../../{$this->moduleName}/validate"); + + // // 删除已存在的文件并重新生成 + $this->deleteFileIfExists($controllerPath); + $this->deleteFileIfExists($servicePath); + $this->deleteFileIfExists($modelPath); + $this->deleteFileIfExists($validatePath); + + // 生成控制器文件 + $ControllerStub = $this->generateFile($templateDir, 'ControllerStub.stub', $controllerPath); + + // 生成服务类文件 + $ServiceStub = $this->generateFile($templateDir, 'ServiceStub.stub', $servicePath); + + // 生成模型类文件 + $ModelStub = $this->generateFile($templateDir, 'ModelStub.stub', $modelPath); + + // 生成验证器类文件 + $ValidatorStub = $this->generateFile($templateDir, 'ValidatorStub.stub', $validatePath); + + return $ControllerStub . $ServiceStub . $ModelStub . $ValidatorStub . "文件生成成功!
"; + } + + /** + * 设置字段 + * + * @param array $fields 字段数组 + */ + public function fillableFields() + { + $tableName = $this->dbPrefix.$this->name; + $fields = TableInspector::getColumnNames($tableName); + $str = ''; + foreach ($fields as $key => $val) { + if ($key < (count($fields)-1)) { + $str .= "'{$val}', "; + } else { + $str .= "'{$val}'"; + } + } + $fields = trim($str, '"'); + return $fields; + } + + /** + * 根据模板生成文件 + * + * @param string $templateDir 模板目录 + * @param string $templateFile 模板文件名 + * @param string $outputPath 输出文件路径 + */ + protected function generateFile($templateDir, $templateFile, $outputPath) + { + $templatePath = $templateDir . $templateFile; + + if (!file_exists($templatePath)) { + echo "模板文件 {$templateFile} 不存在! \n"; + return; + } + + $content = file_get_contents($templatePath); + $content = str_replace('{{name}}', $this->convertName, $content); + + // 填入table名 + $content = str_replace('{{table}}', $this->dbPrefix.$this->name, $content); + + // 填入module名 + $content = str_replace('{{module}}', $this->moduleName, $content); + + // 填入字段 + $content = str_replace('{{fillableFields}}', $this->fillableFields(), $content); + + file_put_contents($outputPath, $content); + + return "生成文件: {$outputPath}
"; + } + + /** + * 如果文件存在,删除它 + * + * @param string $path 文件路径 + */ + protected function deleteFileIfExists($path) + { + if (file_exists($path)) { + unlink($path); + echo "删除已存在文件: {$path} \n"; + } + } + + /** + * 设置名称 + */ + public function setName($name = '') + { + $this->name = $name; + $this->convertName = ucfirst(camelize($name)); + } + + /** + * 设置模块名称 + */ + public function setModule($name = '') + { + $this->moduleName = $name; + } +} + +/** +* 下划线转驼峰 +* 思路: +* step1.原字符串转小写,原字符串中的分隔符用空格替换,在字符串开头加上分隔符 +* step2.将字符串中每个单词的首字母转换为大写,再去空格,去字符串首部附加的分隔符. +*/ +function camelize($uncamelized_words, $separator='_') { + $uncamelized_words = $separator . str_replace($separator, " ", strtolower($uncamelized_words)); + return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $separator ); +} + +/** +* 驼峰命名转下划线命名 +* 思路: +* 小写和大写紧挨一起的地方,加上分隔符,然后全部转小写 +*/ +function uncamelize($camelCaps, $separator='_') { + return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $separator . "$2", $camelCaps)); +} \ No newline at end of file diff --git a/app/mzl/generate/templates/ControllerStub.stub b/app/mzl/generate/templates/ControllerStub.stub new file mode 100644 index 0000000..d0beb70 --- /dev/null +++ b/app/mzl/generate/templates/ControllerStub.stub @@ -0,0 +1,81 @@ +errorResponse($th->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/mzl/generate/templates/ModelStub.stub b/app/mzl/generate/templates/ModelStub.stub new file mode 100644 index 0000000..07c5fa6 --- /dev/null +++ b/app/mzl/generate/templates/ModelStub.stub @@ -0,0 +1,56 @@ + function ($params) { + // $keyword = $params['keyword'] ?? ''; + // return [ + // 'filters' => [ + // ['field' => '***', 'operator' => '=', 'value' => '***'] + // ], + // 'fields' => ['id'], + // 'relations' => [], + // 'custom' => function(Builder $q) use ($keyword) { + // // 示例 + // if ($keyword) { + // $q->where(function($q2) use($keyword) { + // $q2->whereHas('***', function($q3) use($keyword) { + // $q3->where('***', 'like', "%{$keyword}%"); + // })->orWhereHas('***', function($q4) use($keyword) { + // $q4->where('***', 'like', "%{$keyword}%"); + // }); + // }); + // } + // }, + // 'sorts' => [ + // ['field' => 'created_at', 'direction' => 'desc'] + // ], + // 'pagination' => [ + // 'page' => $params['page'] ?? 1, + // 'per_page' => $params['per_page'] ?? 10, + // ] + // ]; + // } + ]; + } +} \ No newline at end of file diff --git a/app/mzl/generate/templates/ValidatorStub.stub b/app/mzl/generate/templates/ValidatorStub.stub new file mode 100644 index 0000000..9089654 --- /dev/null +++ b/app/mzl/generate/templates/ValidatorStub.stub @@ -0,0 +1,29 @@ + [ + + ], + + 'update' => [ + + ] + ]; +} \ No newline at end of file diff --git a/app/mzl/modules/jwt/middleware/JwtMiddleware.php b/app/mzl/modules/jwt/middleware/JwtMiddleware.php new file mode 100644 index 0000000..01654bd --- /dev/null +++ b/app/mzl/modules/jwt/middleware/JwtMiddleware.php @@ -0,0 +1,74 @@ +jwtService = new JwtService(); + } + + public function process(Request $request, callable $handler): Response + { + // 通过反射获取控制器 + $controller = new ReflectionClass($request->controller); + $noNeedJwt = $controller->getDefaultProperties()['noNeedJwt'] ?? []; + + // 除外 + if (in_array($request->action, $noNeedJwt)) { + + return $handler($request); + } else { + try { + $authHeader = $request->header('Authorization'); + + if (!$authHeader || !preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { + $body = [ + 'status' => 'error', + 'code' => 401, + 'message' => '未授权', + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + + $token = $matches[1]; + + $request->user = $this->jwtService->verifyToken($token); + + // 验证权限 -- start + // $user = \app\mzl\modules\permission\model\User::find($request->user->user_id); + // if (!$user || !$user->hasPermission($request->path())) { + // $body = [ + // 'status' => 'error', + // 'code' => 403, + // 'message' => '无权限访问', + // 'data' => [], + // ]; + // return response(json_encode($body, JSON_UNESCAPED_UNICODE), 403, ['Content-Type' => 'application/json']); + // } + // 验证权限 -- end + } catch (\Throwable $e) { + $body = [ + 'status' => 'error', + 'code' => 401, + 'message' => $e->getMessage(), + 'data' => [], + ]; + return response(json_encode($body, JSON_UNESCAPED_UNICODE), 401, ['Content-Type' => 'application/json']); + } + + return $handler($request); + } + } +} \ No newline at end of file diff --git a/app/mzl/modules/jwt/service/JwtService.php b/app/mzl/modules/jwt/service/JwtService.php new file mode 100644 index 0000000..6589558 --- /dev/null +++ b/app/mzl/modules/jwt/service/JwtService.php @@ -0,0 +1,70 @@ +secretKey = getenv('JWT_SECRET_KEY') ?: 'default_secret_key'; + $this->algorithm = getenv('JWT_ALGORITHM') ?: 'HS256'; + $this->issuer = getenv('JWT_ISSUER') ?: 'domain.com'; + $this->expiration = (int)(getenv('JWT_EXPIRATION') ?: 3600); + } + + /** + * 生成 JWT 令牌 + * + * @param array $payload + * @return string + * @throws BusinessException + */ + public function generateToken(array $payload): string + { + $defaultPayload = [ + 'iss' => $this->issuer, + 'iat' => time(), + 'nbf' => time(), + 'exp' => time() + $this->expiration, + ]; + + $payload = array_merge($defaultPayload, $payload); + + try { + return FirebaseJWT::encode($payload, $this->secretKey, $this->algorithm); + } catch (\Exception $e) { + throw new BusinessException("令牌生成失败:" . $e->getMessage(), 500); + } + } + + /** + * 验证 JWT 令牌 + * + * @param string $token + * @return object + * @throws BusinessException + */ + public function verifyToken(string $token) + { + try { + return FirebaseJWT::decode($token, new Key($this->secretKey, $this->algorithm)); + } catch (\Firebase\JWT\ExpiredException $e) { + throw new BusinessException('令牌已过期', 401); + } catch (\Firebase\JWT\SignatureInvalidException $e) { + throw new BusinessException('无效的令牌签名', 401); + } catch (\Firebase\JWT\BeforeValidException $e) { + throw new BusinessException('令牌尚未生效', 401); + } catch (\Exception $e) { + throw new BusinessException('令牌验证失败:' . $e->getMessage(), 400); + } + } +} \ No newline at end of file diff --git a/app/mzl/modules/permission/db/sql.sql b/app/mzl/modules/permission/db/sql.sql new file mode 100644 index 0000000..118b0dd --- /dev/null +++ b/app/mzl/modules/permission/db/sql.sql @@ -0,0 +1,31 @@ +CREATE TABLE `roles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(50) NOT NULL COMMENT '角色标识', + `label` varchar(100) DEFAULT NULL COMMENT '角色名称', + `created_at` int(11) unsigned DEFAULT '0', + `updated_at` int(11) unsigned DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色'; + +CREATE TABLE `permissions` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL COMMENT '权限标识', + `label` varchar(100) DEFAULT NULL COMMENT '权限名称', + `created_at` int(11) unsigned DEFAULT '0', + `updated_at` int(11) unsigned DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限'; + +CREATE TABLE `role_user` ( + `user_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + PRIMARY KEY (`user_id`,`role_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色_用户'; + +CREATE TABLE `permission_role` ( + `role_id` int(11) NOT NULL, + `permission_id` int(11) NOT NULL, + PRIMARY KEY (`role_id`,`permission_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限_角色'; \ No newline at end of file diff --git a/app/mzl/modules/permission/model/Permission.php b/app/mzl/modules/permission/model/Permission.php new file mode 100644 index 0000000..b7d4857 --- /dev/null +++ b/app/mzl/modules/permission/model/Permission.php @@ -0,0 +1,61 @@ +belongsToMany(Role::class, 'permission_role'); + } +} \ No newline at end of file diff --git a/app/mzl/modules/permission/model/Role.php b/app/mzl/modules/permission/model/Role.php new file mode 100644 index 0000000..7bddfd7 --- /dev/null +++ b/app/mzl/modules/permission/model/Role.php @@ -0,0 +1,66 @@ +belongsToMany(Permission::class, 'permission_role'); + } + + public function users() + { + return $this->belongsToMany(User::class, 'role_user'); + } +} \ No newline at end of file diff --git a/app/mzl/modules/permission/model/User.php b/app/mzl/modules/permission/model/User.php new file mode 100644 index 0000000..ee89691 --- /dev/null +++ b/app/mzl/modules/permission/model/User.php @@ -0,0 +1,84 @@ +belongsToMany(Role::class, 'role_user'); + } + + public function hasPermission2(string $permission): bool + { + // 加载角色和每个角色的权限 + $roles = $this->roles()->with('permissions')->get(); + // 判断是否有某个权限 + return $roles->pluck('permissions')->flatten()->pluck('name')->contains($permission); + } + + public function hasPermission(string $permission, int $ttl = 24 * 60 * 60): bool + { + $cacheKey = 'user_permissions_' . $this->id; + $permissions = Redis::get($cacheKey); + if (!$permissions) { + $roles = $this->roles()->with('permissions')->get(); + $permissions = $roles->pluck('permissions')->flatten()->pluck('name')->toArray(); + Redis::setEx($cacheKey, $ttl, json_encode($permissions)); + } else { + $permissions = json_decode($permissions, true); + } + return in_array($permission, $permissions); + } +} \ No newline at end of file diff --git a/app/mzl/modules/phpoffice/config/excel_import.php b/app/mzl/modules/phpoffice/config/excel_import.php new file mode 100644 index 0000000..cac2574 --- /dev/null +++ b/app/mzl/modules/phpoffice/config/excel_import.php @@ -0,0 +1,33 @@ + [ + 'column_map' => [ + 'A' => 'index', + 'B' => 'company', + 'C' => 'idcard', + 'D' => 'name', + 'E' => 'gender', + 'F' => 'nation', + 'G' => 'education', + 'H' => 'position', + 'I' => 'train_type', + 'J' => 'exam_type', + 'K' => 'phone', + 'L' => 'address', + 'M' => 'erp_code', + ] + ], + 'user' => [ + 'column_map' => [ + 'A' => 'realname', + 'B' => 'sex', + 'C' => 'phone', + 'D' => 'idcard', + 'E' => 'identity' + ] + ] +]; \ No newline at end of file diff --git a/app/mzl/modules/phpoffice/service/ExcelService.php b/app/mzl/modules/phpoffice/service/ExcelService.php new file mode 100644 index 0000000..00926cf --- /dev/null +++ b/app/mzl/modules/phpoffice/service/ExcelService.php @@ -0,0 +1,606 @@ +config = getConfig('/mzl/modules/phpoffice/config/excel_import.php'); + } + + /** + * 导入Excel数据 + */ + public function importToData(string $template_key, string $filePath): array + { + $config = $this->config[$template_key] ?? null; + if (!$config) { + throw new BusinessException("未找到模板配置:$template_key"); + } + $spreadsheet = IOFactory::load($filePath); + $sheet = $spreadsheet->getActiveSheet(); + $rows = $sheet->toArray(null, true, true, true); + $callback = $this->callback($template_key) ?? null; + return $this->toData($rows, $config['column_map'], $callback); + } + + /** + * 条件回调 + */ + public function callback(string $template_key) + { + switch ($template_key) { + case 'student': + $callback = function ($row, $i) { + return $i < 3 || empty($row['C']); + }; + break; + case 'user': + $callback = function ($row, $i) { + return $i < 2 || empty($row['D']); + }; + break; + } + return $callback; + } + + /** + * 获取数据 + */ + public function toData(array $rows = [], array $columnMap = [], $callback = null) + { + $data = []; + foreach ($rows as $i => $row) { + if ($callback && $callback($row, $i)) { + continue; + } + $item = []; + foreach ($columnMap as $excelKey => $dbField) { + $item[$dbField] = trim((string)($row[$excelKey] ?? '')); + } + $data[] = $item; + } + return $data; + } + + /** + * 导入practice + */ + public function importPractice(string $filePath) + { + Db::beginTransaction(); + try { + $spreadsheet = IOFactory::load($filePath); + $sheet = $spreadsheet->getSheet(0); + $rows = $sheet->toArray(); + + // 解析标题行 + $titleRow = trim($rows[0][0]); + preg_match('/(.*?)\s+([A-Za-z0-9-]+\/[A-Za-z0-9]+)$/', $titleRow, $matches); + + $practice = PracticeModel::create([ + 'sn' => generateRandomSN(), + 'title' => $matches[1] ?? '未知标题', + 'code' => $matches[2] ?? '未编号' + ]); + + $currentItem = null; + + foreach ($rows as $index => $row) { + // 跳过表头前两行(标题行+列头行) + if ($index < 2 || empty(array_filter($row))) continue; + + // 处理主检查项 + if (!empty($row[0]) && is_numeric($row[0])) { + $currentItem = PracticeItemsModel::create([ + 'practice_id' => $practice->id, + 'index' => $row[0], + 'exam_content' => $row[1], + 'scoring_element' => $row[2], + 'score_allocation' => $row[3] + ]); + } + + // 处理评分标准 + if ($currentItem && !empty($row[4])) { + PracticeItemsScoreModel::create([ + 'practice_item_id' => $currentItem->id, + 'scoring_criteria' => $row[4], + 'max_deduction' => $row[5], + 'single_deduction' => $row[6] + ]); + } + } + + DB::commit(); + return true; + } catch (\Throwable $e) { + DB::rollBack(); + return false; + } + } + + + // ====== ====== ====== 公共方法 ====== ====== ====== + private function setColumnWidths($sheet, array $widths) + { + foreach ($widths as $column => $width) { + $sheet->getColumnDimension($column)->setWidth($width); + } + } + private function setRowHeights($sheet, array $heights) + { + foreach ($heights as $row => $height) { + $sheet->getRowDimension($row)->setRowHeight($height); + } + } + private function centerAlignAllCells($sheet) + { + $maxRow = $sheet->getHighestRow(); + $maxCol = $sheet->getHighestColumn(); + $sheet->getStyle("A1:{$maxCol}{$maxRow}")->applyFromArray([ + 'alignment' => [ + 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER, + 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER, + ] + ]); + } + private function setGlobalFont($sheet, $start = null, $end = null, $name = '宋体', $size = 10) + { + if ($start && $end) { + $fw = "{$start}:{$end}"; + } else { + $maxRow = $sheet->getHighestRow(); + $maxCol = $sheet->getHighestColumn(); + $fw = "A1:{$maxCol}{$maxRow}"; + } + $sheet->getStyle($fw)->applyFromArray([ + 'font' => [ + 'name' => $name, + 'size' => $size, + ], + ]); + } + // ====== ====== ====== 公共方法 ====== ====== ====== + + + /** + * 导出 班级总结 + */ + public function exportClassSummary2(array $data, string $outputPath = '班级总结', ?string $signatureImage = null) + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // ====== 1. 标题 & 基础信息 ====== + $sheet->setCellValue('A1', $data['title'] ?? 'XX(班级名称)班级信息'); + // 标题行立即合并 + $sheet->mergeCells('A1:C1'); + + $basicInfo = [ + ['开班地点', 'location'], + ['开班类型', 'type'], + ['培训项目', 'program'], + ['授课老师', 'teacher'], + ['培训时间', 'time'], + ['参培学员', 'students'], + ['合格人员', 'passed'], + ['通过率', 'pass_rate'], + ]; + $row = 2; + foreach ($basicInfo as $info) { + [$label, $key] = $info; + $sheet->setCellValue("A{$row}", $label); + $sheet->setCellValue("B{$row}", $data[$key] ?? ''); + $sheet->mergeCells("B{$row}:C{$row}"); + $row++; + } + + // ====== 2. 理论考试 ====== + if (! empty($data['theory_exams'])) { + foreach ($data['theory_exams'] as $idx => $exam) { + $subject = $exam['subject'] ?? "科目" . ($idx + 1); + $fields = [ + '考试时间' => 'time', + '考试地点' => 'location', + '主监考' => 'main_invigilator', + '监考人员' => 'invigilators', + '不及格人数' => 'fail_count', + '合格率' => 'pass_rate', + ]; + $blockSize = count($fields); + $startRow = $row; + $endRow = $row + $blockSize - 1; + + $sheet->mergeCells("A{$startRow}:A{$endRow}"); + $sheet->setCellValue("A{$startRow}", "理论考试信息({$subject})"); + + $r = $startRow; + foreach ($fields as $label => $key) { + $sheet->setCellValue("B{$r}", $label); + $sheet->setCellValue("C{$r}", $exam[$key] ?? ''); + $r++; + } + + $row = $endRow + 1; + } + } + + // ====== 3. 实操考试 ====== + if (! empty($data['practice_exams'])) { + foreach ($data['practice_exams'] as $idx => $exam) { + $subject = $exam['subject'] ?? "实操" . ($idx + 1); + $fields = [ + '考试科目' => 'subject', + '考试时间' => 'time', + '考试地点' => 'location', + '不及格人数' => 'fail_count', + '合格率' => 'pass_rate', + ]; + $blockSize = count($fields); + $startRow = $row; + $endRow = $row + $blockSize - 1; + + $sheet->mergeCells("A{$startRow}:A{$endRow}"); + $sheet->setCellValue("A{$startRow}", "实操考试信息({$subject})"); + + $r = $startRow; + foreach ($fields as $label => $key) { + $sheet->setCellValue("B{$r}", $label); + $sheet->setCellValue("C{$r}", $exam[$key] ?? ''); + $r++; + } + + $row = $endRow + 1; + } + } + + // ====== 5. 样式:合并、边框、对齐、行高、字体 ====== + $lastRow = $row; + + // 全区域合并边框 + $sheet->getStyle("A1:C{$lastRow}")->applyFromArray([ + 'borders' => ['allBorders'=>['borderStyle'=>Border::BORDER_THIN]], + ]); + + // 对齐 + $maxCol = $sheet->getHighestColumn(); + $sheet->getStyle("A1:{$maxCol}{$lastRow}")->applyFromArray([ + 'alignment'=>[ + 'horizontal'=>\PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER, + 'vertical' =>\PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER, + ] + ]); + + // 字体 + // 标题行 + $sheet->getStyle('A1')->applyFromArray([ + 'font'=>['name'=>'黑体','size'=>22], + ]); + // 正文字体 + $sheet->getStyle("A2:C{$lastRow}")->applyFromArray([ + 'font'=>['name'=>'宋体','size'=>10], + ]); + // 班主任行单独字号 12 + $sheet->getStyle("A{$row}:C{$row}")->applyFromArray([ + 'font'=>['name'=>'宋体','size'=>12], + ]); + + // 行高 + $sheet->getRowDimension(1)->setRowHeight(36); + for ($r = 2; $r < $lastRow; $r++) { + $sheet->getRowDimension($r)->setRowHeight(21.75); + } + $sheet->getRowDimension($lastRow)->setRowHeight(54); + + // 列宽 + $sheet->getColumnDimension('A')->setWidth(30); + $sheet->getColumnDimension('B')->setWidth(30); + $sheet->getColumnDimension('C')->setWidth(24); + + // ====== 4. 班主任签名(水平+垂直居中) ====== + $sheet->setCellValue("A{$row}", '班主任'); + // 合并 B、C 列 + $sheet->mergeCells("B{$row}:C{$row}"); + + if ($signatureImage && file_exists($signatureImage)) { + $drawing = new Drawing(); + $drawing->setPath($signatureImage); + // 先指定高度,等比例缩放宽度 + $drawing->setHeight(40); + $drawing->setResizeProportional(true); + // 锚定到 B{$row} 单元格 + $drawing->setCoordinates("B{$row}"); + // 必须先 add 到工作表,才能正确计算后续的 getWidth()/getHeight() + $drawing->setWorksheet($sheet); + + // —— 计算区域像素尺寸 —— + // 列宽单位转像素近似:1 单位 ≈ 7px + // 行高点(pt)转像素近似:1pt ≈ 96/72 px + $pxPerWidth = 7; + $pxPerPoint = 96 / 72; + + $widthB = $sheet->getColumnDimension('B')->getWidth(); + $widthC = $sheet->getColumnDimension('C')->getWidth(); + $totalWidthPx = ($widthB + $widthC) * $pxPerWidth; + + $rowHeightPt = $sheet->getRowDimension($row)->getRowHeight(); + $totalHeightPx = $rowHeightPt * $pxPerPoint; + + // 这时 getWidth()/getHeight() 才会返回渲染后的像素值 + $imageWidthPx = $drawing->getWidth(); + $imageHeightPx = $drawing->getHeight(); + + // 计算居中偏移 + $offsetX = max(0, ($totalWidthPx - $imageWidthPx) / 2); + $offsetY = max(0, ($totalHeightPx - $imageHeightPx) / 2); + + $drawing->setOffsetX($offsetX); + $drawing->setOffsetY($offsetY); + } else { + $sheet->setCellValue("B{$row}", $data['class_teacher'] ?? ''); + } + + // 最后保存 + $writer = new Xlsx($spreadsheet); + $writer->save($outputPath); + } + + + public function exportClassSummary(array $data, string $outputPath = '班级总结', ?string $signatureImage = null) + { + $spreadsheet = $this->buildClassSummarySpreadsheet($data, $signatureImage); + $writer = new Xlsx($spreadsheet); + $writer->save($outputPath); + } + + /** + * 构建班级总结的 Spreadsheet 对象(复用给 XLSX 和 PDF) + * + * @param array $data + * @param string|null $signatureImage + * @return Spreadsheet + */ + protected function buildClassSummarySpreadsheet(array $data, ?string $signatureImage = null): Spreadsheet + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + + // + // 1. 标题 & 基础信息 + // + $sheet->setCellValue('A1', $data['title'] ?? 'XX(班级名称)班级信息'); + $sheet->mergeCells('A1:C1'); + + $basicInfo = [ + ['开班地点', 'location'], + ['开班类型', 'type'], + ['培训项目', 'program'], + ['授课老师', 'teacher'], + ['培训时间', 'time'], + ['参培学员', 'students'], + ['合格人员', 'passed'], + ['通过率', 'pass_rate'], + ]; + $row = 2; + foreach ($basicInfo as $info) { + [$label, $key] = $info; + $sheet->setCellValue("A{$row}", $label); + $sheet->setCellValue("B{$row}", $data[$key] ?? ''); + $sheet->mergeCells("B{$row}:C{$row}"); + $row++; + } + + // + // 2. 理论考试(theory_exams) + // + if (! empty($data['theory_exams']) && is_array($data['theory_exams'])) { + foreach ($data['theory_exams'] as $idx => $exam) { + $subject = $exam['subject'] ?? '科目' . ($idx + 1); + $fields = [ + '考试时间' => 'time', + '考试地点' => 'location', + '主监考' => 'main_invigilator', + '监考人员' => 'invigilators', + '不及格人数' => 'fail_count', + '合格率' => 'pass_rate', + ]; + $blockSize = count($fields); + $startRow = $row; + $endRow = $row + $blockSize - 1; + + $sheet->mergeCells("A{$startRow}:A{$endRow}"); + $sheet->setCellValue("A{$startRow}", "理论考试信息({$subject})"); + + $r = $startRow; + foreach ($fields as $label => $key) { + $sheet->setCellValue("B{$r}", $label); + $sheet->setCellValue("C{$r}", $exam[$key] ?? ''); + $r++; + } + + $row = $endRow + 1; + } + } + + // + // 3. 实操考试(practice_exams) + // + if (! empty($data['practice_exams']) && is_array($data['practice_exams'])) { + foreach ($data['practice_exams'] as $idx => $exam) { + $subject = $exam['subject'] ?? '实操' . ($idx + 1); + $fields = [ + '考试科目' => 'subject', + '考试时间' => 'time', + '考试地点' => 'location', + '不及格人数' => 'fail_count', + '合格率' => 'pass_rate', + ]; + $blockSize = count($fields); + $startRow = $row; + $endRow = $row + $blockSize - 1; + + $sheet->mergeCells("A{$startRow}:A{$endRow}"); + $sheet->setCellValue("A{$startRow}", "实操考试信息({$subject})"); + + $r = $startRow; + foreach ($fields as $label => $key) { + $sheet->setCellValue("B{$r}", $label); + $sheet->setCellValue("C{$r}", $exam[$key] ?? ''); + $r++; + } + + $row = $endRow + 1; + } + } + + // + // 4. 样式预设:列宽、行高、边框、对齐、字体 + // + // 列宽 + $sheet->getColumnDimension('A')->setWidth(30); + $sheet->getColumnDimension('B')->setWidth(30); + $sheet->getColumnDimension('C')->setWidth(24); + + // 当前签名行号 + $lastRow = $row; + + // 行高 + $sheet->getRowDimension(1)->setRowHeight(36); + for ($r = 2; $r < $lastRow; $r++) { + $sheet->getRowDimension($r)->setRowHeight(21.75); + } + $sheet->getRowDimension($lastRow)->setRowHeight(54); + + // 边框 + 对齐 + 正文字体 + $sheet->getStyle("A1:C{$lastRow}")->applyFromArray([ + 'borders' => ['allBorders' => ['borderStyle' => Border::BORDER_THIN]], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + ], + 'font' => ['name' => '宋体', 'size' => 10], + ]); + + // 标题行字体 + $sheet->getStyle('A1')->applyFromArray([ + 'font' => ['name' => '黑体', 'size' => 22], + ]); + + // + // 5. 班主任签名 & 图片居中 + // + $sheet->setCellValue("A{$lastRow}", '班主任'); + $sheet->mergeCells("B{$lastRow}:C{$lastRow}"); + + if ($signatureImage && file_exists($signatureImage)) { + $drawing = new Drawing(); + $drawing->setPath($signatureImage); + $drawing->setResizeProportional(true); + $drawing->setHeight(40); + $drawing->setCoordinates("B{$lastRow}"); + $drawing->setWorksheet($sheet); + + // 近似像素转换系数 + $pxPerColWidth = 7; + $pxPerPoint = 96 / 72; + + // 区域宽度 & 高度 + $wB = $sheet->getColumnDimension('B')->getWidth(); + $wC = $sheet->getColumnDimension('C')->getWidth(); + $areaW = ($wB + $wC) * $pxPerColWidth; + $hPt = $sheet->getRowDimension($lastRow)->getRowHeight(); + $areaH = $hPt * $pxPerPoint; + + // 图片渲染后尺寸 + $imgW = $drawing->getWidth(); + $imgH = $drawing->getHeight(); + + // 计算偏移并应用 + $drawing->setOffsetX(max(0, ($areaW - $imgW) / 2)); + $drawing->setOffsetY(max(0, ($areaH - $imgH) / 2)); + } else { + // 文字班主任行单独字号 + $sheet->getStyle("B{$lastRow}:C{$lastRow}")->applyFromArray([ + 'font' => ['name' => '宋体', 'size' => 12], + ]); + $sheet->setCellValue("B{$lastRow}", $data['class_teacher'] ?? ''); + } + + return $spreadsheet; + } + + /** + * 导出 PDF 文件 + * + * @param array $data + * @param string $pdfPath + * @param string|null $signatureImage + */ + public function exportClassSummaryPdf(array $data, string $pdfPath, ?string $signatureImage = null) + { + // 1. 复用生成表格的逻辑 + $spreadsheet = $this->buildClassSummarySpreadsheet($data, $signatureImage); + + // // 2) mPDF 默认目录和字体数据 + // $configVars = new ConfigVariables(); + // $fontDirs = $configVars->getDefaults()['fontDir']; + // $fontVars = new FontVariables(); + // $fontData = $fontVars->getDefaults()['fontdata']; + // $myFontDir = base_path('resources/fonts'); + // $mpdfConfig = [ + // 'mode' => 'utf-8', + // 'format' => 'A4', + // 'fontDir' => array_merge($fontDirs, [$myFontDir]), + // 'fontdata' => array_merge($fontData, [ + // 'sourcehansc' => [ + // 'R' => 'SourceHanSans-Regular.ttf', + // 'B' => 'SourceHanSans-Bold.ttf', + // ] + // ]), + // 'default_font'=> 'sourcehansc', + // 'autoScriptToLang'=> true, + // 'autoLangToFont' => true, + // ]; + // // 2. 用 mPDF Writer 输出 PDF + // $pdfWriter = new PdfWriter($spreadsheet, $mpdfConfig); + // $pdfWriter->setFont('宋体'); + // $pdfWriter->setPaperSize(PageSetup::PAPERSIZE_A4); + // $pdfWriter->setOrientation(PageSetup::ORIENTATION_PORTRAIT); + // $pdfWriter->save($pdfPath); + + + // 2. 用 TCPDF Writer 输出 PDF + $tcpdfWriter = new TcPdfWriter($spreadsheet); + $tcpdfWriter->setFont('cid0cs'); + $tcpdfWriter->setPaperSize(PageSetup::PAPERSIZE_A4); + $tcpdfWriter->setOrientation(PageSetup::ORIENTATION_PORTRAIT); + $tcpdfWriter->save($pdfPath); + } +} \ No newline at end of file diff --git a/app/mzl/modules/translation/config/trans.php b/app/mzl/modules/translation/config/trans.php new file mode 100644 index 0000000..01b353a --- /dev/null +++ b/app/mzl/modules/translation/config/trans.php @@ -0,0 +1,38 @@ + [ + 'vehicle' => [ + 'translation_model' => \app\console\model\VehicleDataTranslationModel::class, + 'foreign_key' => 'vehicle_id', + ], + 'energy' => [ + 'translation_model' => \app\console\model\EnergyTranslationModel::class, + 'foreign_key' => 'energy_id', + ], + 'type' => [ + 'translation_model' => \app\console\model\TypesTranslationModel::class, + 'foreign_key' => 'type_id', + ], + 'model' => [ + 'translation_model' => \app\console\model\ModelTranslationModel::class, + 'foreign_key' => 'model_id', + ], + 'brand' => [ + 'translation_model' => \app\console\model\BrandTranslationModel::class, + 'foreign_key' => 'brand_id', + ], + 'agreement' => [ + 'translation_model' => \app\console\model\AgreementTranslationModel::class, + 'foreign_key' => 'agreement_id', + ], + 'location' => [ + 'translation_model' => \app\console\model\LocationTranslationModel::class, + 'foreign_key' => 'location_id', + ] + ], + + // 默认支持的目标语种列表 + 'locales' => ['zh','en','ru','ar'], +]; diff --git a/app/mzl/modules/translation/jobs/TranslationJob.php b/app/mzl/modules/translation/jobs/TranslationJob.php new file mode 100644 index 0000000..79a1ed4 --- /dev/null +++ b/app/mzl/modules/translation/jobs/TranslationJob.php @@ -0,0 +1,64 @@ +config = getConfig('/mzl/modules/translation/config/trans.php'); + $this->payload = $payload; + } + + public function handle(): void + { + $cfgAll = $this->config['entities']; + $pt = $this->payload; + $type = $pt['entityType']; + $id = $pt['entityId']; + $locale = $pt['locale']; + $sourceLocale = $pt['sourceLocale']; + $sourceFields = $pt['sourceFields']; + $clientFields = $pt['clientFields']; + + if (! isset($cfgAll[$type])) { + Log::warning("TranslationJob: 未配置实体 {$type}"); + return; + } + + $cfg = $cfgAll[$type]; + $modelClass = $cfg['translation_model']; + $foreignKey = $cfg['foreign_key']; + + // 决定采用前端提供、源语种还是第三方翻译 + if (! empty($clientFields)) { + $fields = $clientFields; + } elseif ($locale === $sourceLocale) { + $fields = $sourceFields; + } else { + $translator = new TranslatorClient(); + $fields = []; + foreach ($sourceFields as $key => $text) { + $fields[$key] = $translator->translate($text, $sourceLocale, $locale); + } + } + + // 写库 + try { + $modelClass::updateOrCreate( + [ + $foreignKey => $id, + 'locale' => $locale, + ], + array_merge($fields, ['locale' => $locale]) + ); + } catch (\Throwable $e) { + Log::error("TranslationJob 保存失败: {$type}#{$id} locale={$locale}: ".$e->getMessage()); + } + } +} diff --git a/app/mzl/modules/translation/queue/Translation.php b/app/mzl/modules/translation/queue/Translation.php new file mode 100644 index 0000000..4c06c08 --- /dev/null +++ b/app/mzl/modules/translation/queue/Translation.php @@ -0,0 +1,50 @@ + 'tom@gmail.com', 'content' => 'hello'] + + $job = new TranslationJob($data); + $job->handle(); + } catch (\Throwable $e) { + Log::error("TranslationService 异步任务异常: ".$e->getMessage()); + } + } + // 消费失败回调 + /* + $package = [ + 'id' => 1357277951, // 消息ID + 'time' => 1709170510, // 消息时间 + 'delay' => 0, // 延迟时间 + 'attempts' => 2, // 消费次数 + 'queue' => 'send-mail', // 队列名 + 'data' => ['to' => 'tom@gmail.com', 'content' => 'hello'], // 消息内容 + 'max_attempts' => 5, // 最大重试次数 + 'error' => '错误信息' // 错误信息 + ] + */ + public function onConsumeFailure(\Throwable $e, $package) + { + echo "consume failure\n"; + echo $e->getMessage() . "\n"; + // 无需反序列化 + var_export($package); + } +} \ No newline at end of file diff --git a/app/mzl/modules/translation/service/TranslationService.php b/app/mzl/modules/translation/service/TranslationService.php new file mode 100644 index 0000000..a678a33 --- /dev/null +++ b/app/mzl/modules/translation/service/TranslationService.php @@ -0,0 +1,41 @@ +config = getConfig('/mzl/modules/translation/config/trans.php'); + } + + /** + * 异步排队所有语种的翻译任务 + * + * @param string $entityType 实体名,如 'vehicle' + * @param int $entityId 主表 ID + * @param string $sourceLocale 源语种,如 'zh' + * @param array $sourceFields 源语种字段 ['name'=>…,'condition_desc'=>…] + * @param array $clientFields 前端已传翻译 ['en'=>[...], …] + */ + public function queueTranslations(string $entityType, int $entityId, string $sourceLocale, array $sourceFields, array $clientFields = []): void + { + foreach ($this->config['locales'] as $locale) { + $payload = [ + 'entityType' => $entityType, + 'entityId' => $entityId, + 'locale' => $locale, + 'sourceLocale' => $sourceLocale, + 'sourceFields' => $sourceFields, + 'clientFields' => $clientFields[$locale] ?? [], + ]; + Redis::send('transTask', $payload); + } + } +} \ No newline at end of file diff --git a/app/mzl/modules/translation/service/TranslatorClient.php b/app/mzl/modules/translation/service/TranslatorClient.php new file mode 100644 index 0000000..5b2b652 --- /dev/null +++ b/app/mzl/modules/translation/service/TranslatorClient.php @@ -0,0 +1,38 @@ +endpoint = env('TRANSLATION_API_URL'); + // $this->apiKey = env('TRANSLATION_API_KEY'); + // } + + /** + * 翻译文本 + * @throws \RuntimeException + */ + public function translate(string $text, string $from = 'cn', string $to = 'ru'): string + { + try { + $xunfei = new XunfeiFanyi(); + $rst_dst = $xunfei->fanyi($text, $from, $to); + if ($rst_dst) { + return $rst_dst["trans_result"]["dst"]; + } else { + return ''; + } + } catch (\Throwable $e) { + Log::error("TranslatorClient 错误: ".$e->getMessage()); + throw new \RuntimeException('翻译服务调用失败'); + } + } +} diff --git a/app/mzl/modules/upload/controller/UploadController.php b/app/mzl/modules/upload/controller/UploadController.php new file mode 100644 index 0000000..7114fa0 --- /dev/null +++ b/app/mzl/modules/upload/controller/UploadController.php @@ -0,0 +1,68 @@ +input('scene', 'image'); + $file = $request->file('file'); + if ($file && $file->isValid()) { + $pathUrl = '/uploads/'.$scene.'/'.date('Ymd').'/'.md5($file->getUploadName().time()).'.'.$file->getUploadExtension(); + $fullPathUrl = public_path().$pathUrl; + $file->move($fullPathUrl); + $data = [ + 'domain' => getenv('MAIN_DOMAIN'), + 'file_url' => $pathUrl, + 'full_url' => getenv('MAIN_DOMAIN').$pathUrl, + 'scene' => $scene, + ]; + return $this->successResponse($data, 'upload success'); + } + return $this->errorResponse('file not found'); + } catch (\Throwable $e) { + return $this->errorResponse($e->getMessage()); + } + } + + // 上传单文件图片 + public function uploadImage(Request $request) + { + try { + $file = $request->file('image'); + + if (!$file || !$file->isValid()) { + return $this->errorResponse('上传失败'); + } + + $result = Storage::adapter('oss') + ->path('vehicle_album/'.date('Ymd')) + ->size(1024*1024*5) + ->extYes(['image/jpeg','image/png']) + ->extNo(['image/gif']) + ->upload($file); + + $ossservice = new OssService(); + + $data = [ + 'image_name' => $result->file_name, + 'image_url' => $ossservice->generatePrivateUrl($result->file_name) + ]; + + return $this->successResponse($data); + }catch (\Throwable $e){ + var_dump($e->getFile().'-'.$e->getLine().'-'.$e->getMessage()); + return $this->errorResponse($e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/mzl/modules/upload/service/LocalService.php b/app/mzl/modules/upload/service/LocalService.php new file mode 100644 index 0000000..61a9fda --- /dev/null +++ b/app/mzl/modules/upload/service/LocalService.php @@ -0,0 +1,33 @@ +file('file'); + if ($file && $file->isValid()) { + $pathUrl = $path == '' ? '/uploads/'.$type.'/'.date('Ymd').'/'.md5($file->getUploadName().time()).'.'.$file->getUploadExtension() : $path; + $fullPathUrl = public_path().$pathUrl; + $file->move($fullPathUrl); + $data = [ + 'file_url' => $pathUrl, + 'full_url' => getenv('MAIN_DOMAIN').$pathUrl + ]; + return $data; + } + return false; + } +} \ No newline at end of file diff --git a/app/mzl/modules/upload/service/OssService.php b/app/mzl/modules/upload/service/OssService.php new file mode 100644 index 0000000..e2f3a48 --- /dev/null +++ b/app/mzl/modules/upload/service/OssService.php @@ -0,0 +1,59 @@ +accessKeyId = getenv('OSS_ACCESSID'); + $this->accessKeySecret = getenv('OSS_ACCESSSECRET'); + $this->bucket = getenv('OSS_BUCKET'); + $this->endpoint = getenv('OSS_ENDPOINT'); + $this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint); + } + + /** + * 上传图片到 OSS + * @param string $filePath 本地文件路径 + * @param string $objectKey OSS上存储的对象名称 + * @return string 图片的 URL + */ + public function uploadImage($filePath, $objectKey) + { + try { + // 上传文件到 OSS + $this->ossClient->uploadFile($this->bucket, $objectKey, $filePath); + // 构造图片的私有 URL + $url = $this->generatePrivateUrl($objectKey); + return $url; + } catch (OssException $e) { + // 错误处理 + throw new \RuntimeException('OSS Error: ' . $e->getMessage()); + } catch (\Throwable $th) { + // 错误处理 + throw new BusinessException('Error: ' . $th->getMessage()); + } + } + + /** + * 生成图片的私有访问 URL + * @param string $objectKey + * @return string + */ + public function generatePrivateUrl($objectKey, $expireTime = 3600) + { + // 生成 OSS 私有 URL(有效期为 3600 秒) + return $this->ossClient->signUrl($this->bucket, $objectKey, $expireTime); + } +} \ No newline at end of file diff --git a/app/mzl/modules/wechat/service/WechatService.php b/app/mzl/modules/wechat/service/WechatService.php new file mode 100644 index 0000000..094a283 --- /dev/null +++ b/app/mzl/modules/wechat/service/WechatService.php @@ -0,0 +1,174 @@ +config = $this->loadConfig($type); + } + + /** + * 加载配置 + * + * @param string $type 类型 + * @return array + * @throws BusinessException + */ + public function loadConfig(string $type): array + { + switch ($type) { + case 'OfficialAccount': + $data = [ + 'app_id' => getenv('OFFICIALACCOUNT_APP_ID'), + 'secret' => getenv('OFFICIALACCOUNT_SECRET'), + 'oauth' => [ + 'scopes' => getenv('OFFICIALACCOUNT_OAUTH_SCOPES') + ] + ]; + break; + + case 'MiniApp': + $data = [ + 'app_id' => getenv('MINIAPP_APP_ID'), + 'secret' => getenv('MINIAPP_SECRET'), + 'response_type' => 'array', + 'log' => [ + 'level' => 'debug', + 'file' => runtime_path().'/mzl/wechat.log', + ] + ]; + break; + + case 'Pay': + $data = [ + 'mch_id' => getenv('MCH_ID'), + 'private_key' => base_path() . getenv('PRIVATE_KEY'), + 'certificate' => base_path() . getenv('CERTIFICATE'), + 'secret_key' => getenv('SECRET_KEY'), + 'v2_secret_key' => getenv('V2_SECRET_KEY'), + ]; + break; + + default: + throw new BusinessException('不支持的类型'); + break; + } + return $data; + } + + // === === === === === === 小程序相关方法 === === === === === === + + /** + * 小程序获取openid + * @param string $code + * @return array + * @throws BusinessException + */ + public function xcxOpenid(string $code): array + { + // 创建微信小程序对象 + $wechat = new xcxApplication($this->config); + // 获取 session_key 和 openid + return $wechat->getUtils()->codeToSession($code); + } + + /** + * 小程序获取手机号 + * @param string $phone_code + * @return mixed + * @throws BusinessException + */ + public function xcxPhoneNumber(string $phone_code) + { + // 创建微信小程序对象 + $wechat = new xcxApplication($this->config); + // getPhoneNumber + $data = ['code' => $phone_code]; + $result = $wechat->getClient()->postJson('wxa/business/getuserphonenumber', $data); + if (isset($result['phone_info'])) { + // 业务处理 $result['phone_info']['phoneNumber'] + return $result; + } else { + throw new BusinessException($result['errmsg']); + } + } + + // === === === === === === 公众号相关方法 === === === === === === + + /** + * 公众号获取openid + * @param string $code + * @return mixed + * @throws BusinessException + */ + public function gzhOpenid(string $code) + { + $app = new gzhApplication($this->config); + $app->getClient(); + $oauth = $app->getOAuth(); + return $oauth->userFromCode($code); + } + + /** + * 公众号发送模板消息 + * @param string $openid + * @param string $cagetory + * @param string $area + * @param string $time + * @param int $city_id + * @param int $category_id + * @return bool + * @throws BusinessException + */ + public function gzhSendNotice(string $openid, string $cagetory, string $area, string $time, $city_id, $category_id) + { + $wechat = new gzhApplication($this->config); + $token = $wechat->getAccessToken()->getToken(); + $template = [ + 'touser' => $openid, + 'template_id' => 'NKThzslzC2XabRjfrq5M-TQO1zILJts5gklQlwvG1W8', + "miniprogram" => [ + "appid" => "wxdf4bb512456d3463", + "pagepath" => "pages/index/index?category_id=$category_id&city_id=$city_id&city_name=$area" + ], + 'data' => [ + 'thing3' => ['value' => $cagetory], + 'thing23' => ['value' => $area], + 'time25' => ['value' => $time] + ] + ]; + $url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=".$token; + $result = $wechat->getClient()->postJson($url, $template); + if ($result['errcode'] == 0) { + return true; + }else{ + Log::warning($result->getContent()); + throw new BusinessException($result->getContent()); + } + } + + /** + * 公众号获取用户信息 + * @param string $openid + * @return string + * @throws BusinessException + */ + public function gzhUserinfo(string $openid) + { + $wechat = new gzhApplication($this->config); + $token = $wechat->getAccessToken()->getToken(); + $url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=$token&openid=$openid&lang=zh_CN"; + $result = $wechat->getClient()->get($url); + return $result->getContent(); + } +} \ No newline at end of file diff --git a/app/process/Http.php b/app/process/Http.php new file mode 100644 index 0000000..f462c3a --- /dev/null +++ b/app/process/Http.php @@ -0,0 +1,10 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace app\process; + +use FilesystemIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use SplFileInfo; +use Workerman\Timer; +use Workerman\Worker; + +/** + * Class FileMonitor + * @package process + */ +class Monitor +{ + /** + * @var array + */ + protected array $paths = []; + + /** + * @var array + */ + protected array $extensions = []; + + /** + * @var array + */ + protected array $loadedFiles = []; + + /** + * @var int + */ + protected int $ppid = 0; + + /** + * Pause monitor + * @return void + */ + public static function pause(): void + { + file_put_contents(static::lockFile(), time()); + } + + /** + * Resume monitor + * @return void + */ + public static function resume(): void + { + clearstatcache(); + if (is_file(static::lockFile())) { + unlink(static::lockFile()); + } + } + + /** + * Whether monitor is paused + * @return bool + */ + public static function isPaused(): bool + { + clearstatcache(); + return file_exists(static::lockFile()); + } + + /** + * Lock file + * @return string + */ + protected static function lockFile(): string + { + return runtime_path('monitor.lock'); + } + + /** + * FileMonitor constructor. + * @param $monitorDir + * @param $monitorExtensions + * @param array $options + */ + public function __construct($monitorDir, $monitorExtensions, array $options = []) + { + $this->ppid = function_exists('posix_getppid') ? posix_getppid() : 0; + static::resume(); + $this->paths = (array)$monitorDir; + $this->extensions = $monitorExtensions; + foreach (get_included_files() as $index => $file) { + $this->loadedFiles[$file] = $index; + if (strpos($file, 'webman-framework/src/support/App.php')) { + break; + } + } + if (!Worker::getAllWorkers()) { + return; + } + $disableFunctions = explode(',', ini_get('disable_functions')); + if (in_array('exec', $disableFunctions, true)) { + echo "\nMonitor file change turned off because exec() has been disabled by disable_functions setting in " . PHP_CONFIG_FILE_PATH . "/php.ini\n"; + } else { + if ($options['enable_file_monitor'] ?? true) { + Timer::add(1, function () { + $this->checkAllFilesChange(); + }); + } + } + + $memoryLimit = $this->getMemoryLimit($options['memory_limit'] ?? null); + if ($memoryLimit && ($options['enable_memory_monitor'] ?? true)) { + Timer::add(60, [$this, 'checkMemory'], [$memoryLimit]); + } + } + + /** + * @param $monitorDir + * @return bool + */ + public function checkFilesChange($monitorDir): bool + { + static $lastMtime, $tooManyFilesCheck; + if (!$lastMtime) { + $lastMtime = time(); + } + clearstatcache(); + if (!is_dir($monitorDir)) { + if (!is_file($monitorDir)) { + return false; + } + $iterator = [new SplFileInfo($monitorDir)]; + } else { + // recursive traversal directory + $dirIterator = new RecursiveDirectoryIterator($monitorDir, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS); + $iterator = new RecursiveIteratorIterator($dirIterator); + } + $count = 0; + foreach ($iterator as $file) { + $count ++; + /** var SplFileInfo $file */ + if (is_dir($file->getRealPath())) { + continue; + } + // check mtime + if (in_array($file->getExtension(), $this->extensions, true) && $lastMtime < $file->getMTime()) { + $lastMtime = $file->getMTime(); + if (DIRECTORY_SEPARATOR === '/' && isset($this->loadedFiles[$file->getRealPath()])) { + echo "$file updated but cannot be reloaded because only auto-loaded files support reload.\n"; + continue; + } + $var = 0; + exec('"'.PHP_BINARY . '" -l ' . $file, $out, $var); + if ($var) { + continue; + } + echo $file . " updated and reload\n"; + // send SIGUSR1 signal to master process for reload + if (DIRECTORY_SEPARATOR === '/' && $this->ppid > 1) { + posix_kill($this->ppid, SIGUSR1); + } else { + return true; + } + break; + } + } + if (!$tooManyFilesCheck && $count > 1000) { + echo "Monitor: There are too many files ($count files) in $monitorDir which makes file monitoring very slow\n"; + $tooManyFilesCheck = 1; + } + return false; + } + + /** + * @return bool + */ + public function checkAllFilesChange(): bool + { + if (static::isPaused()) { + return false; + } + foreach ($this->paths as $path) { + if ($this->checkFilesChange($path)) { + return true; + } + } + return false; + } + + /** + * @param $memoryLimit + * @return void + */ + public function checkMemory($memoryLimit): void + { + if (static::isPaused() || $memoryLimit <= 0) { + return; + } + $ppid = $this->ppid; + if ($ppid <= 1) { + return; + } + $processPath = "/proc/$ppid"; + if (!is_dir($processPath)) { + // Process not exist + $this->ppid = 0; + return; + } + $childrenFile = "/proc/$ppid/task/$ppid/children"; + if (!is_file($childrenFile) || !($children = file_get_contents($childrenFile))) { + return; + } + foreach (explode(' ', $children) as $pid) { + $pid = (int)$pid; + $statusFile = "/proc/$pid/status"; + if (!is_file($statusFile) || !($status = file_get_contents($statusFile))) { + continue; + } + $mem = 0; + if (preg_match('/VmRSS\s*?:\s*?(\d+?)\s*?kB/', $status, $match)) { + $mem = $match[1]; + } + $mem = (int)($mem / 1024); + if ($mem >= $memoryLimit) { + posix_kill($pid, SIGINT); + } + } + } + + /** + * Get memory limit + * @param $memoryLimit + * @return int + */ + protected function getMemoryLimit($memoryLimit): int + { + if ($memoryLimit === 0) { + return 0; + } + $usePhpIni = false; + if (!$memoryLimit) { + $memoryLimit = ini_get('memory_limit'); + $usePhpIni = true; + } + + if ($memoryLimit == -1) { + return 0; + } + $unit = strtolower($memoryLimit[strlen($memoryLimit) - 1]); + $memoryLimit = (int)$memoryLimit; + if ($unit === 'g') { + $memoryLimit = 1024 * $memoryLimit; + } else if ($unit === 'k') { + $memoryLimit = ($memoryLimit / 1024); + } else if ($unit === 'm') { + $memoryLimit = (int)($memoryLimit); + } else if ($unit === 't') { + $memoryLimit = (1024 * 1024 * $memoryLimit); + } else { + $memoryLimit = ($memoryLimit / (1024 * 1024)); + } + if ($memoryLimit < 50) { + $memoryLimit = 50; + } + if ($usePhpIni) { + $memoryLimit = (0.8 * $memoryLimit); + } + return (int)$memoryLimit; + } + +} diff --git a/app/view/index/view.html b/app/view/index/view.html new file mode 100644 index 0000000..67ebb26 --- /dev/null +++ b/app/view/index/view.html @@ -0,0 +1,14 @@ + + + + + + + + webman + + + +hello + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fe05350 --- /dev/null +++ b/composer.json @@ -0,0 +1,70 @@ +{ + "name": "workerman/webman", + "type": "project", + "keywords": [ + "high performance", + "http service" + ], + "homepage": "https://www.workerman.net", + "license": "MIT", + "description": "High performance HTTP Service Framework.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/webman/issues", + "forum": "https://wenda.workerman.net/", + "wiki": "https://workerman.net/doc/webman", + "source": "https://github.com/walkor/webman" + }, + "require": { + "php": ">=8.1", + "workerman/webman-framework": "^2.1", + "monolog/monolog": "^2.0", + "vlucas/phpdotenv": "^5.6", + "webman/database": "^2.1", + "illuminate/pagination": "^12.11", + "illuminate/events": "^12.13", + "symfony/var-dumper": "^7.2", + "symfony/filesystem": "^7.2", + "topthink/think-validate": "^3.0", + "symfony/translation": "^7.2", + "shopwwi/filesystem-oss": "^1.0", + "w7corp/easywechat": "^6.17", + "firebase/php-jwt": "^6.11", + "shopwwi/webman-filesystem": "^1.1", + "webman/console": "^2.1", + "webman/redis-queue": "^2.1", + "webman/redis": "^2.1" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "": "./", + "app\\": "./app", + "App\\": "./app", + "app\\View\\Components\\": "./app/view/components" + } + }, + "scripts": { + "post-package-install": [ + "support\\Plugin::install" + ], + "post-package-update": [ + "support\\Plugin::install" + ], + "pre-package-uninstall": [ + "support\\Plugin::uninstall" + ] + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a26ddcf --- /dev/null +++ b/composer.lock @@ -0,0 +1,6420 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "1c40fdbcb5f18044c59bc32cab6ef260", + "packages": [ + { + "name": "aliyuncs/oss-sdk-php", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/aliyun/aliyun-oss-php-sdk.git", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aliyun/aliyun-oss-php-sdk/zipball/483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "reference": "483dd0b8bff5d47f0e4ffc99f6077a295c5ccbb5", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "php-coveralls/php-coveralls": "*", + "phpunit/phpunit": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "OSS\\": "src/OSS" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aliyuncs", + "homepage": "http://www.aliyun.com" + } + ], + "description": "Aliyun OSS SDK for PHP", + "homepage": "http://www.aliyun.com/product/oss/", + "support": { + "issues": "https://github.com/aliyun/aliyun-oss-php-sdk/issues", + "source": "https://github.com/aliyun/aliyun-oss-php-sdk/tree/v2.7.2" + }, + "time": "2024-10-28T10:41:12+00:00" + }, + { + "name": "brick/math", + "version": "0.12.3", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", + "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.3" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-02-28T13:11:00+00:00" + }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.10", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^11.0", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.10" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2024-02-18T20:23:39+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/3d158f36e7875e2f040f37bc0573956240a5a38b", + "reference": "3d158f36e7875e2f040f37bc0573956240a5a38b", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6|^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2023-10-12T05:21:21+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945", + "reference": "3ba905c11371512af9d9bdd27d99b782216b6945", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:45:45+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:37:11+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:27:01+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-03-27T12:30:47+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/30e286560c137526eccd4ce21b2de477ab0676d2", + "reference": "30e286560c137526eccd4ce21b2de477ab0676d2", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-02-03T10:55:03+00:00" + }, + { + "name": "illuminate/bus", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/bus.git", + "reference": "2814b6d6ec18737a852055d44302bcac3221a95b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/bus/zipball/2814b6d6ec18737a852055d44302bcac3221a95b", + "reference": "2814b6d6ec18737a852055d44302bcac3221a95b", + "shasum": "" + }, + "require": { + "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 (^12.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Bus\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Bus package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-04-24T19:45:06+00:00" + }, + { + "name": "illuminate/collections", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/collections.git", + "reference": "97fadea4c64c9bf1e23519a26faaf3ac999e1cfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/collections/zipball/97fadea4c64c9bf1e23519a26faaf3ac999e1cfb", + "reference": "97fadea4c64c9bf1e23519a26faaf3ac999e1cfb", + "shasum": "" + }, + "require": { + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "php": "^8.2" + }, + "suggest": { + "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": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "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 Collections package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-05-02T14:54:36+00:00" + }, + { + "name": "illuminate/conditionable", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/conditionable.git", + "reference": "455a28c08f849db93078c9a60d765b88011affd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/conditionable/zipball/455a28c08f849db93078c9a60d765b88011affd2", + "reference": "455a28c08f849db93078c9a60d765b88011affd2", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Conditionable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-04-23T12:59:28+00:00" + }, + { + "name": "illuminate/container", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "cdd99204b29acf795a02f778542df5a5244361fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/cdd99204b29acf795a02f778542df5a5244361fd", + "reference": "cdd99204b29acf795a02f778542df5a5244361fd", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^12.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-22T17:41:27+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "bbaec083da240396f2186f4c3a9952da207f28a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/bbaec083da240396f2186f4c3a9952da207f28a0", + "reference": "bbaec083da240396f2186f4c3a9952da207f28a0", + "shasum": "" + }, + "require": { + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-19T20:10:05+00:00" + }, + { + "name": "illuminate/database", + "version": "v12.11.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/database.git", + "reference": "8a0fd964b726622e94b4d5d2ba65eff97e42b91d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/database/zipball/8a0fd964b726622e94b4d5d2ba65eff97e42b91d", + "reference": "8a0fd964b726622e94b4d5d2ba65eff97e42b91d", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12", + "ext-pdo": "*", + "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" + }, + "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 (^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": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Database\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Database package.", + "homepage": "https://laravel.com", + "keywords": [ + "database", + "laravel", + "orm", + "sql" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-04-29T14:23:22+00:00" + }, + { + "name": "illuminate/events", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/events.git", + "reference": "c3c837d505ba8914b309f89098a6cc9cf488dace" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/events/zipball/c3c837d505ba8914b309f89098a6cc9cf488dace", + "reference": "c3c837d505ba8914b309f89098a6cc9cf488dace", + "shasum": "" + }, + "require": { + "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": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Events\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Events package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-05-07T00:35:45+00:00" + }, + { + "name": "illuminate/filesystem", + "version": "v12.11.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/filesystem.git", + "reference": "4fa2995bad173c1a0067b285bb7fef47a4b441c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/filesystem/zipball/4fa2995bad173c1a0067b285bb7fef47a4b441c6", + "reference": "4fa2995bad173c1a0067b285bb7fef47a4b441c6", + "shasum": "" + }, + "require": { + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "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 (^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.2).", + "symfony/mime": "Required to enable support for guessing extensions (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php" + ], + "psr-4": { + "Illuminate\\Filesystem\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Filesystem package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-19T20:10:05+00:00" + }, + { + "name": "illuminate/http", + "version": "v12.11.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/http.git", + "reference": "baf0d722b978bd89a6454332aa5313c3fcb5fd25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/http/zipball/baf0d722b978bd89a6454332aa5313c3fcb5fd25", + "reference": "baf0d722b978bd89a6454332aa5313c3fcb5fd25", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "illuminate/collections": "^12.0", + "illuminate/macroable": "^12.0", + "illuminate/session": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.31" + }, + "suggest": { + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Http\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Http package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-04-09T21:27:11+00:00" + }, + { + "name": "illuminate/macroable", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/macroable.git", + "reference": "e862e5648ee34004fa56046b746f490dfa86c613" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/macroable/zipball/e862e5648ee34004fa56046b746f490dfa86c613", + "reference": "e862e5648ee34004fa56046b746f490dfa86c613", + "shasum": "" + }, + "require": { + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Macroable package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-07-23T16:31:01+00:00" + }, + { + "name": "illuminate/pagination", + "version": "v12.11.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pagination.git", + "reference": "82e4eff793c022bde06766ed7f0bf8ae5349ead9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pagination/zipball/82e4eff793c022bde06766ed7f0bf8ae5349ead9", + "reference": "82e4eff793c022bde06766ed7f0bf8ae5349ead9", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.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-04-03T17:58:28+00:00" + }, + { + "name": "illuminate/pipeline", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/pipeline.git", + "reference": "3c6df979e7e8ef09183a610cc0383179c1aba309" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/pipeline/zipball/3c6df979e7e8ef09183a610cc0383179c1aba309", + "reference": "3c6df979e7e8ef09183a610cc0383179c1aba309", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Pipeline\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Pipeline package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-19T20:10:05+00:00" + }, + { + "name": "illuminate/redis", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/redis.git", + "reference": "2ff95e5b90547dd87b9fd677bb292d7898875c08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/redis/zipball/2ff95e5b90547dd87b9fd677bb292d7898875c08", + "reference": "2ff95e5b90547dd87b9fd677bb292d7898875c08", + "shasum": "" + }, + "require": { + "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|^3.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Redis\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Redis package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-05-06T00:10:08+00:00" + }, + { + "name": "illuminate/session", + "version": "v12.11.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/session.git", + "reference": "54f55139d2a535681305f4d6027730b1b96cc7f1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/session/zipball/54f55139d2a535681305f4d6027730b1b96cc7f1", + "reference": "54f55139d2a535681305f4d6027730b1b96cc7f1", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-session": "*", + "illuminate/collections": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/filesystem": "^12.0", + "illuminate/support": "^12.0", + "php": "^8.2", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0" + }, + "suggest": { + "illuminate/console": "Required to use the session:table command (^12.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Session\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Session package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-03-19T20:10:05+00:00" + }, + { + "name": "illuminate/support", + "version": "v12.13.0", + "source": { + "type": "git", + "url": "https://github.com/illuminate/support.git", + "reference": "c2e145ffd418ac20960054cf746f762211e6dfe3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/support/zipball/c2e145ffd418ac20960054cf746f762211e6dfe3", + "reference": "c2e145ffd418ac20960054cf746f762211e6dfe3", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-mbstring": "*", + "illuminate/collections": "^12.0", + "illuminate/conditionable": "^12.0", + "illuminate/contracts": "^12.0", + "illuminate/macroable": "^12.0", + "nesbot/carbon": "^3.8.4", + "php": "^8.2", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "replace": { + "spatie/once": "*" + }, + "suggest": { + "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.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": "12.x-dev" + } + }, + "autoload": { + "files": [ + "functions.php", + "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 Support package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2025-05-06T20:32:43+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "reference": "b352cf0534aa1ae6b4d825d1e762e35d43f8a841", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2025-03-19T13:51:03+00:00" + }, + { + "name": "league/flysystem", + "version": "3.29.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" + }, + "time": "2024-10-08T08:58:34+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.29.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" + }, + "time": "2024-08-09T21:24:39+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7", + "reference": "5cf826f2991858b54d5c3809bee745560a1042a7", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.38 || ^9.6.19", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2024-11-12T12:43:37+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.9.1", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ced71f79398ece168e24f7f7710462f462310d4d", + "reference": "ced71f79398ece168e24f7f7710462f462310d4d", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.57.2", + "kylekatarnls/multi-tester": "^2.5.3", + "ondrejmirtes/better-reflection": "^6.25.0.4", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.11.2", + "phpunit/phpunit": "^10.5.20", + "squizlabs/php_codesniffer": "^3.9.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-05-01T19:51:51+00:00" + }, + { + "name": "nikic/fast-route", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, + "time": "2018-02-13T20:26:39+00:00" + }, + { + "name": "nyholm/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "reference": "a71f2b11690f4b24d099d6b16690a90ae14fc6f3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0", + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/message-factory": "^1.0", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.8.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2024-09-09T07:06:30+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/4335801d851f554ca43fa6e7d2602141538854dc", + "reference": "4335801d851f554ca43fa6e7d2602141538854dc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2023-11-08T09:30:43+00:00" + }, + { + "name": "overtrue/socialite", + "version": "4.11.2", + "source": { + "type": "git", + "url": "https://github.com/overtrue/socialite.git", + "reference": "83dd537a88b30cd9204ee2c46a5b2e181bc1fa66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/overtrue/socialite/zipball/83dd537a88b30cd9204ee2c46a5b2e181bc1fa66", + "reference": "83dd537a88b30cd9204ee2c46a5b2e181bc1fa66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "guzzlehttp/guzzle": "^7.0", + "php": ">=8.0.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.0", + "laravel/pint": "^1.2", + "mockery/mockery": "^1.3", + "phpstan/phpstan": "^1.7", + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/Contracts/FactoryInterface.php", + "src/Contracts/UserInterface.php", + "src/Contracts/ProviderInterface.php" + ], + "psr-4": { + "Overtrue\\Socialite\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "A collection of OAuth 2 packages.", + "keywords": [ + "Feishu", + "login", + "oauth", + "qcloud", + "qq", + "social", + "wechat", + "weibo" + ], + "support": { + "issues": "https://github.com/overtrue/socialite/issues", + "source": "https://github.com/overtrue/socialite/tree/4.11.2" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2024-10-08T16:23:14+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.3", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", + "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:41:07+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "shopwwi/filesystem-oss", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/filesystem-oss.git", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/filesystem-oss/zipball/85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "reference": "85cb385d1a5d11ca3c22bc485a7b18c588fe7645", + "shasum": "" + }, + "require": { + "aliyuncs/oss-sdk-php": "^2.4", + "league/flysystem": "^2.1|^3.0", + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\FilesystemOss\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "AliYun OSS adapter for flysystem V2. aliyuncs/oss-sdk-php ~2.4", + "homepage": "https://github.com/phcent/filesystem-oss", + "keywords": [ + "Flysystem", + "aliyun-oss", + "flysystem-aliyun-oss" + ], + "support": { + "issues": "https://github.com/shopwwi/filesystem-oss/issues", + "source": "https://github.com/shopwwi/filesystem-oss/tree/v1.0.3" + }, + "time": "2022-10-01T03:00:18+00:00" + }, + { + "name": "shopwwi/webman-filesystem", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/shopwwi/webman-filesystem.git", + "reference": "40d85f01d29513299c1d9d129e29d57624bebeae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shopwwi/webman-filesystem/zipball/40d85f01d29513299c1d9d129e29d57624bebeae", + "reference": "40d85f01d29513299c1d9d129e29d57624bebeae", + "shasum": "" + }, + "require": { + "league/flysystem": "^2.3|^3.0", + "php": ">=7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Shopwwi\\WebmanFilesystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tycoon Song", + "email": "8988354@qq.com" + } + ], + "description": "Flysystem V2 adapter for the webman", + "keywords": [ + "cos", + "filesystem", + "oss", + "qiniu", + "webman" + ], + "support": { + "issues": "https://github.com/shopwwi/webman-filesystem/issues", + "source": "https://github.com/shopwwi/webman-filesystem/tree/v1.1.1" + }, + "time": "2024-04-29T12:40:45+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/8b49dde3f5a5e9867595a3a269977f78418d75ee", + "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/cache": "^2.0|^3.0", + "psr/log": "^1.1|^2|^3", + "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.4|^7.0" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" + }, + "provide": { + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "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", + "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": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "classmap": [ + "Traits/ValueWrapper.php" + ], + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides extended PSR-6, PSR-16 (and tags) implementations", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "support": { + "source": "https://github.com/symfony/cache/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-08T09:06:23+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/cache": "^3.0" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/console", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-07T19:09:28+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "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": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.2.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^6.4|^7.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.2.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-03T07:12:39+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "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": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "reference": "b8dce482de9d7c9fe2891155035a7248ab5c7fdb", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.2.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-30T19:00:17+00:00" + }, + { + "name": "symfony/http-client", + "version": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "reference": "78981a2ffef6437ed92d4d7e2a86a82f256c6dc6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.2.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-13T10:27:23+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-07T08:49:48+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.2.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "371272aeb6286f8135e028ca535f8e4d6f114126" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/371272aeb6286f8135e028ca535f8e4d6f114126", + "reference": "371272aeb6286f8135e028ca535f8e4d6f114126", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.2.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-25T15:54:33+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.2.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b1fe91bc1fa454a806d3f98db4ba826eb9941a54", + "reference": "b1fe91bc1fa454a806d3f98db4ba826eb9941a54", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/css-selector": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/dom-crawler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^7.1", + "symfony/routing": "^6.4|^7.0", + "symfony/serializer": "^7.1", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0", + "symfony/var-exporter": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "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.2.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-28T13:32:50+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.2.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "87ca22046b78c3feaff04b337f33b38510fd686b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b", + "reference": "87ca22046b78c3feaff04b337f33b38510fd686b", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "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", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.2.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-19T08:51:20+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "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 for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c36586dcf89a12315939e00ec9b4474adcb1d773", + "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "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 for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "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 for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "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\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "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\\Php81\\": "" + }, + "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.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/2fb86d65e2d424369ad2905e83b236a8805ba491", + "reference": "2fb86d65e2d424369ad2905e83b236a8805ba491", + "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\\Php83\\": "" + }, + "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.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-26T08:57:56+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/string", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-20T20:18:16+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "reference": "e7fd8e2a4239b79a0fd9fb1fef3e0e7f969c6dc6", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-07T19:09:28+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "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": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:20:29+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.2.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/82b478c69745d8878eb60f9a049a4d584996f73a", + "reference": "82b478c69745d8878eb60f9a049a4d584996f73a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.2.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-17T11:39:41+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v7.2.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "422b8de94c738830a1e071f59ad14d67417d7007" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/422b8de94c738830a1e071f59ad14d67417d7007", + "reference": "422b8de94c738830a1e071f59ad14d67417d7007", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.2.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-02T08:36:00+00:00" + }, + { + "name": "thenorthmemory/xml", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/TheNorthMemory/xml.git", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheNorthMemory/xml/zipball/6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "reference": "6f50c63450a0b098772423f8bdc3c4ad2c4c30bb", + "shasum": "" + }, + "require": { + "ext-libxml": "*", + "ext-simplexml": "*", + "php": ">=7.1.2" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.89 || ^1.0", + "phpunit/phpunit": "^7.5 || ^8.5.16 || ^9.3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "TheNorthMemory\\Xml\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "James ZHANG", + "homepage": "https://github.com/TheNorthMemory" + } + ], + "description": "A wrapper of the XML parser and builder", + "homepage": "https://github.com/TheNorthMemory/xml", + "keywords": [ + "xml-builder", + "xml-parser" + ], + "support": { + "issues": "https://github.com/TheNorthMemory/xml/issues", + "source": "https://github.com/TheNorthMemory/xml/tree/1.1.1" + }, + "time": "2023-01-15T06:01:13+00:00" + }, + { + "name": "topthink/think-container", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-container.git", + "reference": "a24d442a02fb2a4716de232ff1a4f006c178a370" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-container/zipball/a24d442a02fb2a4716de232ff1a4f006c178a370", + "reference": "a24d442a02fb2a4716de232ff1a4f006c178a370", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "psr/container": "^2.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "PHP Container & Facade Manager", + "support": { + "issues": "https://github.com/top-think/think-container/issues", + "source": "https://github.com/top-think/think-container/tree/v3.0.1" + }, + "time": "2025-01-07T08:19:23+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.11", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "reference": "1d6ada9b9f3130046bf6922fe1bd159c8d88a33c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.11" + }, + "time": "2025-04-07T06:55:59+00:00" + }, + { + "name": "topthink/think-validate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-validate.git", + "reference": "f7dd85675270e9f8c0b04a13362133067629f53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-validate/zipball/f7dd85675270e9f8c0b04a13362133067629f53c", + "reference": "f7dd85675270e9f8c0b04a13362133067629f53c", + "shasum": "" + }, + "require": { + "php": ">=8.0", + "topthink/think-container": ">=3.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/helper.php" + ], + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think validate", + "support": { + "issues": "https://github.com/top-think/think-validate/issues", + "source": "https://github.com/top-think/think-validate/tree/v3.0.5" + }, + "time": "2025-03-05T06:11:48+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.3", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.3", + "symfony/polyfill-ctype": "^1.24", + "symfony/polyfill-mbstring": "^1.24", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2024-07-20T21:52:34+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "w7corp/easywechat", + "version": "6.17.5", + "source": { + "type": "git", + "url": "https://github.com/w7corp/easywechat.git", + "reference": "ea6460149b20a0a31d614d18c5c8cbe77414c4ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/w7corp/easywechat/zipball/ea6460149b20a0a31d614d18c5c8cbe77414c4ca", + "reference": "ea6460149b20a0a31d614d18c5c8cbe77414c4ca", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-fileinfo": "*", + "ext-libxml": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", + "overtrue/socialite": "^3.5.4|^4.0.1", + "php": ">=8.0.2", + "psr/http-client": "^1.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/polyfill-php81": "^1.25", + "symfony/psr-http-message-bridge": "^2.1.2|^6.4.0|^7.1", + "thenorthmemory/xml": "^1.0" + }, + "conflict": { + "overtrue/wechat": "*" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.0", + "laravel/pint": "^1.2", + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.4.4", + "phpstan/phpstan": "^1.0 | ^2", + "phpunit/phpunit": "^9.5", + "symfony/var-dumper": "^5.2|^6|^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "EasyWeChat\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "overtrue", + "email": "anzhengchao@gmail.com" + } + ], + "description": "微信SDK", + "keywords": [ + "easywechat", + "sdk", + "wechat", + "weixin", + "weixin-sdk" + ], + "support": { + "issues": "https://github.com/w7corp/easywechat/issues", + "source": "https://github.com/w7corp/easywechat/tree/6.17.5" + }, + "funding": [ + { + "url": "https://github.com/overtrue", + "type": "github" + } + ], + "time": "2025-03-19T00:52:30+00:00" + }, + { + "name": "webman/console", + "version": "v2.1.7", + "source": { + "type": "git", + "url": "https://github.com/webman-php/console.git", + "reference": "709b47beb74cb1e7494b4b93a06d1996618bdacf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/console/zipball/709b47beb74cb1e7494b4b93a06d1996618bdacf", + "reference": "709b47beb74cb1e7494b4b93a06d1996618bdacf", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "php": ">=8.1", + "symfony/console": ">=6.0" + }, + "require-dev": { + "workerman/webman": "^2.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\Console\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "Webman console", + "homepage": "http://www.workerman.net", + "keywords": [ + "webman console" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "http://www.workerman.net/questions", + "issues": "https://github.com/webman-php/console/issues", + "source": "https://github.com/webman-php/console", + "wiki": "http://www.workerman.net/doc/webman" + }, + "time": "2025-05-06T02:54:37+00:00" + }, + { + "name": "webman/database", + "version": "v2.1.6", + "source": { + "type": "git", + "url": "https://github.com/webman-php/database.git", + "reference": "943c5a0f92d8959d9160c2ee3925112b15c308c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/database/zipball/943c5a0f92d8959d9160c2ee3925112b15c308c8", + "reference": "943c5a0f92d8959d9160c2ee3925112b15c308c8", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0 || ^11.0 || ^12.0", + "illuminate/http": "^10.0 || ^11.0 || ^12.0", + "laravel/serializable-closure": "^1.0 || ^2.0", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\Database\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman database", + "support": { + "issues": "https://github.com/webman-php/database/issues", + "source": "https://github.com/webman-php/database/tree/v2.1.6" + }, + "time": "2025-04-04T13:23:25+00:00" + }, + { + "name": "webman/redis", + "version": "v2.1.3", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis.git", + "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis/zipball/559eb1692d39c6fef5cf526223fff728be6c0fb9", + "reference": "559eb1692d39c6fef5cf526223fff728be6c0fb9", + "shasum": "" + }, + "require": { + "illuminate/redis": "^10.0 || ^11.0 || ^12.0", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "support\\": "src/support", + "Webman\\Redis\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Webman redis", + "support": { + "issues": "https://github.com/webman-php/redis/issues", + "source": "https://github.com/webman-php/redis/tree/v2.1.3" + }, + "time": "2025-03-14T03:52:14+00:00" + }, + { + "name": "webman/redis-queue", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/webman-php/redis-queue.git", + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webman-php/redis-queue/zipball/12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "reference": "12e1c9915ec64c91c0c9c255606bd0c7de27334c", + "shasum": "" + }, + "require": { + "ext-redis": "*", + "php": ">=8.1", + "workerman/redis-queue": "^1.2", + "workerman/webman-framework": "^2.1 || dev-master" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "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/v2.1.0" + }, + "time": "2025-01-24T12:56:01+00:00" + }, + { + "name": "workerman/coroutine", + "version": "v1.1.3", + "source": { + "type": "git", + "url": "https://github.com/workerman-php/coroutine.git", + "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/workerman-php/coroutine/zipball/df8fc428967d512a74a8a7d80355c1d40228c9fa", + "reference": "df8fc428967d512a74a8a7d80355c1d40228c9fa", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "psr/log": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "src", + "Workerman\\Coroutine\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Workerman coroutine", + "support": { + "issues": "https://github.com/workerman-php/coroutine/issues", + "source": "https://github.com/workerman-php/coroutine/tree/v1.1.3" + }, + "time": "2025-02-17T03:34:21+00:00" + }, + { + "name": "workerman/redis", + "version": "v2.0.5", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis.git", + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis/zipball/49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "reference": "49627c1809eff1ef7175eb8ee7549234a1d67ec5", + "shasum": "" + }, + "require": { + "php": ">=7", + "workerman/workerman": "^4.1.0||^5.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\Redis\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis/issues", + "source": "https://github.com/walkor/redis/tree/v2.0.5" + }, + "time": "2025-04-07T01:58:58+00:00" + }, + { + "name": "workerman/redis-queue", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/redis-queue.git", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/redis-queue/zipball/75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "reference": "75dbf7ed2ea228c45dc0df82c0fea35879b715d0", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "workerman/redis": "^1.0||^2.0", + "workerman/workerman": ">=4.0.20" + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\RedisQueue\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Message queue system written in PHP based on workerman and backed by Redis.", + "homepage": "http://www.workerman.net", + "support": { + "issues": "https://github.com/walkor/redis-queue/issues", + "source": "https://github.com/walkor/redis-queue/tree/v1.2.1" + }, + "time": "2025-01-02T09:21:45+00:00" + }, + { + "name": "workerman/webman-framework", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/walkor/webman-framework.git", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/webman-framework/zipball/f803bd867f07bb0929faef060b59a19a44186bfc", + "reference": "f803bd867f07bb0929faef060b59a19a44186bfc", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": ">=8.1", + "psr/container": ">=1.0", + "psr/log": "^3.0", + "workerman/workerman": "^5.1 || dev-master" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "files": [ + "./src/support/helpers.php" + ], + "psr-4": { + "Webman\\": "./src", + "Support\\": "./src/support", + "support\\": "./src/support", + "Support\\View\\": "./src/support/view", + "Support\\Bootstrap\\": "./src/support/bootstrap", + "Support\\Exception\\": "./src/support/exception" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "https://www.workerman.net", + "role": "Developer" + } + ], + "description": "High performance HTTP Service Framework.", + "homepage": "https://www.workerman.net", + "keywords": [ + "High Performance", + "http service" + ], + "support": { + "email": "walkor@workerman.net", + "forum": "https://wenda.workerman.net/", + "issues": "https://github.com/walkor/webman/issues", + "source": "https://github.com/walkor/webman-framework", + "wiki": "https://doc.workerman.net/" + }, + "time": "2025-03-10T11:52:22+00:00" + }, + { + "name": "workerman/workerman", + "version": "v5.1.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman.git", + "reference": "72cafa19e488896abd2576ea67b3b1af1abf71c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman/zipball/72cafa19e488896abd2576ea67b3b1af1abf71c0", + "reference": "72cafa19e488896abd2576ea67b3b1af1abf71c0", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=8.1", + "workerman/coroutine": "^1.1 || dev-main" + }, + "conflict": { + "ext-swow": "=8.1" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..f26e358 --- /dev/null +++ b/config/app.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Request; + +return [ + 'debug' => true, + 'error_reporting' => E_ALL, + 'default_timezone' => 'Asia/Shanghai', + 'request_class' => Request::class, + 'public_path' => base_path() . DIRECTORY_SEPARATOR . 'public', + 'runtime_path' => base_path(false) . DIRECTORY_SEPARATOR . 'runtime', + 'controller_suffix' => 'Controller', + 'controller_reuse' => false, +]; diff --git a/config/autoload.php b/config/autoload.php new file mode 100644 index 0000000..69a8135 --- /dev/null +++ b/config/autoload.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'files' => [ + base_path() . '/app/functions.php', + base_path() . '/support/Request.php', + base_path() . '/support/Response.php', + ] +]; diff --git a/config/bootstrap.php b/config/bootstrap.php new file mode 100644 index 0000000..95d2e87 --- /dev/null +++ b/config/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 [ + support\bootstrap\Session::class, +]; diff --git a/config/container.php b/config/container.php new file mode 100644 index 0000000..106b7b4 --- /dev/null +++ b/config/container.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return new Webman\Container; \ No newline at end of file diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..93308ad --- /dev/null +++ b/config/database.php @@ -0,0 +1,29 @@ + 'mysql', + 'connections' => [ + 'mysql' => [ + 'driver' => 'mysql', + 'host' => getenv('DB_HOST'), + 'port' => getenv('DB_PORT'), + 'database' => getenv('DB_NAME'), + 'username' => getenv('DB_USER'), + '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. + ], + 'pool' => [ + 'max_connections' => 5, + 'min_connections' => 1, + 'wait_timeout' => 3, + 'idle_timeout' => 60, + 'heartbeat_interval' => 50, + ], + ], + ], +]; \ No newline at end of file diff --git a/config/dependence.php b/config/dependence.php new file mode 100644 index 0000000..8e964ed --- /dev/null +++ b/config/dependence.php @@ -0,0 +1,15 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return []; \ No newline at end of file diff --git a/config/exception.php b/config/exception.php new file mode 100644 index 0000000..f2aede3 --- /dev/null +++ b/config/exception.php @@ -0,0 +1,17 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + '' => support\exception\Handler::class, +]; \ No newline at end of file diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..7f05de5 --- /dev/null +++ b/config/log.php @@ -0,0 +1,32 @@ + + * @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/webman.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/middleware.php b/config/middleware.php new file mode 100644 index 0000000..eadd331 --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,28 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + '' => [ + app\middleware\AccessControl::class + ], + 'apis' => [ + app\middleware\Lang::class + ], + 'applet' => [ + app\middleware\Lang::class + ], + 'console' => [ + app\middleware\Lang::class + ] +]; \ No newline at end of file diff --git a/config/plugin/shopwwi/filesystem/app.php b/config/plugin/shopwwi/filesystem/app.php new file mode 100644 index 0000000..a5a23a3 --- /dev/null +++ b/config/plugin/shopwwi/filesystem/app.php @@ -0,0 +1,116 @@ + true, + 'default' => 'local', + 'max_size' => 1024 * 1024 * 10, //单个文件大小10M + 'ext_yes' => [], //允许上传文件类型 为空则为允许所有 + 'ext_no' => [], // 不允许上传文件类型 为空则不限制 + 'storage' => [ + 'public' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => public_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'local' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\LocalAdapterFactory::class, + 'root' => runtime_path(), + 'url' => '//127.0.0.1:8787' // 静态文件访问域名 + ], + 'ftp' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\FtpAdapterFactory::class, + 'host' => 'ftp.example.com', + 'username' => 'username', + 'password' => 'password', + 'url' => '' // 静态文件访问域名 + // 'port' => 21, + // 'root' => '/path/to/root', + // 'passive' => true, + // 'ssl' => true, + // 'timeout' => 30, + // 'ignorePassiveAddress' => false, + // 'timestampsOnUnixListingsEnabled' => true, + ], + 'memory' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\MemoryAdapterFactory::class, + ], + 's3' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => false, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'minio' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\S3AdapterFactory::class, + 'credentials' => [ + 'key' => 'S3_KEY', + 'secret' => 'S3_SECRET', + ], + 'region' => 'S3_REGION', + 'version' => 'latest', + 'bucket_endpoint' => false, + 'use_path_style_endpoint' => true, + 'endpoint' => 'S3_ENDPOINT', + 'bucket_name' => 'S3_BUCKET', + 'url' => '' // 静态文件访问域名 + ], + 'oss' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\AliyunOssAdapterFactory::class, + 'accessId' => getenv('OSS_ACCESSID'), + 'accessSecret' => getenv('OSS_ACCESSSECRET'), + 'bucket' => getenv('OSS_BUCKET'), + 'endpoint' => getenv('OSS_ENDPOINT'), + 'url' => getenv('OSS_STATIC_URL') // 静态文件访问域名 + // 'timeout' => 3600, + // 'connectTimeout' => 10, + // 'isCName' => false, + // 'token' => null, + // 'proxy' => null, + ], + 'qiniu' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\QiniuAdapterFactory::class, + 'accessKey' => 'QINIU_ACCESS_KEY', + 'secretKey' => 'QINIU_SECRET_KEY', + 'bucket' => 'QINIU_BUCKET', + 'domain' => 'QINBIU_DOMAIN', + 'url' => '' // 静态文件访问域名 + ], + 'cos' => [ + 'driver' => \Shopwwi\WebmanFilesystem\Adapter\CosAdapterFactory::class, + 'region' => 'COS_REGION', + 'app_id' => 'COS_APPID', + 'secret_id' => 'COS_SECRET_ID', + 'secret_key' => 'COS_SECRET_KEY', + // 可选,如果 bucket 为私有访问请打开此项 + // 'signed_url' => false, + 'bucket' => 'COS_BUCKET', + 'read_from_cdn' => false, + 'url' => '' // 静态文件访问域名 + // 'timeout' => 60, + // 'connect_timeout' => 60, + // 'cdn' => '', + // 'scheme' => 'https', + ], + ], +]; \ 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..074e986 --- /dev/null +++ b/config/plugin/webman/console/app.php @@ -0,0 +1,24 @@ + true, + + 'build_dir' => BASE_PATH . DIRECTORY_SEPARATOR . 'build', + + 'phar_filename' => 'webman.phar', + + '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/redis-queue/app.php b/config/plugin/webman/redis-queue/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/redis-queue/app.php @@ -0,0 +1,4 @@ + 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..9fac733 --- /dev/null +++ b/config/plugin/webman/redis-queue/process.php @@ -0,0 +1,12 @@ + [ + 'handler' => Webman\RedisQueue\Process\Consumer::class, + 'count' => getenv('REDIS_QUEUE_PROCESS_COUNT'), // 可以设置多进程同时消费 + 'constructor' => [ + // 消费者类目录 + // 'consumer_dir' => app_path() . '/queue/redis' + 'consumer_dir' => app_path() . getenv('REDIS_QUEUE_CONSUMER_DIR') + ] + ] +]; \ 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..f7cc73f --- /dev/null +++ b/config/plugin/webman/redis-queue/redis.php @@ -0,0 +1,21 @@ + [ + 'host' => getenv('REDIS_QUEUE_HOST'), + 'options' => [ + 'auth' => getenv('REDIS_QUEUE_AUTH'), + 'db' => getenv('REDIS_QUEUE_DB'), + 'prefix' => getenv('REDIS_QUEUE_PREFIX'), + 'max_attempts' => getenv('REDIS_QUEUE_MAX_ATTEMPTS'), + 'retry_seconds' => getenv('REDIS_QUEUE_RETRY_SECONDS'), + ], + // 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/process.php b/config/process.php new file mode 100644 index 0000000..736593a --- /dev/null +++ b/config/process.php @@ -0,0 +1,62 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\Log; +use support\Request; +use app\process\Http; + +global $argv; + +return [ + 'webman' => [ + 'handler' => Http::class, + 'listen' => 'http://0.0.0.0:'.getenv('PROCESS_LISTEN'), + 'count' => getenv('PROCESS_COUNT'), + 'user' => '', + 'group' => '', + 'reusePort' => false, + 'eventLoop' => '', + 'context' => [], + 'constructor' => [ + 'requestClass' => Request::class, + 'logger' => Log::channel('default'), + 'appPath' => app_path(), + 'publicPath' => public_path() + ] + ], + // File update detection and automatic reload + 'monitor' => [ + 'handler' => app\process\Monitor::class, + 'reloadable' => false, + 'constructor' => [ + // Monitor these directories + 'monitorDir' => array_merge([ + app_path(), + config_path(), + base_path() . '/process', + base_path() . '/support', + base_path() . '/resource', + base_path() . '/.env', + ], glob(base_path() . '/plugin/*/app'), glob(base_path() . '/plugin/*/config'), glob(base_path() . '/plugin/*/api')), + // Files with these suffixes will be monitored + 'monitorExtensions' => [ + 'php', 'html', 'htm', 'env' + ], + 'options' => [ + 'enable_file_monitor' => !in_array('-d', $argv) && DIRECTORY_SEPARATOR === '/', + 'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/', + ] + ] + ] +]; diff --git a/config/redis.php b/config/redis.php new file mode 100644 index 0000000..0508ef6 --- /dev/null +++ b/config/redis.php @@ -0,0 +1,29 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'default' => [ + 'password' => getenv('REDIS_PASSWORD'), + 'host' => getenv('REDIS_HOST'), + 'port' => getenv('REDIS_PORT'), + 'database' => getenv('REDIS_DATABASE'), + '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 new file mode 100644 index 0000000..5648b19 --- /dev/null +++ b/config/route.php @@ -0,0 +1,184 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Route; + + +// ****** ****** ****** 生成文件({模块}/{名称}) ****** ****** ****** +Route::any('/cf/{module}/{name}', [\app\mzl\MzlController::class, 'createFile']); +// ****** ****** ****** 生成文件({模块}/{名称}) ****** ****** ****** + + +// ====== ====== ====== 网站 ====== ====== ====== +Route::group('/apis', function() { + // 车辆列表 + Route::any('/vehicle/list', [\app\apis\controller\VehicleDataController::class, 'index']); + // 车辆详情 + Route::any('/vehicle/detail', [\app\apis\controller\VehicleDataController::class, 'show']); + // 联系方式 + Route::any('/contact/add', [\app\apis\controller\ContactsController::class, 'store']); + // 搜索项 + Route::any('/search/options', [\app\apis\controller\DictTypeController::class, 'options']); + // 搜索项 - 型号 + Route::any('/get/models', [\app\apis\controller\DictTypeController::class, 'getModels']); + // 油箱订阅 + Route::any('/email/subscribe', [\app\apis\controller\SubscribeController::class, 'store']); + // 车辆类型 + Route::any('/vehicle/types', [\app\apis\controller\TypesController::class, 'all']); + // 车辆随机 + Route::any('/vehicle/random', [\app\apis\controller\VehicleDataController::class, 'random']); + // 隐私协议 + Route::any('/agt/privacy', [\app\apis\controller\AgreementController::class, 'one']); +}); +// ====== ====== ====== 网站 ====== ====== ====== + + +// ====== ====== ====== 小程序 ====== ====== ====== +Route::group('/applet', function() { + // 品牌 + Route::get('/brand', [\app\applet\controller\BrandController::class, 'all']); + // 类型 + Route::get('/types', [\app\applet\controller\TypesController::class, 'all']); + // 能源 + Route::get('/energy', [\app\applet\controller\EnergyController::class, 'all']); + // 型号 + Route::get('/models', [\app\applet\controller\ModelController::class, 'all']); + // 协议 + Route::get('/agreement/{agt}', [\app\applet\controller\AgreementController::class, 'getOne']); + // 登录 + Route::post('/miniapp/login', [\app\applet\controller\UsersController::class, 'miniappLogin']); + // 手机号 + Route::post('/miniapp/phone', [\app\applet\controller\UsersController::class, 'miniappPhoneNumber'])->middleware([ + \app\mzl\modules\jwt\middleware\JwtMiddleware::class + ]); + + // // 用户 - 信息 + // Route::post('/user/info', [\app\applet\controller\UsersController::class, 'one'])->middleware([ + // \app\mzl\modules\jwt\middleware\JwtMiddleware::class + // ]); + + // 授权 + Route::group('/', function() { + // 用户 - 信息 + Route::post('user/info', [\app\applet\controller\UsersController::class, 'one']); + // 用户 - 更新 + Route::post('user/update', [\app\applet\controller\UsersController::class, 'updateData']); + + // 车辆 - 添加 + Route::post('vehicle/add', [\app\applet\controller\VehicleDataController::class, 'add']); + // 车辆 - 更新 + Route::post('vehicle/update/{id}', [\app\applet\controller\VehicleDataController::class, 'edit']); + // 车辆 - 详情 + Route::get('vehicle/detail/{id}', [\app\applet\controller\VehicleDataController::class, 'show']); + // 车辆 - 列表 + Route::get('vehicle/list', [\app\applet\controller\VehicleDataController::class, 'index']); + // 车辆 - 删除 + Route::post('vehicle/delete/{id}', [\app\applet\controller\VehicleDataController::class, 'destroy']); + // // 车辆 - 申请 + // Route::post('vehicle/apply/status', [\app\applet\controller\VehicleApplyController::class, 'getOne']); + + // // 草稿 - 添加 + // Route::post('draft/add', [\app\applet\controller\DraftsController::class, 'addData']); + // // 草稿 - 更新 + // Route::post('draft/update/{draftId}', [\app\applet\controller\DraftsController::class, 'updateData']); + // // 草稿 - 详情 + // Route::get('draft/detail/{draftId}', [\app\applet\controller\DraftsController::class, 'getOne']); + // // 草稿 - 列表 + // Route::get('draft/list', [\app\applet\controller\DraftsController::class, 'pageLists']); + // // 草稿 - 删除 + // Route::post('draft/delete', [\app\applet\controller\DraftsController::class, 'deleteData']); + })->middleware([ + \app\mzl\modules\jwt\middleware\JwtMiddleware::class + ]); +}); +// ====== ====== ====== 小程序 ====== ====== ====== + + +// ====== ====== ====== 后台管理 ====== ====== ====== +Route::group('/console', function() { + // 协议管理 + Route::any('/agt/list', [\app\console\controller\AgreementController::class, 'index']); + Route::any('/agt/add', [\app\console\controller\AgreementController::class, 'add']); + Route::any('/agt/{id}/update', [\app\console\controller\AgreementController::class, 'edit']); + Route::any('/agt/{id}/delete', [\app\console\controller\AgreementController::class, 'destroy']); + Route::any('/agt/{id}/detail', [\app\console\controller\AgreementController::class, 'show']); + + // 品牌管理 + Route::any('/brand/list', [\app\console\controller\BrandController::class, 'index']); + Route::any('/brand/add', [\app\console\controller\BrandController::class, 'add']); + Route::any('/brand/{id}/update', [\app\console\controller\BrandController::class, 'edit']); + Route::any('/brand/{id}/delete', [\app\console\controller\BrandController::class, 'destroy']); + Route::any('/brand/{id}/detail', [\app\console\controller\BrandController::class, 'show']); + Route::any('/brand/preset', [\app\console\controller\BrandController::class, 'presets']); + + // 能源管理 + Route::any('/energy/list', [\app\console\controller\EnergyController::class, 'index']); + Route::any('/energy/add', [\app\console\controller\EnergyController::class, 'add']); + Route::any('/energy/{id}/update', [\app\console\controller\EnergyController::class, 'edit']); + Route::any('/energy/{id}/delete', [\app\console\controller\EnergyController::class, 'destroy']); + Route::any('/energy/{id}/detail', [\app\console\controller\EnergyController::class, 'show']); + Route::any('/energy/preset', [\app\console\controller\EnergyController::class, 'presets']); + + // 类型管理 + Route::any('/types/list', [\app\console\controller\TypesController::class, 'index']); + Route::any('/types/add', [\app\console\controller\TypesController::class, 'add']); + Route::any('/types/{id}/update', [\app\console\controller\TypesController::class, 'edit']); + Route::any('/types/{id}/delete', [\app\console\controller\TypesController::class, 'destroy']); + Route::any('/types/{id}/detail', [\app\console\controller\TypesController::class, 'show']); + Route::any('/types/preset', [\app\console\controller\TypesController::class, 'presets']); + + // 用户管理 + Route::any('/users/list', [\app\console\controller\UsersController::class, 'index']); + Route::any('/users/{id}/update', [\app\console\controller\UsersController::class, 'update']); + Route::any('/users/{id}/delete', [\app\console\controller\UsersController::class, 'destroy']); + Route::any('/users/{id}/detail', [\app\console\controller\UsersController::class, 'show']); + + // 车辆信息管理 + Route::any('/vehicle/data/list', [\app\console\controller\VehicleDataController::class, 'index']); + Route::any('/vehicle/add', [\app\console\controller\VehicleDataController::class, 'add']); + Route::any('/vehicle/{id}/update', [\app\console\controller\VehicleDataController::class, 'edit']); + Route::any('/vehicle/data/{id}/do', [\app\console\controller\VehicleDataController::class, 'doSaled']); + Route::any('/vehicle/data/{id}/delete', [\app\console\controller\VehicleDataController::class, 'destroy']); + Route::any('/vehicle/data/{id}/detail', [\app\console\controller\VehicleDataController::class, 'detail']); + + // 上传 + Route::any('/upload/image', [\app\mzl\modules\upload\controller\UploadController::class, 'uploadImage']); + + // 型号管理 + Route::any('/models/list', [\app\console\controller\ModelController::class, 'index']); + Route::any('/models/add', [\app\console\controller\ModelController::class, 'add']); + Route::any('/models/{id}/update', [\app\console\controller\ModelController::class, 'edit']); + Route::any('/models/{id}/delete', [\app\console\controller\ModelController::class, 'destroy']); + Route::any('/models/{id}/detail', [\app\console\controller\ModelController::class, 'show']); + Route::any('/models/preset', [\app\console\controller\ModelController::class, 'presets']); + + // 审核管理 + Route::any('/apply/list', [\app\console\controller\VehicleApplyController::class, 'index']); + Route::any('/apply/{id}/do', [\app\console\controller\VehicleApplyController::class, 'doApply']); + Route::any('/apply/{id}/delete', [\app\console\controller\VehicleApplyController::class, 'destroy']); + Route::any('/apply/{id}/detail', [\app\console\controller\VehicleApplyController::class, 'show']); + + // 首页统计 + Route::any('/counts', [\app\console\controller\HomeController::class, 'datas']); + + // 位置管理 + Route::any('/location/list', [\app\console\controller\LocationController::class, 'index']); + Route::any('/location/add', [\app\console\controller\LocationController::class, 'add']); + Route::any('/location/{id}/update', [\app\console\controller\LocationController::class, 'edit']); + Route::any('/location/{id}/delete', [\app\console\controller\LocationController::class, 'destroy']); + Route::any('/location/{id}/detail', [\app\console\controller\LocationController::class, 'show']); + Route::any('/location/preset', [\app\console\controller\LocationController::class, 'presets']); +}); +// ====== ====== ====== 后台管理 ====== ====== ====== + diff --git a/config/server.php b/config/server.php new file mode 100644 index 0000000..238f1aa --- /dev/null +++ b/config/server.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +return [ + 'event_loop' => '', + 'stop_timeout' => 2, + 'pid_file' => runtime_path() . '/webman.pid', + 'status_file' => runtime_path() . '/webman.status', + 'stdout_file' => runtime_path() . '/logs/stdout.log', + 'log_file' => runtime_path() . '/logs/workerman.log', + 'max_package_size' => 10 * 1024 * 1024 +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..043f8c4 --- /dev/null +++ b/config/session.php @@ -0,0 +1,65 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Webman\Session\FileSessionHandler; +use Webman\Session\RedisSessionHandler; +use Webman\Session\RedisClusterSessionHandler; + +return [ + + 'type' => 'file', // or redis or redis_cluster + + 'handler' => FileSessionHandler::class, + + 'config' => [ + 'file' => [ + 'save_path' => runtime_path() . '/sessions', + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'auth' => '', + 'timeout' => 2, + 'database' => '', + 'prefix' => 'redis_session_', + ], + 'redis_cluster' => [ + 'host' => ['127.0.0.1:7000', '127.0.0.1:7001', '127.0.0.1:7001'], + 'timeout' => 2, + 'auth' => '', + 'prefix' => 'redis_session_', + ] + ], + + 'session_name' => 'PHPSID', + + 'auto_update_timestamp' => false, + + 'lifetime' => 7*24*60*60, + + 'cookie_lifetime' => 365*24*60*60, + + 'cookie_path' => '/', + + 'domain' => '', + + 'http_only' => true, + + 'secure' => false, + + 'same_site' => '', + + 'gc_probability' => [1, 1000], + +]; diff --git a/config/static.php b/config/static.php new file mode 100644 index 0000000..2f76cf3 --- /dev/null +++ b/config/static.php @@ -0,0 +1,23 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Static file settings + */ +return [ + 'enable' => true, + 'middleware' => [ // Static file Middleware + //app\middleware\StaticFile::class, + ], +]; \ No newline at end of file diff --git a/config/translation.php b/config/translation.php new file mode 100644 index 0000000..e27f629 --- /dev/null +++ b/config/translation.php @@ -0,0 +1,25 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * Multilingual configuration + */ +return [ + // Default language + 'locale' => 'zh', + // Fallback language + 'fallback_locale' => ['zh', 'en'], + // Folder where language files are stored + 'path' => base_path() . '/resource/translations', +]; \ No newline at end of file diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..e3a7b85 --- /dev/null +++ b/config/view.php @@ -0,0 +1,22 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use support\view\Raw; +use support\view\Twig; +use support\view\Blade; +use support\view\ThinkPHP; + +return [ + 'handler' => Raw::class +]; diff --git a/iCGFiK.zip b/iCGFiK.zip new file mode 100644 index 0000000..ffbfac8 Binary files /dev/null and b/iCGFiK.zip differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b9f722e Binary files /dev/null and b/public/favicon.ico differ diff --git a/resource/translations/ar/messages.php b/resource/translations/ar/messages.php new file mode 100644 index 0000000..f0e0219 --- /dev/null +++ b/resource/translations/ar/messages.php @@ -0,0 +1,38 @@ + '45° front face', + 'front_left_45_degree' => 'Left front 45°', + 'front_left_headlight' => 'Left front headlight', + 'front_view' => 'Front', + 'lower_front' => 'Lower front part', + 'wheel_hub' => 'Vehicle wheels', + 'front_right_seat' => 'Right front seat', + 'right_lower_side' => 'Right bottom edge', + 'rear_right_taillight' => 'Right rear taillight', + 'rear_right_45_degree' => 'Right rear 45°', + 'lower_rear' => 'Parking space below', + 'trunk' => 'Trunk', + 'left_lower_side' => 'Left bottom edge', + 'rear_seats' => 'Rear seats', + 'center_console' => 'Center console', + 'control_panel' => 'Central control panel', + 'gear_shift' => 'Gear lever', + 'car_key' => 'key', + 'steering_wheel' => 'steering wheel', + 'dashboard' => 'Dashboard', + 'odometer_reading' => 'Mileage', + 'front_left_door_panel' => 'Left front door interior panel', + 'front_left_door_function_area' => 'Front left door functional area', + 'front_left_seat' => 'Left front seat', + 'interior_ceiling' => 'Interior ceiling', + + // 系统 + 'success' => 'طلب النجاح', + + // 搜索项 + 'in_stock' => 'بقعة', + 'in_transit' => 'في الطريق', + 'in_order' => 'أمر', +]; \ No newline at end of file diff --git a/resource/translations/en/messages.php b/resource/translations/en/messages.php new file mode 100644 index 0000000..67d3e07 --- /dev/null +++ b/resource/translations/en/messages.php @@ -0,0 +1,39 @@ + '45° front face', + 'front_left_45_degree' => 'Left front 45°', + 'front_left_headlight' => 'Left front headlight', + 'front_view' => 'Front', + 'lower_front' => 'Lower front part', + 'wheel_hub' => 'Vehicle wheels', + 'front_right_seat' => 'Right front seat', + 'right_lower_side' => 'Right bottom edge', + 'rear_right_taillight' => 'Right rear taillight', + 'rear_right_45_degree' => 'Right rear 45°', + 'lower_rear' => 'Parking space below', + 'trunk' => 'Trunk', + 'left_lower_side' => 'Left bottom edge', + 'rear_seats' => 'Rear seats', + 'center_console' => 'Center console', + 'control_panel' => 'Central control panel', + 'gear_shift' => 'Gear lever', + 'car_key' => 'key', + 'steering_wheel' => 'steering wheel', + 'dashboard' => 'Dashboard', + 'odometer_reading' => 'Mileage', + 'front_left_door_panel' => 'Left front door interior panel', + 'front_left_door_function_area' => 'Front left door functional area', + 'front_left_seat' => 'Left front seat', + 'interior_ceiling' => 'Interior ceiling', + + // 系统 + 'success' => 'Request successful', + + + // 搜索项 + 'in_stock' => 'In stock', + 'in_transit' => 'In transit', + 'in_order' => 'order', +]; \ No newline at end of file diff --git a/resource/translations/ru/messages.php b/resource/translations/ru/messages.php new file mode 100644 index 0000000..13ab30d --- /dev/null +++ b/resource/translations/ru/messages.php @@ -0,0 +1,39 @@ + '45° front face', + 'front_left_45_degree' => 'Left front 45°', + 'front_left_headlight' => 'Left front headlight', + 'front_view' => 'Front', + 'lower_front' => 'Lower front part', + 'wheel_hub' => 'Vehicle wheels', + 'front_right_seat' => 'Right front seat', + 'right_lower_side' => 'Right bottom edge', + 'rear_right_taillight' => 'Right rear taillight', + 'rear_right_45_degree' => 'Right rear 45°', + 'lower_rear' => 'Parking space below', + 'trunk' => 'Trunk', + 'left_lower_side' => 'Left bottom edge', + 'rear_seats' => 'Rear seats', + 'center_console' => 'Center console', + 'control_panel' => 'Central control panel', + 'gear_shift' => 'Gear lever', + 'car_key' => 'key', + 'steering_wheel' => 'steering wheel', + 'dashboard' => 'Dashboard', + 'odometer_reading' => 'Mileage', + 'front_left_door_panel' => 'Left front door interior panel', + 'front_left_door_function_area' => 'Front left door functional area', + 'front_left_seat' => 'Left front seat', + 'interior_ceiling' => 'Interior ceiling', + + // 系统 + 'success' => 'Запрос выполнен', + + + // 搜索项 + 'in_stock' => 'Наличные товары', + 'in_transit' => 'В пути', + 'in_order' => 'Заказать', +]; \ No newline at end of file diff --git a/resource/translations/zh/messages.php b/resource/translations/zh/messages.php new file mode 100644 index 0000000..3c191c9 --- /dev/null +++ b/resource/translations/zh/messages.php @@ -0,0 +1,40 @@ + '45°正脸', + 'front_left_45_degree' => '左前45°', + 'front_left_headlight' => '左前大灯', + 'front_view' => '正前', + 'lower_front' => '车头下部', + 'wheel_hub' => '车辆轮毂', + 'front_right_seat' => '右侧前排座椅', + 'right_lower_side' => '右侧底大边', + 'rear_right_taillight' => '右后尾灯', + 'rear_right_45_degree' => '右后45°', + 'lower_rear' => '车位下部', + 'trunk' => '后备厢', + 'left_lower_side' => '左侧底大边', + 'rear_seats' => '后排座椅', + 'center_console' => '中控台', + 'control_panel' => '中控面板', + 'gear_shift' => '档把', + 'car_key' => '钥匙', + 'steering_wheel' => '方向盘', + 'dashboard' => '仪表盘', + 'odometer_reading' => '表显里程', + 'front_left_door_panel' => '左前门内饰板', + 'front_left_door_function_area' => '左前门功能区', + 'front_left_seat' => '左侧前排座椅', + 'interior_ceiling' => '车内顶棚', + + + // 系统 + 'success' => '请求成功', + + + // 搜索项 + 'in_stock' => '现货', + 'in_transit' => '在途', + 'in_order' => '订购', +]; \ No newline at end of file diff --git a/start.php b/start.php new file mode 100755 index 0000000..41ad7ef --- /dev/null +++ b/start.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Request + * @package support + */ +class Request extends \Webman\Http\Request +{ + +} \ No newline at end of file diff --git a/support/Response.php b/support/Response.php new file mode 100644 index 0000000..9bc4e1e --- /dev/null +++ b/support/Response.php @@ -0,0 +1,24 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +namespace support; + +/** + * Class Response + * @package support + */ +class Response extends \Webman\Http\Response +{ + +} \ No newline at end of file diff --git a/support/bootstrap.php b/support/bootstrap.php new file mode 100644 index 0000000..d913def --- /dev/null +++ b/support/bootstrap.php @@ -0,0 +1,139 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use Dotenv\Dotenv; +use support\Log; +use Webman\Bootstrap; +use Webman\Config; +use Webman\Middleware; +use Webman\Route; +use Webman\Util; +use Workerman\Events\Select; +use Workerman\Worker; + +$worker = $worker ?? null; + +if (empty(Worker::$eventLoopClass)) { + Worker::$eventLoopClass = Select::class; +} + +set_error_handler(function ($level, $message, $file = '', $line = 0) { + if (error_reporting() & $level) { + throw new ErrorException($message, 0, $level, $file, $line); + } +}); + +if ($worker) { + register_shutdown_function(function ($startTime) { + if (time() - $startTime <= 0.1) { + sleep(1); + } + }, time()); +} + +if (class_exists('Dotenv\Dotenv') && file_exists(base_path(false) . '/.env')) { + if (method_exists('Dotenv\Dotenv', 'createUnsafeMutable')) { + Dotenv::createUnsafeMutable(base_path(false))->load(); + } else { + Dotenv::createMutable(base_path(false))->load(); + } +} + +Config::clear(); +support\App::loadAllConfig(['route']); +if ($timezone = config('app.default_timezone')) { + date_default_timezone_set($timezone); +} + +foreach (config('autoload.files', []) as $file) { + include_once $file; +} +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['autoload']['files'] ?? [] as $file) { + include_once $file; + } + } + foreach ($projects['autoload']['files'] ?? [] as $file) { + include_once $file; + } +} + +Middleware::load(config('middleware', [])); +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project) || $name === 'static') { + continue; + } + Middleware::load($project['middleware'] ?? []); + } + Middleware::load($projects['middleware'] ?? [], $firm); + if ($staticMiddlewares = config("plugin.$firm.static.middleware")) { + Middleware::load(['__static__' => $staticMiddlewares], $firm); + } +} +Middleware::load(['__static__' => config('static.middleware', [])]); + +foreach (config('bootstrap', []) as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['bootstrap'] ?? [] as $className) { + if (!class_exists($className)) { + $log = "Warning: Class $className setting in config/plugin/$firm/$name/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } + } + foreach ($projects['bootstrap'] ?? [] as $className) { + /** @var string $className */ + if (!class_exists($className)) { + $log = "Warning: Class $className setting in plugin/$firm/config/bootstrap.php not found\r\n"; + echo $log; + Log::error($log); + continue; + } + /** @var Bootstrap $className */ + $className::start($worker); + } +} + +$directory = base_path() . '/plugin'; +$paths = [config_path()]; +foreach (Util::scanDir($directory) as $path) { + if (is_dir($path = "$path/config")) { + $paths[] = $path; + } +} +Route::load($paths); + diff --git a/webman b/webman new file mode 100755 index 0000000..3f55062 --- /dev/null +++ b/webman @@ -0,0 +1,71 @@ +#!/usr/bin/env php +load(); + } else { + Dotenv::createMutable(run_path())->load(); + } +} + +$appConfig = require $appConfigFile; +if ($timezone = $appConfig['default_timezone'] ?? '') { + date_default_timezone_set($timezone); +} + +if ($errorReporting = $appConfig['error_reporting'] ?? '') { + error_reporting($errorReporting); +} + +if (!in_array($argv[1] ?? '', ['start', 'restart', 'stop', 'status', 'reload', 'connections'])) { + require_once __DIR__ . '/support/bootstrap.php'; +} else { + if (class_exists('Support\App')) { + Support\App::loadAllConfig(['route']); + } else { + Config::reload(config_path(), ['route', 'container']); + } +} + +$cli = new Command(); +$cli->setName('webman cli'); +$cli->installInternalCommands(); +if (is_dir($command_path = Util::guessPath(app_path(), '/command', true))) { + $cli->installCommands($command_path); +} + +foreach (config('plugin', []) as $firm => $projects) { + if (isset($projects['app'])) { + foreach (['', '/app'] as $app) { + if ($command_str = Util::guessPath(base_path() . "/plugin/$firm{$app}", 'command')) { + $command_path = base_path() . "/plugin/$firm{$app}/$command_str"; + $cli->installCommands($command_path, "plugin\\$firm" . str_replace('/', '\\', $app) . "\\$command_str"); + } + } + } + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + $project['command'] ??= []; + array_walk($project['command'], [$cli, 'createCommandInstance']); + } +} + +$cli->run(); diff --git a/windows.bat b/windows.bat new file mode 100644 index 0000000..f07ce53 --- /dev/null +++ b/windows.bat @@ -0,0 +1,3 @@ +CHCP 65001 +php windows.php +pause \ No newline at end of file diff --git a/windows.php b/windows.php new file mode 100644 index 0000000..f37a72c --- /dev/null +++ b/windows.php @@ -0,0 +1,136 @@ +load(); + } else { + Dotenv::createMutable(base_path())->load(); + } +} + +App::loadAllConfig(['route']); + +$errorReporting = config('app.error_reporting'); +if (isset($errorReporting)) { + error_reporting($errorReporting); +} + +$runtimeProcessPath = runtime_path() . DIRECTORY_SEPARATOR . '/windows'; +$paths = [ + $runtimeProcessPath, + runtime_path('logs'), + runtime_path('views') +]; +foreach ($paths as $path) { + if (!is_dir($path)) { + mkdir($path, 0777, true); + } +} + +$processFiles = []; +if (config('server.listen')) { + $processFiles[] = __DIR__ . DIRECTORY_SEPARATOR . 'start.php'; +} +foreach (config('process', []) as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, ''); +} + +foreach (config('plugin', []) as $firm => $projects) { + foreach ($projects as $name => $project) { + if (!is_array($project)) { + continue; + } + foreach ($project['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, "$firm.$name"); + } + } + foreach ($projects['process'] ?? [] as $processName => $config) { + $processFiles[] = write_process_file($runtimeProcessPath, $processName, $firm); + } +} + +function write_process_file($runtimeProcessPath, $processName, $firm): string +{ + $processParam = $firm ? "plugin.$firm.$processName" : $processName; + $configParam = $firm ? "config('plugin.$firm.process')['$processName']" : "config('process')['$processName']"; + $fileContent = << true]); + if (!$resource) { + exit("Can not execute $cmd\r\n"); + } + return $resource; +} + +$resource = popen_processes($processFiles); +echo "\r\n"; +while (1) { + sleep(1); + if (!empty($monitor) && $monitor->checkAllFilesChange()) { + $status = proc_get_status($resource); + $pid = $status['pid']; + shell_exec("taskkill /F /T /PID $pid"); + proc_close($resource); + $resource = popen_processes($processFiles); + } +} diff --git a/zNC5ji.zip b/zNC5ji.zip new file mode 100644 index 0000000..a288f30 Binary files /dev/null and b/zNC5ji.zip differ