├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── app ├── Exceptions │ ├── ExceptionsHandler.php │ ├── HttpResponseException.php │ └── RepositoryException.php ├── Http │ ├── Controllers │ │ ├── Admin │ │ │ ├── IndexController.php │ │ │ ├── Sys │ │ │ │ ├── SysDictController.php │ │ │ │ ├── SysDictItemController.php │ │ │ │ ├── SysFileController.php │ │ │ │ ├── SysFileGroupController.php │ │ │ │ ├── SysSettingController.php │ │ │ │ ├── SysSettingGroupController.php │ │ │ │ ├── SysUserController.php │ │ │ │ ├── SysUserDeptController.php │ │ │ │ ├── SysUserListController.php │ │ │ │ ├── SysUserRoleController.php │ │ │ │ ├── SysUserRuleController.php │ │ │ │ └── SysWatcherController.php │ │ │ └── UserController.php │ │ ├── App │ │ │ ├── IndexController.php │ │ │ └── UserController.php │ │ └── BaseController.php │ ├── Middleware │ │ ├── AllowCrossDomainMiddleware.php │ │ ├── AuthGuardMiddleware.php │ │ ├── LoginLogMiddleware.php │ │ └── SysAuthGuardMiddleware.php │ └── Requests │ │ └── App │ │ ├── UserRegisterRequest.php │ │ └── UserUpdateInfoRequest.php ├── Models │ ├── Sanctum │ │ └── PersonalAccessToken.php │ ├── Sys │ │ ├── SysDeptModel.php │ │ ├── SysDictItemModel.php │ │ ├── SysDictModel.php │ │ ├── SysFileGroupModel.php │ │ ├── SysFileModel.php │ │ ├── SysLoginRecordModel.php │ │ ├── SysRoleModel.php │ │ ├── SysRuleModel.php │ │ ├── SysSettingGroupModel.php │ │ ├── SysSettingModel.php │ │ └── SysUserModel.php │ └── UserModel.php ├── Providers │ ├── AnnoRoute │ │ ├── AnnoRoute.php │ │ ├── Attribute │ │ │ ├── Create.php │ │ │ ├── Delete.php │ │ │ ├── DeleteMapping.php │ │ │ ├── Find.php │ │ │ ├── GetMapping.php │ │ │ ├── Mapping.php │ │ │ ├── PostMapping.php │ │ │ ├── PutMapping.php │ │ │ ├── Query.php │ │ │ ├── RequestMapping.php │ │ │ └── Update.php │ │ └── RouteServiceProvider.php │ ├── AppServiceProvider.php │ ├── AutoBindServiceProvider.php │ └── Telescope │ │ ├── Contracts │ │ └── EntriesRepository.php │ │ ├── EntryType.php │ │ ├── IncomingEntry.php │ │ ├── ListensForStorageOpportunities.php │ │ ├── RegistersWatchers.php │ │ ├── Storage │ │ ├── EntryQueryOptions.php │ │ └── StorageRepository.php │ │ ├── Telescope.php │ │ ├── TelescopeServiceProvider.php │ │ └── Watchers │ │ ├── CacheWatcher.php │ │ ├── FetchesStackTrace.php │ │ ├── QueryWatcher.php │ │ ├── RedisWatcher.php │ │ ├── RequestWatcher.php │ │ └── Watcher.php ├── Repositories │ ├── Repository.php │ ├── Sys │ │ ├── SysDeptRepository.php │ │ ├── SysDictItemRepository.php │ │ ├── SysDictRepository.php │ │ ├── SysFileGroupRepository.php │ │ ├── SysFileRepository.php │ │ ├── SysLoginRecordRepository.php │ │ ├── SysRoleRepository.php │ │ ├── SysRuleRepository.php │ │ ├── SysSettingGroupRepository.php │ │ ├── SysSettingRepository.php │ │ └── SysUserRepository.php │ └── UserRepository.php ├── Services │ ├── LengthAwarePaginatorService.php │ ├── Service.php │ ├── SysFileService.php │ ├── SysSettingService.php │ ├── SysUserDeptService.php │ ├── SysUserRuleService.php │ ├── SysUserService.php │ └── UserService.php └── Support │ ├── Enum │ ├── FileType.php │ └── ShowType.php │ ├── Mail │ └── VerificationCodeMail.php │ ├── Trait │ ├── BuildSearch.php │ └── RequestJson.php │ └── helpers.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── config ├── app.php ├── auth.php ├── cache.php ├── database.php ├── filesystems.php ├── logging.php ├── mail.php ├── models.php ├── octane.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php ├── swagger.php └── telescope.php ├── database ├── database.sqlite ├── migrations │ ├── 2025_01_01_000001_create_sys_user_table.php │ ├── 2025_01_01_000003_create_dict_table.php │ ├── 2025_01_01_000004_create_file_table.php │ ├── 2025_01_01_000005_create_setting_table.php │ ├── 2025_01_01_000008_create_user_table.php │ ├── 2025_01_01_000009_create_jobs_table.php │ └── 2025_01_01_000020_create_other_table.php └── seeders │ ├── DatabaseSeeder.php │ ├── SysDataSeeder.php │ └── SysUserSeeder.php ├── lang ├── en │ ├── auth.php │ ├── pagination.php │ ├── passwords.php │ ├── system.php │ ├── user.php │ └── validation.php └── zh │ ├── auth.php │ ├── pagination.php │ ├── passwords.php │ ├── system.php │ ├── user.php │ └── validation.php ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt └── swagger.php ├── resources └── views │ ├── controller.blade.php │ ├── emails │ └── verification_code.blade.php │ ├── model.blade.php │ ├── swagger.blade.php │ └── view.blade.php ├── routes ├── console.php └── web.php ├── storage ├── app │ ├── .gitignore │ ├── private │ │ └── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore ├── logs │ └── .gitignore └── telescope │ └── .gitignore └── tests ├── Feature └── ExampleTest.php ├── TestAutoBind.php ├── TestCase.php └── Unit └── ExampleTest.php /.env.example: -------------------------------------------------------------------------------- 1 | # 网站基本配置 2 | APP_NAME=XinAdmin 3 | APP_ENV=local 4 | APP_KEY= 5 | APP_DEBUG=true 6 | APP_TIMEZONE=UTC 7 | APP_URL=http://localhost 8 | 9 | # 密码加密算法因子 10 | BCRYPT_ROUNDS=12 11 | 12 | # 日志配置 13 | LOG_CHANNEL=stack 14 | LOG_STACK=single 15 | LOG_DEPRECATIONS_CHANNEL=null 16 | LOG_LEVEL=debug 17 | 18 | # 数据库配置 19 | DB_CONNECTION=mysql 20 | DB_HOST=127.0.0.1 21 | DB_PORT=3306 22 | DB_DATABASE=laravel 23 | DB_USERNAME=root 24 | DB_PASSWORD=root 25 | 26 | # redis配置 27 | REDIS_CLIENT=phpredis 28 | REDIS_HOST=127.0.0.1 29 | REDIS_PASSWORD=null 30 | REDIS_PORT=6379 31 | 32 | # 队列配置 33 | BROADCAST_CONNECTION=log 34 | FILESYSTEM_DISK=local 35 | QUEUE_CONNECTION=database 36 | 37 | MEMCACHED_HOST=127.0.0.1 38 | 39 | # 系统设置 40 | SETTING_CACHE_KEY=settings 41 | 42 | # OPENAI配置 43 | OPENAI_API_KEY= 44 | OPENAI_ORGANIZATION= 45 | OPENAI_BASE_URI= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | /.env 9 | /.env.backup 10 | /.env.production 11 | /.phpactor.json 12 | /.phpunit.result.cache 13 | /.phpstorm.meta.php 14 | /_ide_helper.php 15 | Homestead.json 16 | Homestead.yaml 17 | auth.json 18 | npm-debug.log 19 | yarn-error.log 20 | /.fleet 21 | /.idea 22 | /.vscode 23 | /.zed 24 | /composer.lock 25 | 26 | /web/node_modules 27 | /web/.env.local 28 | /web/.umirc.local.ts 29 | /web/config/config.local.ts 30 | /web/src/.umi 31 | /web/src/.umi-production 32 | /web/src/.umi-test 33 | /web/dist 34 | /web/.mfsu 35 | /web/.swc 36 | /web/.idea 37 | /web/pnpm-lock.yaml 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 XinAdmin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | xinadmin 3 |

4 |

Xin Admin

5 |

企业级 PHP 全栈快速开发框架

6 |

7 | 8 | xinadmin 9 | 10 | 11 | php 12 | 13 | 14 | laravel 15 | 16 | 17 | React 18 | 19 | 20 | UmiJs 21 | 22 | 23 | UmiJs 24 | 25 | 26 | license 27 | 28 |

