├── .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 |
3 |
4 | Xin Admin
5 | 企业级 PHP 全栈快速开发框架
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
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 |
52 |
53 | 您好!
54 |
55 | 您正在尝试进行身份验证,请使用以下验证码完成操作:
56 |
57 |
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 |
--------------------------------------------------------------------------------