29 | 30 | 31 | 基于 PHP8.2 + Laravel12 + Mysql + React + TypeScript + UmiJs + Ant Design 等前沿技术栈开发的全栈开发框架,提供按钮级权限验证、动态菜单、用户分组权限、数据字典、系统配置、文件管理系统、AI模块等便捷开发, 32 | 遵循 Apache License 无需授权即可免费商用. 33 | 34 | 35 | 36 | ## 主要特征 37 | 38 | ### ✨ 前后端分离 39 | 采用Api接口规范,前后端分离开发模式,后端不必考虑视图UI功能,前端web目录不包含任何后端代码。 40 | 41 | ### 🎨 React 技术栈 42 | 阿里 Umi Js 以及 AntdPro 组件库,不仅简单易用,并且可以是你的技术更上一层楼,带你体验技术的革新,站在巨人肩膀上享受开发的便捷和乐趣。 43 | 44 | ### 📟 权限控制系统 45 | 我们提供了完善的权限验证系统,支持客户端、管理端,双动态菜单,页面按钮级权限控制,使用PHP8注解验证精确控制接口请求,支持分组权限禁用继承。 46 | 47 | ### ♻️ 数据字典和全局设置 48 | 强大的数据字典,支持CRUD生成,value、label 映射,支持标签、文字、徽标三种表格展示类型,多种显示状态,还有方便的系统配置。 49 | 50 | ### 🎁 文件管理系统 51 | XinAdmin 拥有强大的文件系统,可拓展 AliOss 存储 支持多选、文件分组等,支持图片、视频、音频、压缩文件和其它文件上传 52 | 53 | ## 内置功能 54 | 55 | - 仪表盘:提供基于 antv 开箱即用的仪表盘方案,以及演示页面 56 | - 示例组件:包含图标、表格、列表、表单等组件的示例 57 | - 前台会员:前台会员的权限管理、分组和列表以及余额记录等 58 | - 管理员:管理员是后台系统的访问者,提供管理员分组、权限、列表以及管理员信息设置 59 | - 系统设置:系统设置是对服务器可变参数快速设置的表单,可以自定义分组以及表单类型 60 | - 文件管理:文件上传解决方案,可拓展 AliOss 存储,后台文件管理文件夹,支持多选、文件分组等,支持图片、视频、音频、压缩文件和其它文件上传 61 | - 字典管理:对系统中经常使用的一些较为固定的数据进行维护 62 | 63 | ## 其他 64 | - 接口文档:[https://api.xinadmin.cn/](https://api.xinadmin.cn/) 65 | - 演示站:[https://laravel.xinadmin.cn/](https://laravel.xinadmin.cn/) -------------------------------------------------------------------------------- /app/Exceptions/ExceptionsHandler.php: -------------------------------------------------------------------------------- 1 | function (HttpResponseException $e) { 35 | return response()->json($e->toArray(), $e->getCode()); 36 | }, 37 | MissingAbilityException::class => function ($e) { 38 | return $this->notification( 39 | 'No Permission', 40 | __('system.error.no_permission'), 41 | ShowType::WARN_NOTIFICATION 42 | ); 43 | }, 44 | AuthenticationException::class => function ($e) { 45 | return response()->json([ 46 | 'msg' => __('user.not_login'), 47 | 'success' => false 48 | ], 401); 49 | }, 50 | NotFoundHttpException::class => function ($e) { 51 | return $this->notification( 52 | 'Route Not Exist', 53 | __('system.error.route_not_exist'), 54 | ShowType::WARN_NOTIFICATION 55 | ); 56 | }, 57 | ValidationException::class => function (ValidationException $e) { 58 | return response()->json([ 59 | 'msg' => $e->validator->errors()->first(), 60 | 'showType' => ShowType::WARN_MESSAGE->value, 61 | 'success' => false, 62 | ]); 63 | }, 64 | ]; 65 | 66 | foreach ($exceptionHandlers as $exceptionType => $handler) { 67 | if ($e instanceof $exceptionType) { 68 | $response = $handler($e); 69 | break; 70 | } 71 | } 72 | 73 | if (!isset($response)) { 74 | $debug = config('app.debug'); 75 | $data = [ 76 | 'msg' => $e->getMessage(), 77 | 'showType' => ShowType::ERROR_MESSAGE->value, 78 | 'success' => false, 79 | ]; 80 | 81 | if ($debug) { 82 | $data += [ 83 | 'file' => $e->getFile(), 84 | 'line' => $e->getLine(), 85 | 'trace' => $e->getTrace(), 86 | 'code' => $e->getCode(), 87 | ]; 88 | } 89 | 90 | $response = response()->json($data); 91 | } 92 | 93 | $response->headers->set('Access-Control-Allow-Origin', '*'); 94 | $response->headers->set('Access-Control-Allow-Credentials', 'true'); 95 | $response->headers->set('Access-Control-Max-Age', 1800); 96 | $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 97 | $response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With'); 98 | 99 | return $response; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/Exceptions/HttpResponseException.php: -------------------------------------------------------------------------------- 1 | msg = $data['msg'] ?? ''; 33 | $this->success = $data['success'] ?? true; 34 | if (empty($data['showType']) && $this->success) { 35 | $this->showType = ShowType::SUCCESS_MESSAGE; 36 | } elseif (empty($data['showType']) && ! $this->success) { 37 | $this->showType = ShowType::ERROR_MESSAGE; 38 | } else { 39 | $this->showType = $data['showType']; 40 | } 41 | $this->data = $data['data'] ?? []; 42 | parent::__construct($data['msg'] ?? '', $code); 43 | } 44 | 45 | public function toArray(): array 46 | { 47 | return [ 48 | 'data' => $this->data, 49 | 'success' => $this->success, 50 | 'msg' => $this->msg, 51 | 'showType' => $this->showType->value, 52 | ]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Exceptions/RepositoryException.php: -------------------------------------------------------------------------------- 1 | success(compact('webSetting'), '恭喜你已经成功安装 Xin Admin'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysDictController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 29 | $this->model = $model; 30 | } 31 | 32 | /** 删除字典 */ 33 | #[DeleteMapping('/{id}', 'delete')] 34 | public function delete($id): JsonResponse 35 | { 36 | $model = $this->model->findOrFail($id); 37 | $count = $model->dictItems()->count(); 38 | if ($count > 0) { 39 | return $this->error('字典包含子项!'); 40 | } 41 | $model->delete(); 42 | return $this->success('ok'); 43 | } 44 | 45 | /** 获取字典 */ 46 | #[GetMapping('/list')] 47 | public function itemList(): JsonResponse 48 | { 49 | if (Cache::has('sys_dict')) { 50 | $dict = Cache::get('sys_dict'); 51 | } else { 52 | $dict = $this->model->getDictItems(); 53 | // 缓存字典 54 | Cache::store('redis')->put('bar', $dict, 600); 55 | } 56 | return $this->success($dict); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysDictItemController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysFileController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 24 | } 25 | 26 | protected array $noPermission = ['download']; 27 | 28 | /** 下载文件 */ 29 | #[GetMapping('/download/{id}')] 30 | public function download(int $id): StreamedResponse 31 | { 32 | $fileService = new SysFileService; 33 | return $fileService->download($id); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysFileGroupController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysSettingController.php: -------------------------------------------------------------------------------- 1 | model = $model; 27 | $this->repository = $repository; 28 | } 29 | 30 | /** 保存设置 */ 31 | #[PutMapping('/save/{id}', 'save')] 32 | public function save(int $id): JsonResponse 33 | { 34 | $value = request()->all(); 35 | foreach ($value as $k => $v) { 36 | $model = $this->model->where('key', $k)->where('group_id', $id)->first(); 37 | if ($model) { 38 | $model->values = $v; 39 | $model->save(); 40 | } 41 | } 42 | return $this->success('保存成功'); 43 | } 44 | 45 | /** 刷新设置 */ 46 | #[PostMapping('/refreshCache', 'refresh')] 47 | public function refreshCache(): JsonResponse 48 | { 49 | SysSettingService::refreshSettings(); 50 | return $this->success('重载成功'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysSettingGroupController.php: -------------------------------------------------------------------------------- 1 | model = $model; 25 | $this->repository = $repository; 26 | } 27 | 28 | /** 删除设置 */ 29 | #[DeleteMapping('/{id}', 'delete')] 30 | public function delete($id): JsonResponse 31 | { 32 | $this->repository->delete($id); 33 | return $this->success('ok'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysUserController.php: -------------------------------------------------------------------------------- 1 | service = $service; 29 | $this->repository = $repository; 30 | } 31 | 32 | /** 用户登录 */ 33 | #[PostMapping('/login', middleware: 'login_log')] 34 | public function login(Request $request): JsonResponse 35 | { 36 | return $this->service->login($request); 37 | } 38 | 39 | /** 退出登录 */ 40 | #[PostMapping('/logout')] 41 | public function logout(Request $request): JsonResponse 42 | { 43 | Auth::guard('sys_users')->logout(); 44 | $request->user()->currentAccessToken()->delete(); 45 | return $this->success(__('user.logout_success')); 46 | } 47 | 48 | /** 获取管理员信息 */ 49 | #[GetMapping('/info')] 50 | public function info(): JsonResponse 51 | { 52 | return $this->service->getAdminInfo(); 53 | } 54 | 55 | /** 更新管理员信息 */ 56 | #[PutMapping] 57 | public function updateInfo(Request $request): JsonResponse 58 | { 59 | return $this->service->updateInfo(Auth::id(), $request); 60 | } 61 | 62 | /** 修改密码 */ 63 | #[PutMapping('/updatePassword')] 64 | public function updatePassword(Request $request): JsonResponse 65 | { 66 | return $this->service->updatePassword($request); 67 | } 68 | 69 | /** 上传头像 */ 70 | #[PostMapping('/avatar')] 71 | public function uploadAvatar(): JsonResponse 72 | { 73 | $service = new SysFileService(); 74 | $data = $service->upload(FileType::IMAGE, 1, 'public'); 75 | return $this->success($data); 76 | } 77 | 78 | /** 获取管理员登录日志 */ 79 | #[GetMapping('/login/record')] 80 | public function get(SysLoginRecordRepository $repository): JsonResponse 81 | { 82 | $id = auth()->id(); 83 | $data = $repository->getRecordByID($id); 84 | return $this->success($data); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysUserDeptController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 25 | $this->service = $service; 26 | } 27 | 28 | /** 部门列表 */ 29 | #[GetMapping(authorize: 'query')] 30 | public function listDept(): JsonResponse 31 | { 32 | return $this->service->list(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysUserListController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 28 | $this->service = $service; 29 | } 30 | 31 | /** 重置管理员密码 */ 32 | #[PutMapping('/reset/password', 'resetPassword')] 33 | public function resetPassword(Request $request): JsonResponse 34 | { 35 | return $this->service->resetPassword($request); 36 | } 37 | 38 | /** 修改管理员状态 */ 39 | #[PostMapping('/status/{id}', 'resetStatus')] 40 | public function resetStatus($id): JsonResponse 41 | { 42 | return $this->service->resetStatus($id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysUserRoleController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 27 | $this->model = $model; 28 | } 29 | 30 | /** 删除角色 */ 31 | #[DeleteMapping('/{id}', authorize: 'delete')] 32 | public function delete(int $id): JsonResponse 33 | { 34 | $model = $this->model->findOrFail($id); 35 | $count = $model->users()->count(); 36 | if ($count > 0) { 37 | return $this->error('该角色下存在用户,无法删除'); 38 | } 39 | $this->model->delete(); 40 | return $this->success(); 41 | } 42 | 43 | /** 设置角色权限 */ 44 | #[PostMapping('/rule', 'update')] 45 | public function setRoleRule(Request $request): JsonResponse 46 | { 47 | $this->repository->setRule($request); 48 | return $this->success(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysUserRuleController.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 27 | $this->service = $service; 28 | } 29 | 30 | /** 获取权限列表 */ 31 | #[GetMapping] 32 | public function query(Request $request): JsonResponse 33 | { 34 | return $this->service->getList(); 35 | } 36 | 37 | /** 获取父级权限 */ 38 | #[GetMapping('/parent', authorize: 'query')] 39 | public function getRulesParent(): JsonResponse 40 | { 41 | return $this->service->getRuleParent(); 42 | } 43 | 44 | /** 设置显示 */ 45 | #[PutMapping('/show/{id}', authorize: 'update')] 46 | public function show($id): JsonResponse 47 | { 48 | return $this->service->setShow($id); 49 | } 50 | 51 | /** 设置状态 */ 52 | #[PutMapping('/status/{id}', authorize: 'update')] 53 | public function status($id): JsonResponse 54 | { 55 | return $this->service->setStatus($id); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Sys/SysWatcherController.php: -------------------------------------------------------------------------------- 1 | type('request'); 21 | return $this->success($storage->get($options)); 22 | } 23 | 24 | #[GetMapping('/auth', 'auth')] 25 | public function auth(Request $request, EntriesRepository $storage): JsonResponse 26 | { 27 | $options = EntryQueryOptions::fromRequest($request); 28 | $options->type('auth'); 29 | return $this->success($storage->get($options)); 30 | } 31 | 32 | #[GetMapping('/query', 'query')] 33 | public function queryLog(Request $request, EntriesRepository $storage): JsonResponse 34 | { 35 | $options = EntryQueryOptions::fromRequest($request); 36 | $options->type('query'); 37 | return $this->success($storage->get($options)); 38 | } 39 | 40 | #[GetMapping('/cache', 'cache')] 41 | public function cache(Request $request, EntriesRepository $storage): JsonResponse 42 | { 43 | $options = EntryQueryOptions::fromRequest($request); 44 | $options->type('cache'); 45 | return $this->success($storage->get($options)); 46 | } 47 | 48 | #[GetMapping('/redis', 'redis')] 49 | public function redis(Request $request, EntriesRepository $storage): JsonResponse 50 | { 51 | $options = EntryQueryOptions::fromRequest($request); 52 | $options->type('redis'); 53 | return $this->success($storage->get($options)); 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/UserController.php: -------------------------------------------------------------------------------- 1 | service = $service; 25 | $this->repository = $repository; 26 | } 27 | 28 | /** 重置密码 */ 29 | #[PutMapping('/resetPassword', 'resetPassword')] 30 | public function resetPassword(Request $request): JsonResponse 31 | { 32 | return $this->service->resetPassword($request); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Controllers/App/IndexController.php: -------------------------------------------------------------------------------- 1 | success(compact('web_setting')); 28 | } 29 | 30 | /** 用户登录 */ 31 | #[PostMapping('/login')] 32 | public function login(Request $request): JsonResponse 33 | { 34 | $credentials = $request->validate([ 35 | 'username' => 'required|min:4|alphaDash', 36 | 'password' => 'required|min:4|alphaDash', 37 | ]); 38 | if (Auth::guard('users')->attempt($credentials, true)) { 39 | $data = $request->user('users') 40 | ->createToken($credentials['username']) 41 | ->toArray(); 42 | return $this->success($data, __('user.login_success')); 43 | } 44 | return $this->error(__('user.login_error')); 45 | } 46 | 47 | /** 用户注册 */ 48 | #[PostMapping('/register')] 49 | public function register(UserRegisterRequest $request): JsonResponse 50 | { 51 | $data = $request->validated(); 52 | $model = new UserModel; 53 | $model->username = $data['username']; 54 | $model->password = password_hash($data['password'], PASSWORD_DEFAULT); 55 | $model->email = $data['email']; 56 | if ($model->save()) { 57 | return $this->success(); 58 | } 59 | 60 | return $this->error('创建用户失败'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Controllers/App/UserController.php: -------------------------------------------------------------------------------- 1 | user(); 24 | return $this->success(compact('info')); 25 | } 26 | 27 | #[PostMapping('/logout')] 28 | public function logout(): JsonResponse 29 | { 30 | $user_id = auth('users')->id(); 31 | $model = new UserModel; 32 | if ($model->logout($user_id)) { 33 | return $this->success('退出登录成功'); 34 | } else { 35 | return $this->error($model->getErrorMsg()); 36 | } 37 | } 38 | 39 | #[PutMapping] 40 | public function setUserInfo(UserUpdateInfoRequest $request): JsonResponse 41 | { 42 | UserModel::where('user_id', auth('user')->id())->update($request->validated()); 43 | 44 | return $this->error('更新成功'); 45 | } 46 | 47 | #[PostMapping('/setPwd')] 48 | public function setPassword(Request $request): JsonResponse 49 | { 50 | $data = $request->validate([ 51 | 'oldPassword' => 'required|string|max:20', 52 | 'newPassword' => 'required|string|min:6|max:20', 53 | 'rePassword' => 'required|same:newPassword', 54 | ]); 55 | $user_id = auth('user')->id(); 56 | $user = UserModel::query()->find($user_id); 57 | if (! password_verify($data['oldPassword'], $user['password'])) { 58 | return $this->error('旧密码不正确!'); 59 | } 60 | $user->password = password_hash($data['newPassword'], PASSWORD_DEFAULT); 61 | if ($user->save()) { 62 | return $this->success('更新成功'); 63 | } 64 | 65 | return $this->error('更新失败'); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Http/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | repository->find($id); 44 | return $this->success($item); 45 | } 46 | 47 | /** 列表响应 */ 48 | public function query(Request $request): JsonResponse 49 | { 50 | $list = $this->repository->list($request->query()); 51 | return $this->success($list); 52 | } 53 | 54 | /** 更新响应 */ 55 | public function update(Request $request, int $id): JsonResponse 56 | { 57 | $this->repository->update($id, $request->all()); 58 | return $this->success('ok'); 59 | } 60 | 61 | /** 新增响应 */ 62 | public function create(Request $request): JsonResponse 63 | { 64 | $this->repository->create($request->all()); 65 | return $this->success('ok'); 66 | } 67 | 68 | /** 删除响应 */ 69 | public function delete(int $id): JsonResponse 70 | { 71 | $this->repository->delete($id); 72 | return $this->success('ok'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Http/Middleware/AllowCrossDomainMiddleware.php: -------------------------------------------------------------------------------- 1 | headers->set('Access-Control-Allow-Origin', '*'); 22 | $response->headers->set('Access-Control-Allow-Credentials', 'true'); 23 | $response->headers->set('Access-Control-Max-Age', 1800); 24 | $response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 25 | $response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With'); 26 | 27 | // 如果是预检请求, 返回 204 28 | if ($request->isMethod('OPTIONS')) { 29 | return response()->json([], 204, $response->headers->all()); 30 | } 31 | 32 | return $response; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Middleware/AuthGuardMiddleware.php: -------------------------------------------------------------------------------- 1 | bearerToken(); 21 | if (!$token) { 22 | return response()->json(['msg' => 'Token not provided', 'success' => false], 401); 23 | } 24 | // 查找 token 25 | $accessToken = PersonalAccessToken::findToken($token); 26 | if (!$accessToken) { 27 | return response()->json(['msg' => 'Invalid token', 'success' => false], 401); 28 | } 29 | if (empty($guards)) { 30 | $guards = ['sys_users']; 31 | } 32 | Log::info('Guards: ', $guards); 33 | Log::info('Auth Providers: ', config('auth.providers')); 34 | foreach ($guards as $guard) { 35 | Log::info('Auth Providers Model: ' . config('auth.providers.' . $guard . '.model')); 36 | if ($accessToken->tokenable_type == config('auth.providers.' . $guard . '.model')) { 37 | return $next($request); 38 | } 39 | } 40 | return response()->json([ 41 | 'msg' => __('user.not_login'), 42 | 'success' => false 43 | ], 401); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Middleware/LoginLogMiddleware.php: -------------------------------------------------------------------------------- 1 | userAgent(); 22 | // 继续处理请求 23 | $response = $next($request); 24 | $user_id = auth()->id(); 25 | $username = auth()->user()['username']; 26 | try { 27 | // 获取响应状态和消息 28 | $content = json_decode($response->getContent(), true); // 响应内容 29 | $message = $content['msg'] ?? 'No message'; // 从响应中提取消息 30 | SysLoginRecordModel::create([ 31 | 'ipaddr' => $request->ip(), 32 | 'browser' => $this->getBrowser($userAgent), 33 | 'os' => $this->getOs($userAgent), 34 | 'username' => $username, 35 | 'user_id' => $user_id, 36 | 'login_location' => $this->getLocation($request->ip()), 37 | 'status' => $content['success'] ? '0' : '1', 38 | 'msg' => $message, 39 | 'login_time' => date('Y-m-d H:i:s'), 40 | ]); 41 | }catch (\Exception $e) { 42 | // 记录错误日志 43 | Log::error('Failed to log user login info: ' . $e->getMessage()); 44 | } 45 | return $response; 46 | } 47 | 48 | /** 49 | * 获取浏览器信息 50 | * @param string $userAgent 51 | * @return string 52 | */ 53 | private function getBrowser(string $userAgent): string 54 | { 55 | $browser = 'XXX'; 56 | // 简单的解析逻辑(可以根据需要扩展) 57 | if (str_contains($userAgent, 'Firefox')) { 58 | $browser = 'Firefox'; 59 | } elseif (str_contains($userAgent, 'Chrome')) { 60 | $browser = 'Chrome'; 61 | } elseif (str_contains($userAgent, 'Safari')) { 62 | $browser = 'Safari'; 63 | } elseif (str_contains($userAgent, 'MSIE') || str_contains($userAgent, 'Trident')) { 64 | $browser = 'Internet Explorer'; 65 | } 66 | return $browser; 67 | } 68 | 69 | /** 70 | * 获取操作系统信息 71 | * @param string $userAgent 72 | * @return string 73 | */ 74 | private function getOs(string $userAgent): string 75 | { 76 | $os = 'Unknown'; 77 | if (str_contains($userAgent, 'Windows')) { 78 | $os = 'Windows'; 79 | } elseif (str_contains($userAgent, 'Macintosh')) { 80 | $os = 'Mac OS'; 81 | } elseif (str_contains($userAgent, 'Linux')) { 82 | $os = 'Linux'; 83 | } elseif (str_contains($userAgent, 'Android')) { 84 | $os = 'Android'; 85 | } elseif (str_contains($userAgent, 'iOS')) { 86 | $os = 'iOS'; 87 | } 88 | return $os; 89 | } 90 | 91 | /** 92 | * 获取 IP 地址对应的地理位置 93 | * 94 | * @param string $ip 95 | * @return string 96 | */ 97 | private function getLocation(string $ip): string 98 | { 99 | if($ip == '127.0.0.1') { 100 | return '本地'; 101 | } 102 | // 这里可以使用第三方 API(如 IPStack 或 IPInfo)来获取地理位置 103 | try { 104 | $response = file_get_contents("https://ipinfo.io/{$ip}/json"); 105 | $data = json_decode($response, true); 106 | return $data['city'] . ', ' . $data['country']; 107 | }catch (\Exception $e) { 108 | return 'XXX'; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/Http/Middleware/SysAuthGuardMiddleware.php: -------------------------------------------------------------------------------- 1 | json('Not Authorized', 401); 22 | } 23 | } -------------------------------------------------------------------------------- /app/Http/Requests/App/UserRegisterRequest.php: -------------------------------------------------------------------------------- 1 | 'required|min:4|alphaDash', 13 | 'password' => 'required|min:4|alphaDash', 14 | 'rePassword' => 'required|min:4|same:password', 15 | 'email' => 'required|email', 16 | ]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Requests/App/UserUpdateInfoRequest.php: -------------------------------------------------------------------------------- 1 | 'required|min:4|max:20', 13 | 'nickname' => 'required|min:4|max:20', 14 | 'gender' => 'required', 15 | 'email' => 'required|email', 16 | 'avatar_id' => 'required|integer', 17 | 'mobile' => 'required|regex:/^1[34578]\d{9}$/', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Models/Sanctum/PersonalAccessToken.php: -------------------------------------------------------------------------------- 1 | tokenable_type == SysUserModel::class 14 | && $this->tokenable_id == 1 15 | ) { 16 | return true; 17 | } 18 | return in_array('*', $this->abilities) || 19 | array_key_exists($ability, array_flip($this->abilities)); 20 | } 21 | } -------------------------------------------------------------------------------- /app/Models/Sys/SysDeptModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 29 | 'sort' => 'integer', 30 | 'status' => 'integer' 31 | ]; 32 | 33 | /** 34 | * 定义与用户的关联关系(一个部门有多个用户) 35 | */ 36 | public function users(): HasMany 37 | { 38 | return $this->hasMany(SysUserModel::class, 'dept_id', 'id'); 39 | } 40 | 41 | /** 42 | * 定义父级部门关联 43 | */ 44 | public function parent(): BelongsTo 45 | { 46 | return $this->belongsTo(SysDeptModel::class, 'parent_id', 'id'); 47 | } 48 | 49 | /** 50 | * 定义子部门关联 51 | */ 52 | public function children(): HasMany 53 | { 54 | return $this->hasMany(SysDeptModel::class, 'parent_id', 'id'); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Models/Sys/SysDictItemModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 16 | 'switch' => 'integer', 17 | 'created_at' => 'datetime', 18 | 'updated_at' => 'datetime' 19 | ]; 20 | 21 | protected $fillable = [ 22 | 'dict_id', 23 | 'label', 24 | 'value', 25 | 'switch', 26 | 'status', 27 | ]; 28 | 29 | /** 30 | * 字典项关联字典表 31 | */ 32 | public function dict(): BelongsTo 33 | { 34 | return $this->belongsTo(SysDictModel::class, 'dict_id', 'id'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Models/Sys/SysDictModel.php: -------------------------------------------------------------------------------- 1 | 'datetime', 23 | 'updated_at' => 'datetime' 24 | ]; 25 | 26 | /** 27 | * 关联字典子项 28 | */ 29 | public function dictItems(): HasMany 30 | { 31 | return $this->hasMany(SysDictItemModel::class, 'dict_id', 'id'); 32 | } 33 | 34 | /** 35 | * 获取字典子项 36 | * @return array 37 | */ 38 | public function getDictItems(): array 39 | { 40 | return $this->query() 41 | ->with('dictItems') 42 | ->get() 43 | ->map(function ($dict) { 44 | return [ 45 | 'id' => $dict->id, 46 | 'name' => $dict->name, 47 | 'type' => $dict->type, 48 | 'code' => $dict->code, 49 | 'describe' => $dict->describe, 50 | 'dict_items' => $dict->dictItems->map(function ($dictItem) { 51 | return [ 52 | 'label' => $dictItem->label, 53 | 'value' => $dictItem->value, 54 | 'status' => $dictItem->status, 55 | ]; 56 | }), 57 | ]; 58 | })->toArray(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Models/Sys/SysFileGroupModel.php: -------------------------------------------------------------------------------- 1 | 'int', 17 | ]; 18 | 19 | protected $fillable = [ 20 | 'name', 21 | 'parent_id', 22 | 'sort', 23 | 'describe', 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /app/Models/Sys/SysFileModel.php: -------------------------------------------------------------------------------- 1 | 'int', 22 | 'channel' => 'int', 23 | 'file_type' => 'int', 24 | 'file_size' => 'int', 25 | 'uploader_id' => 'int', 26 | 'is_recycle' => 'int', 27 | ]; 28 | 29 | protected $fillable = [ 30 | 'group_id', 31 | 'disk', 32 | 'channel', 33 | 'storage', 34 | 'domain', 35 | 'file_type', 36 | 'file_name', 37 | 'file_path', 38 | 'file_size', 39 | 'file_ext', 40 | 'cover', 41 | 'uploader_id', 42 | 'is_recycle', 43 | ]; 44 | 45 | protected $appends = ['preview_url']; 46 | 47 | protected function previewUrl(): Attribute 48 | { 49 | return new Attribute( 50 | get: function ($value, array $data) { 51 | // 图片的预览图直接使用外链 52 | if ($data['file_type'] == FileType::IMAGE->value) { 53 | return Storage::disk($data['disk'])->url($data['file_path']); 54 | } 55 | // 生成默认的预览图 56 | $previewPath = FileType::from($data['file_type'])->previewPath(); 57 | return config('app.url').$previewPath; 58 | } 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Models/Sys/SysLoginRecordModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 31 | 'login_time' => 'datetime' 32 | ]; 33 | 34 | /** 35 | * 定义与用户的关联关系 36 | */ 37 | public function user(): BelongsTo 38 | { 39 | return $this->belongsTo(SysUserModel::class, 'user_id', 'id'); 40 | } 41 | } -------------------------------------------------------------------------------- /app/Models/Sys/SysRoleModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 26 | 'status' => 'integer', 27 | 'rules' => 'array' 28 | ]; 29 | 30 | /** 31 | * 角色用户关联 32 | */ 33 | public function users(): BelongsToMany 34 | { 35 | return $this->belongsToMany(SysUserModel::class, 'sys_user_role', 'role_id', 'user_id'); 36 | } 37 | 38 | /** 39 | * 角色权限关联中间表 40 | */ 41 | public function rules(): BelongsToMany 42 | { 43 | return $this->belongsToMany(SysRuleModel::class, 'sys_role_rule', 'role_id', 'rule_id'); 44 | } 45 | } -------------------------------------------------------------------------------- /app/Models/Sys/SysRuleModel.php: -------------------------------------------------------------------------------- 1 | 'integer', 33 | 'sort' => 'integer', 34 | 'status' => 'integer', 35 | 'show' => 'integer' 36 | ]; 37 | 38 | /** 39 | * 定义子权限关联 40 | */ 41 | public function children(): HasMany 42 | { 43 | return $this->hasMany(SysRuleModel::class, 'parent_id', 'id') 44 | ->orderBy('sort'); 45 | } 46 | 47 | /** 48 | * 定义父权限关联 49 | */ 50 | public function parent(): BelongsTo 51 | { 52 | return $this->belongsTo(SysRuleModel::class, 'parent_id', 'id'); 53 | } 54 | 55 | /** 56 | * 角色权限关联中间表 57 | */ 58 | public function roles(): BelongsToMany 59 | { 60 | return $this->belongsToMany(SysRoleModel::class, 'sys_role_rule', 'rule_id', 'role_id'); 61 | } 62 | } -------------------------------------------------------------------------------- /app/Models/Sys/SysSettingGroupModel.php: -------------------------------------------------------------------------------- 1 | hasMany(SysSettingModel::class ,'group_id', 'id'); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/Models/Sys/SysSettingModel.php: -------------------------------------------------------------------------------- 1 | 'int', 17 | 'sort' => 'int', 18 | ]; 19 | 20 | protected $fillable = [ 21 | 'key', 22 | 'title', 23 | 'describe', 24 | 'values', 25 | 'type', 26 | 'options', 27 | 'props', 28 | 'group_id', 29 | 'sort', 30 | ]; 31 | 32 | /** 33 | * 关联设置 34 | * @return BelongsTo 35 | */ 36 | public function group(): BelongsTo 37 | { 38 | return $this->belongsTo(SysSettingGroupModel::class, 'id', 'group_id'); 39 | } 40 | 41 | protected function options(): Attribute 42 | { 43 | return Attribute::make( 44 | get: function($value) { 45 | if(empty($value)) { 46 | return []; 47 | } 48 | $data = []; 49 | $value = explode("\n",$value); 50 | foreach ($value as $item) { 51 | $item = explode('=',$item); 52 | if(count($item) < 2) { 53 | continue; 54 | } 55 | $data[] = [ 56 | 'label' => $item[0], 57 | 'value' => $item[1] 58 | ]; 59 | } 60 | return $data; 61 | }, 62 | ); 63 | } 64 | 65 | protected function props(): Attribute 66 | { 67 | return Attribute::make( 68 | get: function($value) { 69 | if(empty($value)) { 70 | return []; 71 | } 72 | $data = []; 73 | $value = explode("\n",$value); 74 | foreach ($value as $item) { 75 | $item = explode('=',$item); 76 | if(count($item) < 2) { 77 | continue; 78 | } 79 | if($item[1] === 'false') { 80 | $data[$item[0]] = false; 81 | }elseif ($item[1] === 'true') { 82 | $data[$item[0]] = true; 83 | }else { 84 | $data[$item[0]] = $item[1]; 85 | } 86 | } 87 | return $data; 88 | }, 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /app/Models/Sys/SysUserModel.php: -------------------------------------------------------------------------------- 1 | 'datetime', 41 | 'login_time' => 'datetime', 42 | 'status' => 'integer', 43 | 'sex' => 'integer', 44 | 'avatar_id' => 'integer', 45 | 'dept_id' => 'integer' 46 | ]; 47 | 48 | 49 | protected $hidden = [ 50 | 'password', 51 | 'remember_token', 52 | ]; 53 | 54 | /** 55 | * 定义与部门的归属关系 56 | */ 57 | public function dept(): BelongsTo 58 | { 59 | return $this->belongsTo(SysDeptModel::class, 'dept_id', 'id'); 60 | } 61 | 62 | /** 63 | * 定义与角色的关联 64 | */ 65 | public function roles(): BelongsToMany 66 | { 67 | return $this->belongsToMany(SysRoleModel::class, 'sys_user_role', 'user_id', 'role_id'); 68 | } 69 | 70 | /** 71 | * 定义与登录日志的关联 72 | */ 73 | public function loginRecords(): HasMany 74 | { 75 | return $this->hasMany(SysLoginRecordModel::class, 'user_id', 'id'); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/Models/UserModel.php: -------------------------------------------------------------------------------- 1 | app->singleton(AnnoRoute::class, AnnoRoute::class); 13 | } 14 | 15 | public function boot(AnnoRoute $annoRoute): void 16 | { 17 | // 获取所有控制器 18 | $dir = app_path(); 19 | $controllers = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); 20 | foreach ($controllers as $controller) { 21 | if (! $controller->isFile()) { 22 | continue; 23 | } 24 | if (! str_contains($controller->getFilename(), 'Controller')) { 25 | continue; 26 | } 27 | if ($controller->getFilename() === 'BaseController.php') { 28 | continue; 29 | } 30 | $className = static::getClassName($controller); 31 | $annoRoute->register($className); 32 | } 33 | } 34 | 35 | /** 36 | * get class name 37 | */ 38 | private function getClassName($controller): string 39 | { 40 | $filePath = str_replace(app_path(), '', $controller->getPath()); 41 | $filePath = str_replace('/', '\\', $filePath); 42 | return 'App'.$filePath.'\\'.basename($controller->getFileName(), '.php'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(SysSettingService::class, SysSettingService::class); 22 | 23 | $this->app->bind('Illuminate\Pagination\LengthAwarePaginator', function ($app, $options) { 24 | return new LengthAwarePaginatorService( 25 | $options['items'], 26 | $options['total'], 27 | $options['perPage'], 28 | $options['currentPage'], 29 | $options['options'] 30 | ); 31 | }); 32 | 33 | $this->app->bind(ExceptionsHandler::class, \App\Exceptions\ExceptionsHandler::class); 34 | } 35 | 36 | /** 37 | * Bootstrap any application services. 38 | */ 39 | public function boot(): void 40 | { 41 | Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class); 42 | if (!$this->app->runningInConsole()) { 43 | SysSettingService::refreshSettings(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Providers/AutoBindServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\\Repositories', 17 | 'Services' => 'App\\Services', 18 | ]; 19 | 20 | /** 21 | * 要排除的基类 22 | */ 23 | protected array $excludeClasses = [ 24 | 'App\\Repositories\\Repository', // 仓储基类 25 | 'App\\Services\\Service', // 服务基类 26 | ]; 27 | 28 | public function register(): void 29 | { 30 | foreach ($this->scanConfig as $directory => $namespace) { 31 | $classes = $this->scanClasses($directory, $namespace); 32 | 33 | foreach ($classes as $class) { 34 | if ($this->shouldBind($class)) { 35 | $this->app->bind($class, $class); 36 | // 如果需要单例模式,使用:$this->app->singleton($class, $class); 37 | } 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * 扫描目录获取所有类 44 | */ 45 | protected function scanClasses(string $directory, string $namespace): array 46 | { 47 | $absolutePath = app_path($directory); 48 | 49 | if (!is_dir($absolutePath)) { 50 | return []; 51 | } 52 | 53 | $classes = []; 54 | $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($absolutePath)); 55 | 56 | $regex = new RegexIterator($iterator, '/^.+\.php$/i', RegexIterator::GET_MATCH); 57 | 58 | foreach ($regex as $file) { 59 | $filePath = $file[0]; 60 | $relativePath = str_replace([$absolutePath, '.php'], '', $filePath); 61 | $relativePath = trim($relativePath, DIRECTORY_SEPARATOR); 62 | 63 | $className = str_replace( 64 | DIRECTORY_SEPARATOR, 65 | '\\', 66 | $namespace . '\\' . $relativePath 67 | ); 68 | 69 | if (class_exists($className)) { 70 | $classes[] = $className; 71 | } 72 | } 73 | 74 | return $classes; 75 | } 76 | 77 | /** 78 | * 判断是否应该绑定该类 79 | */ 80 | protected function shouldBind(string $className): bool 81 | { 82 | // 排除抽象类 83 | if ((new \ReflectionClass($className))->isAbstract()) { 84 | return false; 85 | } 86 | 87 | // 排除基类 88 | foreach ($this->excludeClasses as $exclude) { 89 | if (str_contains($className, $exclude) || class_basename($className) === $exclude) { 90 | return false; 91 | } 92 | } 93 | 94 | return true; 95 | } 96 | 97 | /** 98 | * 获取已绑定的类列表(用于调试) 99 | */ 100 | public function getBoundClasses(): array 101 | { 102 | $boundClasses = []; 103 | 104 | foreach ($this->scanConfig as $directory => $namespace) { 105 | $classes = $this->scanClasses($directory, $namespace); 106 | foreach ($classes as $class) { 107 | if ($this->shouldBind($class)) { 108 | $boundClasses[] = $class; 109 | } 110 | } 111 | } 112 | 113 | return $boundClasses; 114 | } 115 | } -------------------------------------------------------------------------------- /app/Providers/Telescope/Contracts/EntriesRepository.php: -------------------------------------------------------------------------------- 1 | type = $type; 30 | 31 | $this->content = array_merge($content, [ 32 | 'host_name' => gethostname(), 33 | 'recorded_at' => now()->toDateTimeString() 34 | ]); 35 | } 36 | 37 | /** 38 | * Create a new entry instance. 39 | */ 40 | public static function make(array $content, string $type): static 41 | { 42 | return new static($content, $type); 43 | } 44 | 45 | /** 46 | * Set the currently authenticated user. 47 | */ 48 | public function user(Authenticatable $user): static 49 | { 50 | $this->user = $user; 51 | 52 | $this->content = array_merge($this->content, [ 53 | 'user' => [ 54 | 'id' => $user->getAuthIdentifier(), 55 | 'name' => $user->username ?? null, 56 | 'email' => $user->email ?? null, 57 | 'avatar' => $user->avatarUrl ?? null, 58 | ], 59 | ]); 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Set the entry's type. 66 | */ 67 | public function type(string $type): static 68 | { 69 | $this->type = $type; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * Determine if the incoming entry is a cache entry. 76 | */ 77 | public function isCache(): bool 78 | { 79 | return $this->type === EntryType::CACHE; 80 | } 81 | 82 | /** 83 | * Determine if the incoming entry is a auth. 84 | */ 85 | public function isAuth(): bool 86 | { 87 | return $this->type === EntryType::AUTH; 88 | } 89 | 90 | /** 91 | * Determine if the incoming entry is a query. 92 | */ 93 | public function isQuery(): bool 94 | { 95 | return $this->type === EntryType::QUERY; 96 | } 97 | 98 | /** 99 | * Determine if the incoming entry is a slow query. 100 | */ 101 | public function isSlowQuery(): bool 102 | { 103 | return $this->type === EntryType::QUERY && ($this->content['slow'] ?? false); 104 | } 105 | 106 | /** 107 | * Determine if the incoming entry is a redis. 108 | */ 109 | public function isRedis(): bool 110 | { 111 | return $this->type === EntryType::REDIS; 112 | } 113 | 114 | /** 115 | * Determine if the incoming entry is a request. 116 | */ 117 | public function isRequest(): bool 118 | { 119 | return $this->type === EntryType::REQUEST; 120 | } 121 | 122 | /** 123 | * Determine if the incoming entry is a failed request. 124 | */ 125 | public function isFailedRequest(): bool 126 | { 127 | return $this->type === EntryType::REQUEST && 128 | ($this->content['response_status'] ?? 200) >= 500; 129 | } 130 | 131 | /** 132 | * Get the entry's content as a string. 133 | */ 134 | public function getContentAsString(): string 135 | { 136 | return json_encode($this->content, JSON_INVALID_UTF8_SUBSTITUTE); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/Providers/Telescope/ListensForStorageOpportunities.php: -------------------------------------------------------------------------------- 1 | listen(RequestReceived::class, function ($event) { 27 | if (static::requestIsToApprovedUri($event->request)) { 28 | static::startRecording(); 29 | } 30 | }); 31 | 32 | $app['events']->listen(RequestTerminated::class, function ($event) { 33 | static::stopRecording(); 34 | }); 35 | } 36 | 37 | /** 38 | * Store the entries in queue before the application termination. 39 | * 40 | * This handles storing entries for HTTP requests and Artisan commands. 41 | */ 42 | protected static function storeEntriesBeforeTermination($app): void 43 | { 44 | $app->terminating(function () use ($app) { 45 | static::store($app[EntriesRepository::class]); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Providers/Telescope/RegistersWatchers.php: -------------------------------------------------------------------------------- 1 | $watcher) { 28 | if (is_string($key) && $watcher === false) { 29 | continue; 30 | } 31 | 32 | if (is_array($watcher) && ! ($watcher['enabled'] ?? true)) { 33 | continue; 34 | } 35 | 36 | $watcher = $app->make(is_string($key) ? $key : $watcher, [ 37 | 'options' => is_array($watcher) ? $watcher : [], 38 | ]); 39 | 40 | static::$watchers[] = get_class($watcher); 41 | 42 | $watcher->register($app); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Storage/EntryQueryOptions.php: -------------------------------------------------------------------------------- 1 | validate([ 43 | 'type' => ['nullable', 'string'], 44 | 'date' => ['nullable', 'date'], 45 | 'pageSize' => ['nullable', 'integer'], 46 | 'current' => ['nullable', 'integer'], 47 | ]); 48 | 49 | return (new static) 50 | ->type($params['type'] ?? 'request') 51 | ->date($params['date'] ?? date('Y-m-d')) 52 | ->page($params['current'] ?? 1) 53 | ->limit($params['pageSize'] ?? 10); 54 | } 55 | 56 | /** 57 | * Set the date of entries to retrieve. 58 | */ 59 | public function date(string $date): static 60 | { 61 | $this->date = $date; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Set the type of entries to retrieve. 68 | */ 69 | public function page(int $page): static 70 | { 71 | $this->page = $page; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Set the type of entries to retrieve. 78 | */ 79 | public function type(string $type): static 80 | { 81 | $this->type = $type; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * Set the number of entries that should be retrieved. 88 | */ 89 | public function limit(int $limit): static 90 | { 91 | $this->limit = $limit; 92 | 93 | return $this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Storage/StorageRepository.php: -------------------------------------------------------------------------------- 1 | type. '-' . $options->date . '.jsonl'; 16 | $storage = Storage::disk('telescope'); 17 | if (!$storage->exists($fileName)) { 18 | return []; 19 | } 20 | $filePath = storage_path('telescope/' . $fileName); 21 | 22 | $collection = LazyCollection::make(function () use ($filePath) { 23 | $file = fopen($filePath, 'r'); 24 | 25 | while (($line = fgets($file)) !== false) { 26 | yield json_decode($line, true); 27 | } 28 | fclose($file); 29 | }); 30 | 31 | return [ 32 | 'data' => $collection->forPage($options->page, $options->limit)->values()->all(), 33 | 'total' => $collection->count(), 34 | 'pageSize' => $options->limit, 35 | 'current' => $options->page, 36 | ]; 37 | } 38 | 39 | public function store(array|Collection $entries): void 40 | { 41 | if ($entries->isEmpty()) { 42 | return; 43 | } 44 | $date = date('Y-m-d'); 45 | $contents = []; 46 | foreach (config('telescope.watchers') as $key => $watcher) { 47 | if (! is_string($key)) continue; 48 | if( $watcher === false ) continue; 49 | $contents[$key] = []; 50 | } 51 | $entries->each(function (IncomingEntry $chunked) use (&$contents) { 52 | if(empty($chunked->content)) { 53 | return; 54 | } 55 | $contents[$chunked->type][] = $chunked->getContentAsString(); 56 | }); 57 | $storage = Storage::disk('telescope'); 58 | foreach ($contents as $type => $entries) { 59 | if (empty($entries)) { 60 | continue; 61 | } 62 | $fileName = $type. '-' . $date . '.jsonl'; 63 | // 文件不存在创建文件 64 | if (!$storage->exists($fileName)) { 65 | $storage->put($fileName, implode("\n", $entries)); 66 | continue; 67 | } 68 | $storage->append($fileName, implode("\n", $entries)); 69 | } 70 | } 71 | 72 | public function clear(): void 73 | { 74 | // TODO: Implement clear() method. 75 | } 76 | } -------------------------------------------------------------------------------- /app/Providers/Telescope/TelescopeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app); 21 | Telescope::listenForStorageOpportunities($this->app); 22 | } 23 | 24 | /** 25 | * Register any package services. 26 | */ 27 | public function register(): void 28 | { 29 | $this->registerDatabaseDriver(); 30 | 31 | $this->hideSensitiveRequestDetails(); 32 | 33 | Telescope::filter(function (IncomingEntry $entry) { 34 | return $entry->isFailedRequest() || 35 | $entry->isQuery() || 36 | $entry->isSlowQuery() || 37 | $entry->isRequest() || 38 | $entry->isCache() || 39 | $entry->isRedis() || 40 | $entry->isAuth(); 41 | }); 42 | } 43 | 44 | /** 45 | * Register the package database storage driver. 46 | */ 47 | protected function registerDatabaseDriver(): void 48 | { 49 | $this->app->singleton( 50 | EntriesRepository::class, StorageRepository::class 51 | ); 52 | } 53 | 54 | /** 55 | * Prevent sensitive request details from being logged by Telescope. 56 | */ 57 | protected function hideSensitiveRequestDetails(): void 58 | { 59 | Telescope::hideRequestParameters(['_token']); 60 | 61 | Telescope::hideRequestHeaders([ 62 | 'cookie', 63 | 'x-csrf-token', 64 | 'x-xsrf-token', 65 | ]); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Watchers/CacheWatcher.php: -------------------------------------------------------------------------------- 1 | listen(CacheHit::class, [$this, 'recordCacheHit']); 25 | $app['events']->listen(CacheMissed::class, [$this, 'recordCacheMissed']); 26 | 27 | $app['events']->listen(KeyWritten::class, [$this, 'recordKeyWritten']); 28 | $app['events']->listen(KeyForgotten::class, [$this, 'recordKeyForgotten']); 29 | } 30 | 31 | /** 32 | * Record a cache key was found. 33 | * 34 | * @param CacheHit $event 35 | * @return void 36 | */ 37 | public function recordCacheHit(CacheHit $event): void 38 | { 39 | if (! Telescope::isRecording() || $this->shouldIgnore($event)) { 40 | return; 41 | } 42 | 43 | $entry = IncomingEntry::make([ 44 | 'type' => 'hit', 45 | 'key' => $event->key, 46 | 'value' => $this->formatValue($event), 47 | ], EntryType::CACHE); 48 | 49 | Telescope::record($entry); 50 | } 51 | 52 | /** 53 | * Record a missing cache key. 54 | * 55 | * @param CacheMissed $event 56 | * @return void 57 | */ 58 | public function recordCacheMissed(CacheMissed $event): void 59 | { 60 | if (! Telescope::isRecording() || $this->shouldIgnore($event)) { 61 | return; 62 | } 63 | $entry = IncomingEntry::make([ 64 | 'type' => 'missed', 65 | 'key' => $event->key, 66 | ], EntryType::CACHE); 67 | 68 | Telescope::record($entry); 69 | } 70 | 71 | /** 72 | * Record a cache key was updated. 73 | * 74 | * @param KeyWritten $event 75 | * @return void 76 | */ 77 | public function recordKeyWritten(KeyWritten $event): void 78 | { 79 | if (! Telescope::isRecording() || $this->shouldIgnore($event)) { 80 | return; 81 | } 82 | $entry = IncomingEntry::make([ 83 | 'type' => 'set', 84 | 'key' => $event->key, 85 | 'value' => $this->formatValue($event), 86 | 'expiration' => $event->seconds, 87 | ], EntryType::CACHE); 88 | 89 | Telescope::record($entry); 90 | } 91 | 92 | /** 93 | * Record a cache key was forgotten / removed. 94 | * 95 | * @param KeyForgotten $event 96 | * @return void 97 | */ 98 | public function recordKeyForgotten(KeyForgotten $event): void 99 | { 100 | if (! Telescope::isRecording() || $this->shouldIgnore($event)) { 101 | return; 102 | } 103 | $entry = IncomingEntry::make([ 104 | 'type' => 'forget', 105 | 'key' => $event->key, 106 | ], EntryType::CACHE); 107 | 108 | Telescope::record($entry); 109 | } 110 | 111 | /** 112 | * Determine the value of an event. 113 | */ 114 | private function formatValue(mixed $event): mixed 115 | { 116 | return (! $this->shouldHideValue($event)) 117 | ? $event->value 118 | : '********'; 119 | } 120 | 121 | /** 122 | * Determine if the event value should be ignored. 123 | */ 124 | private function shouldHideValue(mixed $event): bool 125 | { 126 | return Str::is( 127 | $this->options['hidden'] ?? [], 128 | $event->key 129 | ); 130 | } 131 | 132 | /** 133 | * Determine if the event should be ignored. 134 | */ 135 | private function shouldIgnore(mixed $event): bool 136 | { 137 | return Str::is([ 138 | 'illuminate:queue:restart', 139 | 'framework/schedule*', 140 | 'telescope:*', 141 | ], $event->key); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Watchers/FetchesStackTrace.php: -------------------------------------------------------------------------------- 1 | forget($forgetLines); 18 | 19 | return $trace->first(function ($frame) { 20 | if (! isset($frame['file'])) { 21 | return false; 22 | } 23 | 24 | return ! Str::contains($frame['file'], $this->ignoredPaths()); 25 | }); 26 | } 27 | 28 | /** 29 | * Get the file paths that should not be used by backtraces. 30 | * 31 | * @return array 32 | */ 33 | protected function ignoredPaths(): array 34 | { 35 | return array_merge( 36 | [base_path('vendor'.DIRECTORY_SEPARATOR.$this->ignoredVendorPath())], 37 | $this->options['ignore_paths'] ?? [] 38 | ); 39 | } 40 | 41 | /** 42 | * Choose the frame outside of either Telescope / Laravel or all extends. 43 | */ 44 | protected function ignoredVendorPath(): string 45 | { 46 | if (! ($this->options['ignore_packages'] ?? true)) { 47 | return 'laravel'; 48 | } 49 | return ""; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Watchers/QueryWatcher.php: -------------------------------------------------------------------------------- 1 | listen(QueryExecuted::class, [$this, 'recordQuery']); 20 | } 21 | 22 | /** 23 | * Record a query was executed. 24 | * 25 | * @param QueryExecuted $event 26 | * @return void 27 | */ 28 | public function recordQuery(QueryExecuted $event): void 29 | { 30 | if (! Telescope::isRecording()) { 31 | return; 32 | } 33 | 34 | $time = $event->time; 35 | 36 | if ($caller = $this->getCallerFromStackTrace()) { 37 | Telescope::record(IncomingEntry::make([ 38 | 'connection' => $event->connectionName, 39 | 'sql' => $this->replaceBindings($event), 40 | 'time' => number_format($time, 2, '.', ''), 41 | 'slow' => isset($this->options['slow']) && $time >= $this->options['slow'], 42 | 'file' => $caller['file'], 43 | 'line' => $caller['line'], 44 | ], EntryType::QUERY)); 45 | } 46 | } 47 | 48 | /** 49 | * Calculate the family look-up hash for the query event. 50 | */ 51 | public function familyHash(QueryExecuted $event): string 52 | { 53 | return md5($event->sql); 54 | } 55 | 56 | /** 57 | * Format the given bindings to strings. 58 | */ 59 | protected function formatBindings(QueryExecuted $event): array 60 | { 61 | return $event->connection->prepareBindings($event->bindings); 62 | } 63 | 64 | /** 65 | * Replace the placeholders with the actual bindings. 66 | */ 67 | public function replaceBindings(QueryExecuted $event): string 68 | { 69 | $sql = $event->sql; 70 | 71 | foreach ($this->formatBindings($event) as $key => $binding) { 72 | $regex = is_numeric($key) 73 | ? "/\?(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/" 74 | : "/:{$key}(?=(?:[^'\\\']*'[^'\\\']*')*[^'\\\']*$)/"; 75 | 76 | if ($binding === null) { 77 | $binding = 'null'; 78 | } elseif (! is_int($binding) && ! is_float($binding)) { 79 | $binding = $this->quoteStringBinding($event, $binding); 80 | } 81 | 82 | $sql = preg_replace( 83 | $regex, 84 | $binding, 85 | $sql, 86 | is_numeric($key) ? 1 : -1 87 | ); 88 | } 89 | 90 | return $sql; 91 | } 92 | 93 | /** 94 | * Add quotes to string bindings. 95 | */ 96 | protected function quoteStringBinding(QueryExecuted $event, string $binding): string 97 | { 98 | try { 99 | $pdo = $event->connection->getPdo(); 100 | 101 | if ($pdo instanceof \PDO) { 102 | return $pdo->quote($binding); 103 | } 104 | } catch (\PDOException $e) { 105 | throw_if('IM001' !== $e->getCode(), $e); 106 | } 107 | 108 | // Fallback when PDO::quote function is missing... 109 | $binding = \strtr($binding, [ 110 | chr(26) => '\\Z', 111 | chr(8) => '\\b', 112 | '"' => '\"', 113 | "'" => "\'", 114 | '\\' => '\\\\', 115 | ]); 116 | 117 | return "'".$binding."'"; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Watchers/RedisWatcher.php: -------------------------------------------------------------------------------- 1 | bound('redis')) { 18 | return; 19 | } 20 | 21 | $app['events']->listen(CommandExecuted::class, [$this, 'recordCommand']); 22 | 23 | foreach ((array) $app['redis']->connections() as $connection) { 24 | $connection->setEventDispatcher($app['events']); 25 | } 26 | 27 | $app['redis']->enableEvents(); 28 | } 29 | 30 | /** 31 | * Record a Redis command was executed. 32 | * 33 | * @param CommandExecuted $event 34 | * @return void 35 | */ 36 | public function recordCommand(CommandExecuted $event): void 37 | { 38 | if (! Telescope::isRecording() || $this->shouldIgnore($event)) { 39 | return; 40 | } 41 | 42 | Telescope::record(IncomingEntry::make([ 43 | 'connection' => $event->connectionName, 44 | 'command' => $this->formatCommand($event->command, $event->parameters), 45 | 'time' => number_format($event->time, 2, '.', ''), 46 | ], EntryType::REDIS)); 47 | } 48 | 49 | /** 50 | * Format the given Redis command. 51 | */ 52 | private function formatCommand(string $command, array $parameters): string 53 | { 54 | $parameters = collect($parameters)->map(function ($parameter) { 55 | if (is_array($parameter)) { 56 | return collect($parameter)->map(function ($value, $key) { 57 | if (is_array($value)) { 58 | return json_encode($value); 59 | } 60 | 61 | return is_int($key) ? $value : "{$key} {$value}"; 62 | })->implode(' '); 63 | } 64 | 65 | return $parameter; 66 | })->implode(' '); 67 | 68 | return "{$command} {$parameters}"; 69 | } 70 | 71 | /** 72 | * Determine if the event should be ignored. 73 | */ 74 | private function shouldIgnore(mixed $event): bool 75 | { 76 | return in_array($event->command, [ 77 | 'pipeline', 'transaction', 78 | ]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/Providers/Telescope/Watchers/Watcher.php: -------------------------------------------------------------------------------- 1 | options = $options; 23 | } 24 | 25 | /** 26 | * Register the watcher. 27 | */ 28 | abstract public function register($app); 29 | } 30 | -------------------------------------------------------------------------------- /app/Repositories/Sys/SysDeptRepository.php: -------------------------------------------------------------------------------- 1 | 'required|unique:sys_dept,name', 14 | 'sort' => 'required|integer', 15 | 'parent_id' => 'required|integer|exists:sys_dept,id', 16 | 'leader' => 'required', 17 | 'phone' => 'required', 18 | 'email' => 'required|email', 19 | 'status' => 'required|in:0,1' 20 | ]; 21 | 22 | /** @var array 验证消息 */ 23 | protected array $messages = [ 24 | 'name.required' => '部门名称不能为空', 25 | 'name.unique' => '部门名称已存在', 26 | 'sort.required' => '排序不能为空', 27 | 'sort.integer' => '排序必须为整数', 28 | 'parent_id.required' => '父级部门不能为空', 29 | 'parent_id.integer' => '父级部门格式错误', 30 | 'parent_id.exists' => '父级部门不存在', 31 | 'leader.required' => '负责人不能为空', 32 | 'phone.required' => '电话不能为空', 33 | 'email.required' => '邮箱不能为空', 34 | 'email.email' => '邮箱格式错误', 35 | 'status.required' => '状态不能为空', 36 | 'status.in' => '状态格式错误' 37 | ]; 38 | 39 | /** @var array|string[] 搜索字段 */ 40 | protected array $searchField = [ 41 | 'parent_id' => '=', 42 | 'status' => '=' 43 | ]; 44 | 45 | /** @var array|string[] 快速搜索字段 */ 46 | protected array $quickSearchField = ['name', 'leader', 'phone']; 47 | 48 | /** 49 | * @inheritDoc 50 | */ 51 | protected function model(): Builder 52 | { 53 | return SysDeptModel::newQuery(); 54 | } 55 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysDictItemRepository.php: -------------------------------------------------------------------------------- 1 | 'required|exists:sys_dict,id', 14 | 'label' => 'required', 15 | 'value' => 'required', 16 | 'switch' => 'required|in:0,1', 17 | 'status' => 'required|in:default,success,error,processing,warning' 18 | ]; 19 | 20 | /** @var array 验证消息 */ 21 | protected array $messages = [ 22 | 'dict_id.required' => '字典ID不能为空', 23 | 'dict_id.exists' => '字典不存在', 24 | 'label.required' => '字典项名称不能为空', 25 | 'value.required' => '字典项值不能为空', 26 | 'switch.required' => '启用状态不能为空', 27 | 'switch.in' => '启用状态格式错误', 28 | 'status.required' => '状态不能为空', 29 | 'status.in' => '状态格式错误' 30 | ]; 31 | 32 | /** @var array|string[] 搜索字段 */ 33 | protected array $searchField = [ 34 | 'dict_id' => '=', 35 | 'switch' => '=', 36 | 'status' => '=' 37 | ]; 38 | 39 | /** @var array|string[] 快速搜索字段 */ 40 | protected array $quickSearchField = ['label', 'value']; 41 | 42 | /** 43 | * @inheritDoc 44 | */ 45 | protected function model(): Builder 46 | { 47 | return SysDictItemModel::query(); 48 | } 49 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysDictRepository.php: -------------------------------------------------------------------------------- 1 | 'required', 14 | 'code' => 'required|unique:sys_dict,code', 15 | 'describe' => 'sometimes', 16 | 'type' => 'required|in:default,custom,system' 17 | ]; 18 | 19 | /** @var array 验证消息 */ 20 | protected array $messages = [ 21 | 'name.required' => '字典名称不能为空', 22 | 'code.required' => '字典编码不能为空', 23 | 'code.unique' => '字典编码已存在', 24 | 'type.required' => '字典类型不能为空', 25 | 'type.in' => '字典类型格式错误' 26 | ]; 27 | 28 | /** @var array|string[] 搜索字段 */ 29 | protected array $searchField = [ 30 | 'type' => '=' 31 | ]; 32 | 33 | /** @var array|string[] 快速搜索字段 */ 34 | protected array $quickSearchField = ['name', 'code', 'describe']; 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | protected function model(): Builder 40 | { 41 | return SysDictModel::query(); 42 | } 43 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysFileGroupRepository.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 13 | 'describe' => 'sometimes|string|max:500', 14 | 'sort' => 'sometimes|integer|min:0', 15 | ]; 16 | 17 | protected array $messages = [ 18 | 'name.required' => '分组名称不能为空', 19 | 'name.string' => '分组名称必须是字符串', 20 | 'name.max' => '分组名称不能超过50个字符', 21 | 22 | 'sort.integer' => '分组排序必须是整数', 23 | 'sort.min' => '分组排序不能为负数', 24 | 25 | 'describe.string' => '分组描述必须是字符串', 26 | 'describe.max' => '分组描述不能超过500个字符', 27 | ]; 28 | 29 | protected array $searchField = ['name' => 'like']; 30 | 31 | protected function model(): Builder 32 | { 33 | return SysFileGroupModel::query(); 34 | } 35 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysFileRepository.php: -------------------------------------------------------------------------------- 1 | 'required|integer|exists:sys_file_group,id', 13 | 'channel' => 'required|integer|in:10,20', 14 | 'disk' => 'required|string|max:10', 15 | 'file_type' => 'required|integer', 16 | 'file_name' => 'required|string|max:255', 17 | 'file_path' => 'required|string|max:255', 18 | 'file_size' => 'required|integer|min:0', 19 | 'file_ext' => 'required|string|max:20', 20 | 'uploader_id' => 'required|integer', 21 | ]; 22 | 23 | protected array $messages = [ 24 | // 文件表验证消息 25 | 'group_id.required' => '文件分组ID不能为空', 26 | 'group_id.integer' => '文件分组ID必须是整数', 27 | 'group_id.exists' => '文件分组不存在', 28 | 29 | 'channel.required' => '上传来源不能为空', 30 | 'channel.integer' => '上传来源必须是整数', 31 | 'channel.in' => '上传来源必须是10(系统用户)或20(App用户端)', 32 | 33 | 'disk.required' => '存储方式不能为空', 34 | 'disk.string' => '存储方式必须是字符串', 35 | 'disk.max' => '存储方式不能超过10个字符', 36 | 37 | 'file_type.required' => '文件类型不能为空', 38 | 'file_type.integer' => '文件类型必须是整数', 39 | 40 | 'file_name.required' => '文件名称不能为空', 41 | 'file_name.string' => '文件名称必须是字符串', 42 | 'file_name.max' => '文件名称不能超过255个字符', 43 | 44 | 'file_path.required' => '文件路径不能为空', 45 | 'file_path.string' => '文件路径必须是字符串', 46 | 'file_path.max' => '文件路径不能超过255个字符', 47 | 48 | 'file_size.required' => '文件大小不能为空', 49 | 'file_size.integer' => '文件大小必须是整数', 50 | 'file_size.min' => '文件大小不能为负数', 51 | 52 | 'file_ext.required' => '文件扩展名不能为空', 53 | 'file_ext.string' => '文件扩展名必须是字符串', 54 | 'file_ext.max' => '文件扩展名不能超过20个字符', 55 | 56 | 'uploader_id.required' => '上传者用户ID不能为空', 57 | 'uploader_id.integer' => '上传者用户ID必须是整数', 58 | ]; 59 | 60 | protected array $searchField = [ 61 | 'group_id' => '=', 62 | 'name' => 'like', 63 | 'file_type' => '=', 64 | ]; 65 | 66 | /** 67 | * @inheritDoc 68 | */ 69 | protected function model(): Builder 70 | { 71 | return SysFileModel::query(); 72 | } 73 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysLoginRecordRepository.php: -------------------------------------------------------------------------------- 1 | 'required', 14 | 'user_id' => 'required|exists:sys_user,id', 15 | 'ipaddr' => 'required|ip', 16 | 'login_location' => 'required', 17 | 'browser' => 'required', 18 | 'os' => 'required', 19 | 'status' => 'required|in:0,1', 20 | 'msg' => 'required', 21 | 'login_time' => 'required|date' 22 | ]; 23 | 24 | /** @var array 验证消息 */ 25 | protected array $messages = [ 26 | 'username.required' => '用户名不能为空', 27 | 'user_id.required' => '用户ID不能为空', 28 | 'user_id.exists' => '用户不存在', 29 | 'ipaddr.required' => 'IP地址不能为空', 30 | 'ipaddr.ip' => 'IP地址格式错误', 31 | 'login_location.required' => '登录地点不能为空', 32 | 'browser.required' => '浏览器不能为空', 33 | 'os.required' => '操作系统不能为空', 34 | 'status.required' => '登录状态不能为空', 35 | 'status.in' => '登录状态格式错误', 36 | 'msg.required' => '提示消息不能为空', 37 | 'login_time.required' => '登录时间不能为空', 38 | 'login_time.date' => '登录时间格式错误' 39 | ]; 40 | 41 | /** @var array|string[] 搜索字段 */ 42 | protected array $searchField = [ 43 | 'status' => '=', 44 | 'user_id' => '=' 45 | ]; 46 | 47 | /** @var array|string[] 快速搜索字段 */ 48 | protected array $quickSearchField = ['username', 'ipaddr', 'browser', 'os']; 49 | 50 | /** 51 | * @inheritDoc 52 | */ 53 | protected function model(): Builder 54 | { 55 | return SysLoginRecordModel::query(); 56 | } 57 | 58 | /** 59 | * 通过用户ID获取记录 60 | */ 61 | public function getRecordByID(int $id): array 62 | { 63 | return $this->model() 64 | ->where('user_id', $id) 65 | ->limit(20) 66 | ->orderBy('id', 'desc') 67 | ->get() 68 | ->toArray(); 69 | } 70 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysRoleRepository.php: -------------------------------------------------------------------------------- 1 | 'required|unique:sys_role,name', 15 | 'sort' => 'required|integer', 16 | 'description' => 'required', 17 | 'status' => 'required|in:0,1' 18 | ]; 19 | 20 | /** @var array 验证消息 */ 21 | protected array $messages = [ 22 | 'name.required' => '角色名称不能为空', 23 | 'name.unique' => '角色名称已存在', 24 | 'sort.required' => '排序不能为空', 25 | 'sort.integer' => '排序必须为整数', 26 | 'description.required' => '描述不能为空', 27 | 'status.required' => '状态不能为空', 28 | 'status.in' => '状态格式错误' 29 | ]; 30 | 31 | /** @var array|string[] 搜索字段 */ 32 | protected array $searchField = [ 33 | 'status' => '=', 34 | 'name' => 'like', 35 | ]; 36 | 37 | /** @var array|string[] 快速搜索字段 */ 38 | protected array $quickSearchField = ['name', 'description']; 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | protected function model(): Builder 44 | { 45 | return SysRoleModel::query(); 46 | } 47 | 48 | public function setRule(Request $request): void 49 | { 50 | $validated = $request->validate([ 51 | 'role_id' => 'required|exists:sys_role,id', 52 | 'rule_ids' => 'required|array|exists:sys_rule,id', 53 | ]); 54 | $model = $this->model()->findOrFail($validated['role_id']); 55 | $model->rules()->sync($validated['rule_ids']); 56 | } 57 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysRuleRepository.php: -------------------------------------------------------------------------------- 1 | 'required', 13 | 'type' => 'required|in:1,2,3', 14 | 'sort' => 'required|integer', 15 | 'key' => 'required|unique:sys_rule,key', 16 | 'path' => 'required', 17 | 'status' => 'required|in:1,0', 18 | 'show' => 'required|in:1,0', 19 | 'parent_id' => 'required|integer|exists:sys_rule,id' 20 | ]; 21 | 22 | protected array $messages = [ 23 | 'name.required' => '权限名称不能为空', 24 | 'type.required' => '类型不能为空', 25 | 'type.in' => '类型格式错误', 26 | 'sort.required' => '排序不能为空', 27 | 'sort.integer' => '排序必须为整数', 28 | 'key.required' => '唯一标识不能为空', 29 | 'key.unique' => '唯一标识已存在', 30 | 'path.required' => '路径不能为空', 31 | 'status.required' => '状态不能为空', 32 | 'status.in' => '状态格式错误', 33 | 'show.required' => '显示状态不能为空', 34 | 'show.in' => '显示状态格式错误', 35 | 'parent_id.required' => '父级权限不能为空', 36 | 'parent_id.integer' => '父级权限格式错误', 37 | 'parent_id.exists' => '父级权限不存在' 38 | ]; 39 | 40 | protected array $searchField = [ 41 | 'type' => '=', 42 | 'status' => '=', 43 | 'show' => '=', 44 | 'parent_id' => '=' 45 | ]; 46 | 47 | protected array $quickSearchField = ['name', 'key', 'path']; 48 | 49 | protected function model(): Builder 50 | { 51 | return SysRuleModel::query(); 52 | } 53 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysSettingGroupRepository.php: -------------------------------------------------------------------------------- 1 | 'required', 14 | 'title' => 'required', 15 | 'remark' => 'sometimes|required', 16 | ]; 17 | 18 | protected array $messages = [ 19 | 'key.required' => '键名字段是必填的', 20 | 'title.required' => '标题字段是必填的', 21 | 'remark.required' => '备注字段是必填的', 22 | ]; 23 | 24 | protected array $searchField = [ 25 | 'id' => '=', 26 | 'key' => '=', 27 | ]; 28 | 29 | /** 30 | * @inheritDoc 31 | */ 32 | protected function model(): Builder 33 | { 34 | return SysSettingGroupModel::query(); 35 | } 36 | 37 | public function delete(int $id): bool 38 | { 39 | $model = $this->model()->find($id); 40 | if (empty($model)) { 41 | throw new RepositoryException('Model not found'); 42 | } 43 | $count = $model->settings()->count(); 44 | if ($count > 0) { 45 | throw new RepositoryException('当前分组有未删除的设置项!'); 46 | } 47 | return $model->delete(); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /app/Repositories/Sys/SysSettingRepository.php: -------------------------------------------------------------------------------- 1 | 'required', 14 | 'key' => 'required|min:2|max:255', 15 | 'group_id' => 'required|exists:sys_setting_group,id', 16 | 'type' => 'required', 17 | 'describe' => 'sometimes|string', 18 | 'options' => [ 19 | 'sometimes', 20 | 'regex:/^(?:[^=\n]+=[^=\n]+)(?:\n[^=\n]+=[^=\n]+)*$/', 21 | ], 22 | 'props' => [ 23 | 'sometimes', 24 | 'regex:/^(?:[^=\n]+=[^=\n]+)(?:\n[^=\n]+=[^=\n]+)*$/', 25 | ], 26 | 'sort' => 'sometimes|integer', 27 | 'values' => 'sometimes|string', 28 | ]; 29 | 30 | protected array $searchField = [ 'group_id' => '=' ]; 31 | 32 | protected array $messages = [ 33 | 'title.required' => '标题字段是必填的', 34 | 'key.required' => '键名字段是必填的', 35 | 'key.min' => '键名至少需要 :min 个字符', 36 | 'key.max' => '键名不能超过 :max 个字符', 37 | 'group_id.required' => '分组ID是必填的', 38 | 'group_id.exists' => '选择的分组不存在', 39 | 'type.required' => '类型字段是必填的', 40 | 'describe.string' => '描述必须是字符串', 41 | 'options.regex' => '选项格式不正确,应为 key=value 格式,多个用换行分隔', 42 | 'props.regex' => '属性格式不正确,应为 key=value 格式,多个用换行分隔', 43 | 'sort.integer' => '排序必须是整数', 44 | 'values.string' => '值必须是字符串', 45 | ]; 46 | 47 | /** 验证数据 */ 48 | protected function validation(array $data): array 49 | { 50 | $data = parent::validation($data); 51 | $num = $this->model()->where('group_id', $data['group_id'])->where('key', $data['key'])->count(); 52 | if($num > 0 && request()->method() != 'PUT') { 53 | throw new RepositoryException( 54 | 'Validation failed: ' . '该键名在此分组中已存在', 55 | ); 56 | } 57 | return $data; 58 | } 59 | 60 | /** 61 | * @inheritDoc 62 | */ 63 | protected function model(): Builder 64 | { 65 | return SysSettingModel::query(); 66 | } 67 | } -------------------------------------------------------------------------------- /app/Repositories/UserRepository.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:20|unique:user,username', 14 | 'nickname' => 'sometimes|string|max:20', 15 | 'email' => 'sometimes|email|max:50|unique:user,email', 16 | ]; 17 | 18 | protected array $messages = [ 19 | 'username.required' => '用户名不能为空', 20 | 'username.string' => '用户名必须是字符串', 21 | 'username.max' => '用户名不能超过20个字符', 22 | 'username.unique' => '用户名已存在', 23 | 24 | 'nickname.string' => '昵称必须是字符串', 25 | 'nickname.max' => '昵称不能超过20个字符', 26 | 27 | 'email.email' => '邮箱格式不正确', 28 | 'email.max' => '邮箱不能超过50个字符', 29 | 'email.unique' => '邮箱已被注册', 30 | ]; 31 | 32 | protected function model(): Builder 33 | { 34 | return UserModel::query(); 35 | } 36 | } -------------------------------------------------------------------------------- /app/Services/LengthAwarePaginatorService.php: -------------------------------------------------------------------------------- 1 | $this->items(), 17 | 'total' => $this->total(), 18 | 'pageSize' => $this->perPage(), 19 | 'current' => $this->currentPage(), 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Services/Service.php: -------------------------------------------------------------------------------- 1 | 'parent_id', 25 | 'children' => 'children' 26 | ] 27 | ): array 28 | { 29 | $data = []; 30 | foreach ($list as $k => $item) { 31 | if ($item[$fieldNames['pid']] == $parentId) { 32 | $children = $this->getTreeData($list, $key, $item[$key]); 33 | ! empty($children) && $item[$fieldNames['children']] = $children; 34 | $data[] = $item; 35 | unset($list[$k]); 36 | } 37 | } 38 | return $data; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Services/SysFileService.php: -------------------------------------------------------------------------------- 1 | file('file'); 24 | // 验证文件大小 25 | $file_size = $file->getSize(); 26 | if ($file_size > $fileType->maxSize()) { 27 | throw new HttpResponseException(['success' => false, 'msg' => __('system.file.size_limit')]); 28 | } 29 | // 验证扩展名 30 | $file_ext = $file->extension(); 31 | $vel_ext = $fileType->fileExt(); 32 | if (is_array($vel_ext)) { 33 | if (! in_array($file_ext, $vel_ext)) { 34 | throw new HttpResponseException(['success' => false, 'msg' => __('system.file.ext_limit', ['ext' => $fileType->name()])]); 35 | } 36 | }else { 37 | if ($vel_ext !== '*' && $vel_ext !== $file_ext) { 38 | throw new HttpResponseException(['success' => false, 'msg' => __('system.file.ext_limit', ['ext' => $fileType->name()])]); 39 | } 40 | } 41 | $path = $file->store('file', $disk); 42 | if (! $path) { 43 | throw new HttpResponseException(['success' => false, 'msg' => __('system.file.upload_failed')]); 44 | } 45 | if ($type === 'admin') { 46 | $user_id = auth()->id(); 47 | $channel = 10; 48 | } else { 49 | $user_id = auth('user')->id(); 50 | $channel = 20; 51 | } 52 | $data = [ 53 | 'disk' => $disk, // 磁盘 54 | 'group_id' => $group_id, // 文件分组 55 | 'channel' => $channel, // 来源 56 | 'file_name' => $file->getClientOriginalName(), // 文件名 57 | 'file_type' => $fileType->value, // 类型 58 | 'file_path' => $path, // 地址 59 | 'file_size' => $file->getSize(), // 大小 60 | 'file_ext' => $file_ext, // 扩展名 61 | 'uploader_id' => $user_id, // 上传用户ID 62 | ]; 63 | $fileData = SysFileModel::create($data); 64 | return $fileData->toArray(); 65 | } 66 | 67 | /** 68 | * 删除文件 69 | * @param int $fileId 70 | * @param bool $recycle 71 | * @return void 72 | */ 73 | public function delete(int $fileId, bool $recycle = true): void 74 | { 75 | if ($recycle) { 76 | SysFileModel::where('file_id', $fileId)->delete(); 77 | return; 78 | } 79 | $file = SysFileModel::withTrashed()->find($fileId); 80 | SysFileModel::withTrashed()->where('file_id', $fileId)->forceDelete(); 81 | Storage::disk($file->value('disk'))->delete($file->value('file_path')); 82 | } 83 | 84 | /** 85 | * 获取文件 Url 86 | * @param int $fileId 87 | * @return string 88 | */ 89 | public function url(int $fileId): string 90 | { 91 | $file = SysFileModel::find($fileId); 92 | 93 | return Storage::disk($file->value('disk'))->url($file->value('file_path')); 94 | } 95 | 96 | /** 97 | * @param int $fileId 98 | * @param int $expire 99 | * @param array $options 100 | * @return string 101 | */ 102 | public function temporaryUrl(int $fileId, int $expire = 5, array $options = []): string 103 | { 104 | $file = SysFileModel::find($fileId); 105 | return Storage::disk($file->value('disk'))->temporaryUrl( 106 | $file->value('file_path'), 107 | now()->addMinutes($expire), 108 | $options 109 | ); 110 | } 111 | 112 | /** 113 | * 下载文件 114 | * @param int $fileId 115 | * @return StreamedResponse 116 | */ 117 | public function download(int $fileId): StreamedResponse 118 | { 119 | $file = SysFileModel::where('file_id', $fileId)->first(); 120 | return Storage::disk($file->value('disk'))->download($file->value('file_path'), $file->value('file_name')); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/Services/SysSettingService.php: -------------------------------------------------------------------------------- 1 | get(); 21 | $settings = []; 22 | foreach ($groups as $group) { 23 | $settings[$group->key] = []; 24 | foreach ($group->settings as $setting) { 25 | $settings[$group->key][$setting->key] = $setting->values; 26 | } 27 | } 28 | Cache::forever(self::getCacheKey(), $settings); 29 | } 30 | 31 | /** 32 | * 获取设置 33 | * @param string $name 34 | * @param $default 35 | * @return mixed 36 | */ 37 | public static function getSetting(string $name, $default = null): mixed 38 | { 39 | $name = explode('.', $name); 40 | $settings = Cache::get(self::getCacheKey()); 41 | if (count($name) > 1) { 42 | return $settings[$name[0]][$name[1]] ?? $default; 43 | } else { 44 | return $settings[$name[0]] ?? $default; 45 | } 46 | } 47 | 48 | /** 49 | * 获取缓存KEY 50 | * @return string 51 | */ 52 | private static function getCacheKey(): string 53 | { 54 | return env('SETTING_CACHE_KEY', 'settings'); 55 | } 56 | } -------------------------------------------------------------------------------- /app/Services/SysUserDeptService.php: -------------------------------------------------------------------------------- 1 | orderBy('sort', 'desc')->get()->toArray(); 21 | $data = $this->getTreeData($data, 'id'); 22 | 23 | return $this->success(compact('data')); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Services/SysUserRuleService.php: -------------------------------------------------------------------------------- 1 | model = $model; 15 | } 16 | 17 | /** 18 | * 获取权限列表 19 | */ 20 | public function getList(): JsonResponse 21 | { 22 | $rules = $this->model->all(); 23 | return $this->success($rules->toArray()); 24 | } 25 | 26 | /** 27 | * 设置显示状态 28 | */ 29 | public function setShow($ruleID): JsonResponse 30 | { 31 | $model = $this->model->find($ruleID); 32 | if (! $model) { 33 | return $this->error(__('system.data_not_exist')); 34 | } 35 | $model->show = $model->show ? 0 : 1; 36 | $model->save(); 37 | return $this->success(); 38 | } 39 | 40 | /** 41 | * 设置状态 42 | */ 43 | public function setStatus($ruleID): JsonResponse 44 | { 45 | $model = $this->model->find($ruleID); 46 | if (! $model) { 47 | return $this->error(__('system.data_not_exist')); 48 | } 49 | $model->status = $model->status ? 0 : 1; 50 | $model->save(); 51 | return $this->success(); 52 | } 53 | 54 | /** 55 | * 获取父节点 56 | */ 57 | public function getRuleParent(): JsonResponse 58 | { 59 | $data = $this->model 60 | ->whereIn('type', [0, 1]) 61 | ->get(['name', 'id', 'parent_id']) 62 | ->toArray(); 63 | $data = $this->getTreeData($data, 'id'); 64 | return $this->success(compact('data')); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Services/UserService.php: -------------------------------------------------------------------------------- 1 | validate([ 17 | 'user_id' => 'required|exists:user,id', 18 | 'password' => 'required|string|min:6|max:20', 19 | 'rePassword' => 'required|same:password', 20 | ]); 21 | UserModel::find($data['user_id'])->update([ 22 | 'password' => password_hash($data['password'], PASSWORD_DEFAULT), 23 | ]); 24 | return $this->success(__('user.reset_password')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Support/Enum/FileType.php: -------------------------------------------------------------------------------- 1 | __('system.file.image'), 33 | self::AUDIO => __('system.file.audio'), 34 | self::VIDEO => __('system.file.video'), 35 | self::ZIP => __('system.file.zip'), 36 | self::ANNEX => __('system.file.annex'), 37 | }; 38 | } 39 | 40 | /** 41 | * 获取预览地址 42 | */ 43 | public function previewPath(): string 44 | { 45 | return match ($this) { 46 | self::IMAGE => 'static/image.png', 47 | self::AUDIO => 'static/audio.png', 48 | self::VIDEO => 'static/video.png', 49 | self::ZIP => 'static/zip.png', 50 | self::ANNEX => 'static/annex.png', 51 | }; 52 | } 53 | 54 | /** 55 | * 获取最大大小 56 | */ 57 | public function maxSize(): int 58 | { 59 | return match ($this) { 60 | self::IMAGE => 2097152, 61 | self::AUDIO, self::VIDEO, self::ZIP, self::ANNEX => 10485760, 62 | }; 63 | } 64 | 65 | /** 66 | * 文件扩展名 67 | */ 68 | public function fileExt(): array|string 69 | { 70 | return match ($this) { 71 | self::IMAGE => ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'avif', 'webp'], 72 | self::AUDIO => ['mp3', 'wma', 'wav', 'ape', 'flac', 'ogg', 'aac'], 73 | self::VIDEO => ['mp4', 'mov', 'wmv', 'flv', 'avl', 'webm', 'mkv'], 74 | self::ZIP => ['zip', 'rar'], 75 | self::ANNEX => '*', 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/Support/Enum/ShowType.php: -------------------------------------------------------------------------------- 1 | code = $code; 26 | $this->expireMinutes = $expireMinutes; 27 | } 28 | 29 | /** 30 | * Get the message content definition. 31 | */ 32 | public function content(): Content 33 | { 34 | return new Content( 35 | view: 'emails.verification_code', 36 | with: [ 37 | 'code' => $this->code, 38 | 'expireMinutes' => $this->expireMinutes, 39 | ], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Support/Trait/BuildSearch.php: -------------------------------------------------------------------------------- 1 | all(); 17 | // 构建筛选 18 | if (isset($params['filter']) && $params['filter'] != '') { 19 | $filter = json_decode($params['filter'], true); 20 | foreach ($filter as $k => $v) { 21 | if (! $v) { 22 | continue; 23 | } 24 | $model->whereIn($k, $v); 25 | } 26 | } 27 | 28 | // 构建查询 29 | foreach ($this->searchField ?? [] as $key => $op) { 30 | if (isset($params[$key]) && $params[$key] != '') { 31 | if (in_array($op, ['=', '>', '<>', '<', '>=', '<='])) { 32 | $model->where($key, $op, $params[$key]); 33 | 34 | continue; 35 | } 36 | if ($op == 'like') { 37 | $model->where($key, $op, '%'.$params[$key].'%'); 38 | 39 | continue; 40 | } 41 | if ($op == 'afterLike') { 42 | $model->where($key, $op, $params[$key].'%'); 43 | 44 | continue; 45 | } 46 | if ($op == 'beforeLike') { 47 | $model->where($key, $op, '%'.$params[$key]); 48 | 49 | continue; 50 | } 51 | if ($op == 'date') { 52 | $date = date('Y-m-d', strtotime($params[$key])); 53 | $model->whereDate($key, $date); 54 | 55 | continue; 56 | } 57 | if ($op == 'betweenDate') { 58 | if (is_array($params[$key])) { 59 | $start = $params[$key][0]; 60 | $end = $params[$key][1]; 61 | $model->whereDate($key, '>=', $start); 62 | $model->whereDate($key, '<=', $end); 63 | } 64 | } 65 | } 66 | } 67 | 68 | // 快速搜索 69 | if (isset($params['keywordSearch']) && $params['keywordSearch'] != '') { 70 | $quickSearchArr = $this->quickSearchField ?? []; 71 | if (count($quickSearchArr) > 0) { 72 | $model->whereAny( 73 | $quickSearchArr, 74 | 'like', 75 | '%'.str_replace('%', '\%', $params['keywordSearch']).'%' 76 | ); 77 | } 78 | } 79 | 80 | // 构建排序 81 | if (isset($params['sorter']) && $params['sorter']) { 82 | $sorter = json_decode($params['sorter'], true); 83 | if (count($sorter) > 0) { 84 | $column = array_keys($sorter)[0]; 85 | $direction = $sorter[$column] == 'ascend' ? 'asc' : 'desc'; 86 | $model->orderBy($column, $direction); 87 | } 88 | } 89 | 90 | return $model; 91 | } 92 | } -------------------------------------------------------------------------------- /app/Support/helpers.php: -------------------------------------------------------------------------------- 1 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 15 | web: __DIR__.'/../routes/web.php' 16 | ) 17 | ->withMiddleware(function (Middleware $middleware) { 18 | // 全局跨域中间件 19 | $middleware->append(AllowCrossDomainMiddleware::class); 20 | $middleware->alias([ 21 | 'login_log' => LoginLogMiddleware::class, 22 | 'abilities' => CheckAbilities::class, 23 | 'ability' => CheckForAnyAbility::class, 24 | 'authGuard' => AuthGuardMiddleware::class, 25 | ]); 26 | // 未登录响应 27 | $middleware->redirectGuestsTo(function (Request $request) { 28 | return response()->json([ 29 | 'success' => false, 30 | 'msg' => __('user.not_login') 31 | ], 401); 32 | }); 33 | }) 34 | ->withExceptions(function (Exceptions $exceptions) {})->create(); 35 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'Laravel'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Environment 21 | |-------------------------------------------------------------------------- 22 | | 23 | | This value determines the "environment" your application is currently 24 | | running in. This may determine how you prefer to configure various 25 | | services the application utilizes. Set this in your ".env" file. 26 | | 27 | */ 28 | 29 | 'env' => env('APP_ENV', 'production'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application Debug Mode 34 | |-------------------------------------------------------------------------- 35 | | 36 | | When your application is in debug mode, detailed error messages with 37 | | stack traces will be shown on every error that occurs within your 38 | | application. If disabled, a simple generic error page is shown. 39 | | 40 | */ 41 | 42 | 'debug' => (bool) env('APP_DEBUG', false), 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Application URL 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This URL is used by the console to properly generate URLs when using 50 | | the Artisan command line tool. You should set this to the root of 51 | | the application so that it's available within Artisan commands. 52 | | 53 | */ 54 | 55 | 'url' => env('APP_URL', 'http://localhost'), 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Application Timezone 60 | |-------------------------------------------------------------------------- 61 | | 62 | | Here you may specify the default timezone for your application, which 63 | | will be used by the PHP date and date-time functions. The timezone 64 | | is set to "UTC" by default as it is suitable for most use cases. 65 | | 66 | */ 67 | 68 | 'timezone' => env('APP_TIMEZONE', 'UTC'), 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Application Locale Configuration 73 | |-------------------------------------------------------------------------- 74 | | 75 | | The application locale determines the default locale that will be used 76 | | by Laravel's translation / localization methods. This option can be 77 | | set to any locale for which you plan to have translation strings. 78 | | 79 | */ 80 | 81 | 'locale' => env('APP_LOCALE', 'en'), 82 | 83 | 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 84 | 85 | 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Encryption Key 90 | |-------------------------------------------------------------------------- 91 | | 92 | | This key is utilized by Laravel's encryption services and should be set 93 | | to a random, 32 character string to ensure that all encrypted values 94 | | are secure. You should do this prior to deploying the application. 95 | | 96 | */ 97 | 98 | 'cipher' => 'AES-256-CBC', 99 | 100 | 'key' => env('APP_KEY'), 101 | 102 | 'previous_keys' => [ 103 | ...array_filter( 104 | explode(',', env('APP_PREVIOUS_KEYS', '')) 105 | ), 106 | ], 107 | 108 | /* 109 | |-------------------------------------------------------------------------- 110 | | Maintenance Mode Driver 111 | |-------------------------------------------------------------------------- 112 | | 113 | | These configuration options determine the driver used to determine and 114 | | manage Laravel's "maintenance mode" status. The "cache" driver will 115 | | allow maintenance mode to be controlled across multiple machines. 116 | | 117 | | Supported drivers: "file", "cache" 118 | | 119 | */ 120 | 121 | 'maintenance' => [ 122 | 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 123 | 'store' => env('APP_MAINTENANCE_STORE', 'database'), 124 | ], 125 | 126 | ]; 127 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'sys_users', 18 | 'passwords' => 'sys_users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | which utilizes session storage plus the Eloquent user provider. 29 | | 30 | | All authentication guards have a user provider, which defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | system used by the application. Typically, Eloquent is utilized. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'sys_users' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'sys_users', 42 | ], 43 | 'users' => [ 44 | 'driver' => 'session', 45 | 'provider' => 'users', 46 | ], 47 | ], 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | User Providers 52 | |-------------------------------------------------------------------------- 53 | | 54 | | All authentication guards have a user provider, which defines how the 55 | | users are actually retrieved out of your database or other storage 56 | | system used by the application. Typically, Eloquent is utilized. 57 | | 58 | | If you have multiple user tables or models you may configure multiple 59 | | providers to represent the model / table. These providers may then 60 | | be assigned to any extra authentication guards you have defined. 61 | | 62 | | Supported: "database", "eloquent" 63 | | 64 | */ 65 | 66 | 'providers' => [ 67 | 'sys_users' => [ 68 | 'driver' => 'eloquent', 69 | 'model' => \App\Models\Sys\SysUserModel::class 70 | ], 71 | 'users' => [ 72 | 'driver' => 'eloquent', 73 | 'model' => \App\Models\UserModel::class 74 | ], 75 | ], 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Resetting Passwords 80 | |-------------------------------------------------------------------------- 81 | | 82 | | These configuration options specify the behavior of Laravel's password 83 | | reset functionality, including the table utilized for token storage 84 | | and the user provider that is invoked to actually retrieve users. 85 | | 86 | | The expiry time is the number of minutes that each reset token will be 87 | | considered valid. This security feature keeps tokens short-lived so 88 | | they have less time to be guessed. You may change this as needed. 89 | | 90 | | The throttle setting is the number of seconds a user must wait before 91 | | generating more password reset tokens. This prevents the user from 92 | | quickly generating a very large amount of password reset tokens. 93 | | 94 | */ 95 | 96 | 'passwords' => [ 97 | 'sys_users' => [ 98 | 'driver' => 'cache', 99 | 'provider' => 'sys_users', 100 | 'store' => 'sys_passwords', 101 | 'expire' => 60, 102 | 'throttle' => 60, 103 | ], 104 | 'users' => [ 105 | 'driver' => 'cache', 106 | 'provider' => 'users', 107 | 'store' => 'passwords', 108 | 'expire' => 60, 109 | 'throttle' => 60, 110 | ], 111 | ], 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | Password Confirmation Timeout 116 | |-------------------------------------------------------------------------- 117 | | 118 | | Here you may define the amount of seconds before a password confirmation 119 | | window expires and users are asked to re-enter their password via the 120 | | confirmation screen. By default, the timeout lasts for three hours. 121 | | 122 | */ 123 | 124 | 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 125 | 126 | ]; 127 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_STORE', 'redis'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "array", "database", "file", "memcached", 30 | | "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'array' => [ 37 | 'driver' => 'array', 38 | 'serialize' => false, 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'connection' => env('DB_CACHE_CONNECTION'), 44 | 'table' => env('DB_CACHE_TABLE', 'sys_cache'), 45 | 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), 46 | 'lock_table' => env('DB_CACHE_LOCK_TABLE', 'sys_cache_locks'), 47 | ], 48 | 49 | 'file' => [ 50 | 'driver' => 'file', 51 | 'path' => storage_path('framework/cache/data'), 52 | 'lock_path' => storage_path('framework/cache/data'), 53 | ], 54 | 55 | 'memcached' => [ 56 | 'driver' => 'memcached', 57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 58 | 'sasl' => [ 59 | env('MEMCACHED_USERNAME'), 60 | env('MEMCACHED_PASSWORD'), 61 | ], 62 | 'options' => [ 63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 64 | ], 65 | 'servers' => [ 66 | [ 67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 68 | 'port' => env('MEMCACHED_PORT', 11211), 69 | 'weight' => 100, 70 | ], 71 | ], 72 | ], 73 | 74 | 'redis' => [ 75 | 'driver' => 'redis', 76 | 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 77 | 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), 78 | ], 79 | 80 | 'dynamodb' => [ 81 | 'driver' => 'dynamodb', 82 | 'key' => env('AWS_ACCESS_KEY_ID'), 83 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 84 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 85 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 86 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 87 | ], 88 | 89 | 'octane' => [ 90 | 'driver' => 'octane', 91 | ], 92 | 93 | ], 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Cache Key Prefix 98 | |-------------------------------------------------------------------------- 99 | | 100 | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache 101 | | stores, there might be other applications using the same cache. For 102 | | that reason, you may prefix every cache key to avoid collisions. 103 | | 104 | */ 105 | 106 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 107 | 108 | ]; 109 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app/private'), 36 | 'serve' => true, 37 | 'throw' => false, 38 | ], 39 | 40 | 'public' => [ 41 | 'driver' => 'local', 42 | 'root' => storage_path('app/public'), 43 | 'url' => env('APP_URL').'/storage', 44 | 'visibility' => 'public', 45 | 'throw' => false, 46 | ], 47 | 48 | 'telescope' => [ 49 | 'driver' => 'local', 50 | 'root' => storage_path('telescope'), 51 | 'serve' => true, 52 | 'throw' => false, 53 | ], 54 | 55 | 's3' => [ 56 | 'driver' => 's3', 57 | 'key' => env('AWS_ACCESS_KEY_ID'), 58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 59 | 'region' => env('AWS_DEFAULT_REGION'), 60 | 'bucket' => env('AWS_BUCKET'), 61 | 'url' => env('AWS_URL'), 62 | 'endpoint' => env('AWS_ENDPOINT'), 63 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 64 | 'throw' => false, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Symbolic Links 72 | |-------------------------------------------------------------------------- 73 | | 74 | | Here you may configure the symbolic links that will be created when the 75 | | `storage:link` Artisan command is executed. The array keys should be 76 | | the locations of the links and the values should be their targets. 77 | | 78 | */ 79 | 80 | 'links' => [ 81 | public_path('storage') => storage_path('app/public'), 82 | ], 83 | 84 | ]; 85 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'log'), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Mailer Configurations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may configure all of the mailers used by your application plus 25 | | their respective settings. Several examples have been configured for 26 | | you and you are free to add your own as your application requires. 27 | | 28 | | Laravel supports a variety of mail "transport" drivers that can be used 29 | | when delivering an email. You may specify which one you're using for 30 | | your mailers below. You may also add additional mailers if needed. 31 | | 32 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 33 | | "postmark", "resend", "log", "array", 34 | | "failover", "roundrobin" 35 | | 36 | */ 37 | 38 | 'mailers' => [ 39 | 'mailgun' => [ 40 | 'transport' => 'mailgun', 41 | // 'client' => [ 42 | // 'timeout' => 5, 43 | // ], 44 | ], 45 | 46 | 'smtp' => [ 47 | 'transport' => 'smtp', 48 | 'scheme' => env('MAIL_SCHEME'), 49 | 'url' => env('MAIL_URL'), 50 | 'host' => env('MAIL_HOST', '127.0.0.1'), 51 | 'port' => env('MAIL_PORT', 2525), 52 | 'username' => env('MAIL_USERNAME'), 53 | 'password' => env('MAIL_PASSWORD'), 54 | 'timeout' => null, 55 | 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST)), 56 | ], 57 | 58 | 'ses' => [ 59 | 'transport' => 'ses', 60 | ], 61 | 62 | 'postmark' => [ 63 | 'transport' => 'postmark', 64 | // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), 65 | // 'client' => [ 66 | // 'timeout' => 5, 67 | // ], 68 | ], 69 | 70 | 'resend' => [ 71 | 'transport' => 'resend', 72 | ], 73 | 74 | 'sendmail' => [ 75 | 'transport' => 'sendmail', 76 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 77 | ], 78 | 79 | 'log' => [ 80 | 'transport' => 'log', 81 | 'channel' => env('MAIL_LOG_CHANNEL'), 82 | ], 83 | 84 | 'array' => [ 85 | 'transport' => 'array', 86 | ], 87 | 88 | 'failover' => [ 89 | 'transport' => 'failover', 90 | 'mailers' => [ 91 | 'smtp', 92 | 'log', 93 | ], 94 | ], 95 | 96 | 'roundrobin' => [ 97 | 'transport' => 'roundrobin', 98 | 'mailers' => [ 99 | 'ses', 100 | 'postmark', 101 | ], 102 | ], 103 | 104 | ], 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Global "From" Address 109 | |-------------------------------------------------------------------------- 110 | | 111 | | You may wish for all emails sent by your application to be sent from 112 | | the same address. Here you may specify a name and address that is 113 | | used globally for all emails that are sent by your application. 114 | | 115 | */ 116 | 117 | 'from' => [ 118 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 119 | 'name' => env('MAIL_FROM_NAME', 'Example'), 120 | ], 121 | 122 | ]; 123 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'database'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection options for every queue backend 24 | | used by your application. An example configuration is provided for 25 | | each backend supported by Laravel. You're also free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'connection' => env('DB_QUEUE_CONNECTION'), 40 | 'table' => env('DB_QUEUE_TABLE', 'sys_jobs'), 41 | 'queue' => env('DB_QUEUE', 'default'), 42 | 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 43 | 'after_commit' => false, 44 | ], 45 | 46 | 'beanstalkd' => [ 47 | 'driver' => 'beanstalkd', 48 | 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 49 | 'queue' => env('BEANSTALKD_QUEUE', 'default'), 50 | 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 51 | 'block_for' => 0, 52 | 'after_commit' => false, 53 | ], 54 | 55 | 'sqs' => [ 56 | 'driver' => 'sqs', 57 | 'key' => env('AWS_ACCESS_KEY_ID'), 58 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 59 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 60 | 'queue' => env('SQS_QUEUE', 'default'), 61 | 'suffix' => env('SQS_SUFFIX'), 62 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 63 | 'after_commit' => false, 64 | ], 65 | 66 | 'redis' => [ 67 | 'driver' => 'redis', 68 | 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 69 | 'queue' => env('REDIS_QUEUE', 'default'), 70 | 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 71 | 'block_for' => null, 72 | 'after_commit' => false, 73 | ], 74 | 75 | ], 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Job Batching 80 | |-------------------------------------------------------------------------- 81 | | 82 | | The following options configure the database and table that store job 83 | | batching information. These options can be updated to any database 84 | | connection and table which has been defined by your application. 85 | | 86 | */ 87 | 88 | 'batching' => [ 89 | 'database' => env('DB_CONNECTION', 'sqlite'), 90 | 'table' => 'sys_job_batches', 91 | ], 92 | 93 | /* 94 | |-------------------------------------------------------------------------- 95 | | Failed Queue Jobs 96 | |-------------------------------------------------------------------------- 97 | | 98 | | These options configure the behavior of failed queue job logging so you 99 | | can control how and where failed jobs are stored. Laravel ships with 100 | | support for storing failed jobs in a simple file or in a database. 101 | | 102 | | Supported drivers: "database-uuids", "dynamodb", "file", "null" 103 | | 104 | */ 105 | 106 | 'failed' => [ 107 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 108 | 'database' => env('DB_CONNECTION', 'sqlite'), 109 | 'table' => 'sys_failed_jobs', 110 | ], 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort() 22 | ))), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Sanctum Guards 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This array contains the authentication guards that will be checked when 30 | | Sanctum is trying to authenticate a request. If none of these guards 31 | | are able to authenticate the request, Sanctum will use the bearer 32 | | token that's present on an incoming request for authentication. 33 | | 34 | */ 35 | 36 | 'guard' => ['web'], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Expiration Minutes 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This value controls the number of minutes until an issued token will be 44 | | considered expired. This will override any values set in the token's 45 | | "expires_at" attribute, but first-party sessions are not affected. 46 | | 47 | */ 48 | 49 | 'expiration' => null, 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Token Prefix 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Sanctum can prefix new tokens in order to take advantage of numerous 57 | | security scanning initiatives maintained by open source platforms 58 | | that notify developers if they commit tokens into repositories. 59 | | 60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning 61 | | 62 | */ 63 | 64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Sanctum Middleware 69 | |-------------------------------------------------------------------------- 70 | | 71 | | When authenticating your first-party SPA with Sanctum you may need to 72 | | customize some of the middleware Sanctum uses while processing the 73 | | request. You may change the middleware listed below as required. 74 | | 75 | */ 76 | 77 | 'middleware' => [ 78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 79 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, 80 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, 81 | ], 82 | 83 | ]; 84 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'resend' => [ 28 | 'key' => env('RESEND_KEY'), 29 | ], 30 | 31 | 'slack' => [ 32 | 'notifications' => [ 33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 35 | ], 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/swagger.php: -------------------------------------------------------------------------------- 1 | 'XinAdmin 文档', 5 | ]; -------------------------------------------------------------------------------- /config/telescope.php: -------------------------------------------------------------------------------- 1 | env('TELESCOPE_ENABLED', true), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Telescope Domain. 监控可访问的域名,为空时监控所有 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This is the subdomain where Telescope will be accessible from. If the 26 | | setting is null, Telescope will reside under the same domain as the 27 | | application. Otherwise, this value will be used as the subdomain. 28 | | 29 | */ 30 | 31 | 'domain' => env('TELESCOPE_DOMAIN'), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Allowed / Ignored Paths & Commands . 监控地址白名单 36 | |-------------------------------------------------------------------------- 37 | | 38 | | The following array lists the URI paths and Artisan commands that will 39 | | not be watched by Telescope. In addition to this list, some Laravel 40 | | commands. 41 | | 42 | */ 43 | 44 | 'only_paths' => [ 45 | // 'api/*' 46 | ], 47 | 48 | 'ignore_paths' => [ 49 | 'livewire*', 50 | 'nova-api*', 51 | 'pulse*', 52 | ], 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Telescope Watchers 监控器 57 | |-------------------------------------------------------------------------- 58 | | 59 | | The following array lists the "watchers" that will be registered with 60 | | Telescope. The watchers gather the application's profile data when 61 | | a request or task is executed. Feel free to customize this list. 62 | | 63 | */ 64 | 65 | 'watchers' => [ 66 | 67 | \App\Providers\Telescope\Watchers\CacheWatcher::class => [ 68 | 'enabled' => env('TELESCOPE_CACHE_WATCHER', true), 69 | 'hidden' => [], 70 | ], 71 | 72 | \App\Providers\Telescope\Watchers\QueryWatcher::class => [ 73 | 'enabled' => env('TELESCOPE_QUERY_WATCHER', true), 74 | 'ignore_packages' => true, 75 | 'ignore_paths' => [], 76 | 'slow' => 100, 77 | ], 78 | 79 | \App\Providers\Telescope\Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true), 80 | 81 | \App\Providers\Telescope\Watchers\RequestWatcher::class => [ 82 | 'enabled' => env('TELESCOPE_REQUEST_WATCHER', true), 83 | 'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 10), 84 | 'ignore_http_methods' => [ 85 | 'OPTIONS' 86 | ], 87 | 'ignore_status_codes' => [], 88 | 'ignore_http_path' => [ 89 | 'system/watcher*', 90 | '*login', 91 | '*logout', 92 | ], 93 | ], 94 | ], 95 | ]; 96 | -------------------------------------------------------------------------------- /database/database.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xin-admin/xin-admin-laravel/d856147493409ec5f082283d320a31537fc86c43/database/database.sqlite -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000003_create_dict_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name', 50)->comment('字典名称'); 19 | $table->string('code', 50)->comment('字典编码'); 20 | $table->string('describe', 255)->nullable()->comment('字典描述'); 21 | $table->string('type', 10)->default('default')->comment('字典类型'); 22 | $table->timestamps(); 23 | $table->comment('字典表'); 24 | }); 25 | } 26 | if (! Schema::hasTable('sys_dict_item')) { 27 | Schema::create('sys_dict_item', function (Blueprint $table) { 28 | $table->increments('id'); 29 | $table->unsignedBigInteger('dict_id')->comment('字典ID'); 30 | $table->string('label', 50)->comment('字典项名称'); 31 | $table->string('value', 50)->comment('字典项值'); 32 | $table->unsignedInteger('switch')->default(1)->comment('是否启用:0:禁用,1:启用'); 33 | $table->string('status', 10)->default('default')->comment('状态:(default,success,error,processing,warning)'); 34 | $table->timestamps(); 35 | $table->comment('字典项表'); 36 | }); 37 | } 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists('sys_dict'); 46 | Schema::dropIfExists('sys_dict_item'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000004_create_file_table.php: -------------------------------------------------------------------------------- 1 | increments('id')->comment('文件ID'); 18 | $table->integer('group_id')->comment('文件分组ID'); 19 | $table->integer('channel')->comment('上传来源(10:系统用户 20:App用户端)'); 20 | $table->string('disk', 10)->comment('存储方式'); 21 | $table->integer('file_type')->comment('文件类型'); 22 | $table->string('file_name', 255)->comment('文件名称'); 23 | $table->string('file_path', 255)->comment('文件路径'); 24 | $table->integer('file_size')->comment('文件大小(字节)'); 25 | $table->string('file_ext', 20)->comment('文件扩展名'); 26 | $table->integer('uploader_id')->comment('上传者用户ID'); 27 | $table->softDeletes(); 28 | $table->timestamps(); 29 | $table->comment('文件表'); 30 | }); 31 | } 32 | if (! Schema::hasTable('sys_file_group')) { 33 | Schema::create('sys_file_group', function (Blueprint $table) { 34 | $table->increments('id')->comment('文件分组ID'); 35 | $table->string('name', 50)->comment('文件名称'); 36 | $table->integer('sort')->comment('分组排序'); 37 | $table->string('describe', 500)->comment('分组描述'); 38 | $table->timestamps(); 39 | $table->comment('文件分组表'); 40 | }); 41 | } 42 | } 43 | 44 | /** 45 | * Reverse the migrations. 46 | */ 47 | public function down(): void 48 | { 49 | Schema::dropIfExists('sys_file'); 50 | Schema::dropIfExists('sys_file_group'); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000005_create_setting_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('key', 50)->comment('设置项标示'); 18 | $table->string('title', 50)->comment('设置标题'); 19 | $table->string('describe', 500)->default('')->comment('设置项描述'); 20 | $table->string('values', 255)->default('')->comment('设置值'); 21 | $table->string('type', 50)->comment('设置类型'); 22 | $table->string('options', 500)->nullable()->comment('options配置'); 23 | $table->string('props', 500)->nullable()->comment('props配置'); 24 | $table->integer('group_id')->comment('分组ID'); 25 | $table->integer('sort')->comment('排序'); 26 | $table->timestamps(); 27 | $table->comment('系统设置表'); 28 | $table->unique(['key', 'group_id']); 29 | }); 30 | } 31 | if (! Schema::hasTable('sys_setting_group')) { 32 | Schema::create('sys_setting_group', function (Blueprint $table) { 33 | $table->increments('id'); 34 | $table->string('title', 50)->comment('分组标题'); 35 | $table->string('key', 50)->comment('分组KEY'); 36 | $table->string('remark', 255)->nullable()->comment('备注描述'); 37 | $table->timestamps(); 38 | $table->comment('设置分组表'); 39 | }); 40 | } 41 | } 42 | 43 | /** 44 | * Reverse the migrations. 45 | */ 46 | public function down(): void 47 | { 48 | Schema::dropIfExists('sys_setting'); 49 | Schema::dropIfExists('sys_setting_group'); 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000008_create_user_table.php: -------------------------------------------------------------------------------- 1 | increments('id')->comment('用户ID'); 17 | $table->string('username', 20)->unique()->comment('用户名'); 18 | $table->string('password', 100)->comment('密码'); 19 | $table->string('nickname', 20)->default('')->comment('昵称'); 20 | $table->string('email', 50)->default('')->comment('邮箱'); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | $table->rememberToken(); 23 | $table->timestamps(); 24 | $table->comment('APP用户表'); 25 | }); 26 | } 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | */ 32 | public function down(): void 33 | { 34 | Schema::dropIfExists('user'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000009_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('sys_job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('sys_failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('sys_jobs'); 54 | Schema::dropIfExists('sys_job_batches'); 55 | Schema::dropIfExists('sys_failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /database/migrations/2025_01_01_000020_create_other_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamp('expires_at')->nullable(); 24 | $table->timestamps(); 25 | $table->comment('token table'); 26 | }); 27 | } 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('personal_access_tokens'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call([ 15 | SysUserSeeder::class, 16 | SysDataSeeder::class, 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /database/seeders/SysDataSeeder.php: -------------------------------------------------------------------------------- 1 | insert([ 18 | ['id' => 1, 'title' => '网站设置', 'key' => 'web', 'remark' => '网站基础设置', 'created_at' => $date, 'updated_at' => $date] 19 | ]); 20 | DB::table('sys_setting')->insert([ 21 | ['id' => 1, 'group_id' => 1, 'key' => 'title', 'title' => '网站标题', 'describe' => '网站标题,用于展示在网站logo旁边和登录页面以及网页title中', 'values' => 'Xin Admin', 'type' => 'input', 'sort' => 0, 'created_at' => $date, 'updated_at' => $date,], 22 | ['id' => 2, 'group_id' => 1, 'key' => 'logo', 'title' => '网站LOGO', 'describe' => '网站的LOGO,用于标识网站', 'values' => 'https://file.xinadmin.cn/file/favicons.ico', 'type' => 'input', 'sort' => 1, 'created_at' => $date, 'updated_at' => $date,], 23 | ['id' => 3, 'group_id' => 1, 'key' => 'subtitle', 'title' => '网站副标题', 'describe' => '网站副标题,展示在登录页面标题的下面', 'values' => 'Xin Admin 快速开发框架', 'type' => 'input', 'sort' => 2, 'created_at' => $date, 'updated_at' => $date,], 24 | ['id' => 4, 'group_id' => 1, 'key' => 'describe', 'title' => '网站描述', 'describe' => '网站的基本描述', 'values' => '没有描述', 'type' => 'textarea', 'sort' => 2, 'created_at' => $date, 'updated_at' => $date,], 25 | ]); 26 | // 字典初始数据 27 | DB::table('sys_dict')->insert([ 28 | ['id' => 1, 'name' => '性别', 'type' => 'default', 'describe' => '性别字典', 'code' => 'sex', 'created_at' => $date, 'updated_at' => $date,], 29 | ['id' => 2, 'name' => '状态', 'type' => 'default', 'describe' => '状态字典', 'code' => 'status', 'created_at' => $date, 'updated_at' => $date,], 30 | ['id' => 3, 'name' => '权限类型', 'type' => 'tag', 'describe' => '系统权限类型字典', 'code' => 'ruleType', 'created_at' => $date, 'updated_at' => $date,], 31 | ]); 32 | DB::table('sys_dict_item')->insert([ 33 | ['id' => 1, 'dict_id' => 1, 'label' => '男', 'value' => '0', 'switch' => '1', 'status' => 'default', 'created_at' => $date, 'updated_at' => $date], 34 | ['id' => 2, 'dict_id' => 1, 'label' => '女', 'value' => '1', 'switch' => '1', 'status' => 'default', 'created_at' => $date, 'updated_at' => $date], 35 | ['id' => 3, 'dict_id' => 1, 'label' => '其它', 'value' => '2', 'switch' => '1', 'status' => 'default', 'created_at' => $date, 'updated_at' => $date], 36 | ['id' => 4, 'dict_id' => 2, 'label' => '开启', 'value' => '1', 'switch' => '1', 'status' => 'success', 'created_at' => $date, 'updated_at' => $date], 37 | ['id' => 5, 'dict_id' => 2, 'label' => '关闭', 'value' => '0', 'switch' => '1', 'status' => 'error', 'created_at' => $date, 'updated_at' => $date], 38 | ['id' => 6, 'dict_id' => 3, 'label' => '一级菜单', 'value' => '0', 'switch' => '1', 'status' => 'processing', 'created_at' => $date, 'updated_at' => $date], 39 | ['id' => 7, 'dict_id' => 3, 'label' => '子菜单', 'value' => '1', 'switch' => '1', 'status' => 'success', 'created_at' => $date, 'updated_at' => $date], 40 | ['id' => 8, 'dict_id' => 3, 'label' => '按钮/API', 'value' => '2', 'switch' => '1', 'status' => 'default', 'created_at' => $date, 'updated_at' => $date], 41 | ]); 42 | // 文件初始化数据 43 | DB::table('sys_file_group')->insert([ 44 | ['id' => 1, 'name' => '默认分组', 'sort' => 0, 'describe' => '默认分组', 'created_at' => $date, 'updated_at' => $date] 45 | ]); 46 | } 47 | } -------------------------------------------------------------------------------- /lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset.', 17 | 'sent' => 'We have emailed your password reset link.', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /lang/en/system.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'image' => 'image', 12 | 'audio' => 'audio', 13 | 'video' => 'video', 14 | 'zip' => 'zip', 15 | 'annex' => 'annex', 16 | 'size_limit' => 'The file size exceeds the limit', 17 | 'ext_limit' => 'The file extension is not allowed :ext', 18 | 'upload_failed' => 'Upload failed', 19 | ], 20 | 'error' => [ 21 | 'no_permission' => 'Sorry, you do not have this permission at the moment, please contact the administrator', 22 | 'route_not_exist' => 'Route does not exist', 23 | ], 24 | 'data_not_exist' => 'Data does not exist', 25 | ]; 26 | -------------------------------------------------------------------------------- /lang/en/user.php: -------------------------------------------------------------------------------- 1 | 'Please log in first', 17 | 'user_not_exist' => 'User does not exist, please register first', 18 | 'password_error' => 'Password error', 19 | 'admin_login' => 'Admin Login', 20 | 'admin_logout' => 'Admin Logout', 21 | 'login_success' => 'Login Success', 22 | 'login_error' => 'Login Error, Please check your username and password', 23 | 'logout_success' => 'Logout Success', 24 | 'old_password_error' => 'Old password error', 25 | 'user_is_disabled' => 'User is disabled', 26 | 'invalid_token' => 'Invalid Token', 27 | 'refresh_token_expired' => 'Refresh Token Expired, Please login again', 28 | 29 | 'recharge_success' => 'Recharge Success', 30 | 'reset_password' => 'Reset Password Success', 31 | ]; 32 | -------------------------------------------------------------------------------- /lang/zh/auth.php: -------------------------------------------------------------------------------- 1 | '锁提供的凭据不匹配我们的记录。', 17 | 'password' => '输入的密码不正确。', 18 | 'throttle' => '登录尝试次数过多。请在:秒后重试。', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /lang/zh/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /lang/zh/passwords.php: -------------------------------------------------------------------------------- 1 | '您的密码已重置。', 17 | 'sent' => '我们已经通过电子邮件发送了您的密码重置链接。', 18 | 'throttled' => '请稍候再试。', 19 | 'token' => '此密码重置令牌无效。', 20 | 'user' => "我们找不到有那个邮箱地址的用户。", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /lang/zh/system.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'image' => '图片', 12 | 'audio' => '音频', 13 | 'video' => '视频', 14 | 'zip' => '压缩包', 15 | 'annex' => '附件', 16 | 'size_limit' => '文件大小超出限制', 17 | 'ext_limit' => '文件扩展名不允许 :ext', 18 | 'upload_failed' => '上传失败', 19 | ], 20 | 'error' => [ 21 | 'no_permission' => '对不起,你暂时没有该权限,请联系管理员', 22 | 'route_not_exist' => '路由不存在', 23 | ], 24 | 'data_not_exist' => '数据不存在', 25 | ]; 26 | -------------------------------------------------------------------------------- /lang/zh/user.php: -------------------------------------------------------------------------------- 1 | '请先登录', 16 | 'user_not_exist' => '用户不存在,请先注册', 17 | 'password_error' => '密码错误', 18 | 'admin_login' => '管理员登录', 19 | 'admin_logout' => '管理员退出', 20 | 'login_success' => '登录成功', 21 | 'login_error' => '登录失败,用户名或者密码错误!', 22 | 'logout_success' => '退出成功', 23 | 'old_password_error' => '旧密码错误', 24 | 'user_is_disabled' => '用户已被禁用', 25 | 'invalid_token' => '无效的令牌', 26 | 'refresh_token_expired' => '刷新令牌已过期,请重新登录', 27 | 28 | 'recharge_success' => '充值成功', 29 | 'reset_password' => '重置密码成功', 30 | 31 | ]; 32 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xin-admin/xin-admin-laravel/d856147493409ec5f082283d320a31537fc86c43/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/swagger.php: -------------------------------------------------------------------------------- 1 | toJson(); 8 | -------------------------------------------------------------------------------- /resources/views/controller.blade.php: -------------------------------------------------------------------------------- 1 | {!! ' $value) 21 | '{{ $key }}' => '{!! $value !!}', 22 | @endforeach 23 | ]; 24 | 25 | // 快速查询字段 26 | protected array $quickSearchField = [@foreach($quickSearchField as $value) '{!! $value !!}', @endforeach]; 27 | 28 | // 验证 29 | protected array $rule = [ 30 | @foreach($rules as $key => $value) 31 | '{{ $key }}' => '{!! $value !!}', 32 | @endforeach 33 | ]; 34 | 35 | // 错误提醒 36 | protected array $message = [ 37 | @foreach($message as $key => $value) 38 | '{{ $key }}' => '{!! $value !!}', 39 | @endforeach 40 | ]; 41 | 42 | /** 43 | * 若需重写新增、查看、编辑、删除等方法,请复制 @see \app\Http\Controllers\Admin\Controller 中对应的方法至此进行重写 44 | */ 45 | 46 | } 47 | -------------------------------------------------------------------------------- /resources/views/emails/verification_code.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 验证码邮件 6 | 46 | 47 | 48 |
49 | 50 |

您的验证码

51 |
52 | 53 |

您好!

54 | 55 |

您正在尝试进行身份验证,请使用以下验证码完成操作:

56 | 57 |
58 |
{{ $code }}
59 |
60 | 61 |

此验证码将在 {{ $expireMinutes }} 分钟后失效,请尽快使用。

62 | 63 |

如果您没有请求此验证码,请忽略此邮件。

64 | 65 | 68 | 69 | -------------------------------------------------------------------------------- /resources/views/model.blade.php: -------------------------------------------------------------------------------- 1 | {!! ' 2 | 3 | 4 | Redoc 5 | 6 | 7 | 8 | 9 | 10 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/views/view.blade.php: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import XinTable from '@/components/XinTable' 3 | import {ProFormColumnsAndProColumns, TableProps} from '@/components/XinTable/typings'; 4 | import * as verify from '@/utils/format'; 5 | 6 | /** 7 | * Api 接口 8 | */ 9 | const api = '/{{$api}}' 10 | 11 | /** 12 | * 数据类型 13 | */ 14 | interface Data { 15 | [key: string] : any 16 | } 17 | 18 | /** 19 | * 表格渲染 20 | */ 21 | const {{$name}}: React.FC = () => { 22 | 23 | {!! 'const columns: ProFormColumnsAndProColumns[] =' !!} 24 | {!! $columns !!} 25 | 26 | {!! 'const tableConfig: TableProps =' !!} 27 | {!! $table_config !!} 28 | 29 | {!! 'return {...tableConfig}/>' !!} 30 | 31 | } 32 | 33 | export default {{$name}} 34 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestAutoBind.php: -------------------------------------------------------------------------------- 1 | getBoundClasses()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | --------------------------------------------------------------------------------