醉梦人间三千年 7 mesi fa
parent
commit
e03d0381ee
34 ha cambiato i file con 1376 aggiunte e 81 eliminazioni
  1. 3 5
      .env
  2. 11 0
      app/Enums/Common/Status.php
  3. 23 0
      app/Enums/System/Login/LogType.php
  4. 20 0
      app/Enums/System/Role/DataScope.php
  5. 14 9
      app/Exceptions/Handler.php
  6. 3 1
      app/Http/Controllers/Backend/Server/Controller.php
  7. 129 0
      app/Http/Controllers/Backend/Server/System/AuthController.php
  8. 38 0
      app/Http/Controllers/Backend/Server/System/CaptchaController.php
  9. 49 0
      app/Http/Controllers/Backend/Server/System/DictDataController.php
  10. 31 0
      app/Http/Controllers/Backend/Server/System/DictTypeController.php
  11. 44 9
      app/Http/Controllers/Backend/Server/System/MenuController.php
  12. 19 0
      app/Http/Controllers/Backend/Server/System/NotifyMessageController.php
  13. 78 0
      app/Http/Controllers/Backend/Server/System/PermissionController.php
  14. 7 6
      app/Http/Controllers/Controller.php
  15. 96 0
      app/Http/Requests/Backend/Server/System/AuthRequest.php
  16. 29 13
      app/Http/Requests/Backend/Server/System/MenuRequest.php
  17. 24 0
      app/Http/Services/Backend/Server/System/AdminUserService.php
  18. 98 0
      app/Http/Services/Backend/Server/System/AuthService.php
  19. 31 0
      app/Http/Services/Backend/Server/System/Captcha/AbstractCaptchaService.php
  20. 43 0
      app/Http/Services/Backend/Server/System/Captcha/BlockPuzzleService.php
  21. 35 0
      app/Http/Services/Backend/Server/System/CaptchaService.php
  22. 28 0
      app/Http/Services/Backend/Server/System/DictDataService.php
  23. 25 0
      app/Http/Services/Backend/Server/System/DictTypeService.php
  24. 112 12
      app/Http/Services/Backend/Server/System/MenuService.php
  25. 71 0
      app/Http/Services/Backend/Server/System/PermissionService.php
  26. 132 0
      app/Http/Services/Backend/Server/System/RoleService.php
  27. 9 3
      app/Http/Services/Service.php
  28. 22 0
      app/Models/System/DictData.php
  29. 20 0
      app/Models/System/DictType.php
  30. 85 5
      app/Models/System/Menu.php
  31. 2 0
      bootstrap/app.php
  32. 1 1
      database/migrations/0001_01_01_000000_create_system_users_table.php
  33. 10 7
      database/seeders/DatabaseSeeder.php
  34. 34 10
      routes/web.php

+ 3 - 5
.env

@@ -1,12 +1,10 @@
-APP_NAME=Laravel
+APP_NAME=Massage
 APP_ENV=local
 APP_KEY=base64:tXBuLJEDanur8QpVucLgqSpvCJvjQAA5ngxD/vMHwjM=
 APP_DEBUG=true
 APP_TIMEZONE=UTC
 APP_URL=http://localhost:8000
-FRONTEND_URL=http://localhost:3000
-FRONTEND_URL=http://localhost:3000
-FRONTEND_URL=http://localhost:3000
+FRONTEND_URL=http://127.0.0.1
 
 APP_LOCALE=en
 APP_FALLBACK_LOCALE=en
@@ -39,7 +37,7 @@ BROADCAST_CONNECTION=log
 FILESYSTEM_DISK=local
 QUEUE_CONNECTION=database
 
-CACHE_STORE=database
+CACHE_STORE=redis
 CACHE_PREFIX=
 
 MEMCACHED_HOST=127.0.0.1

+ 11 - 0
app/Enums/Common/Status.php

@@ -0,0 +1,11 @@
+<?php declare(strict_types=1);
+
+namespace App\Enums\Common;
+
+use BenSampo\Enum\Enum;
+
+final class Status extends Enum
+{
+    const ENABLE = 0; // 开启
+    const DISABLE = 1; // 关闭
+}

+ 23 - 0
app/Enums/System/Login/LogType.php

@@ -0,0 +1,23 @@
+<?php declare(strict_types=1);
+
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/19 01:05
+ */
+
+namespace App\Enums\System\Login;
+
+use BenSampo\Enum\Enum;
+
+final class LogType extends Enum
+{
+    const LOGIN_USERNAME = 100; // 使用账号登录
+    const LOGIN_SOCIAL = 101; // 使用社交登录
+    const LOGIN_MOBILE = 103; // 使用手机登陆
+    const LOGIN_SMS = 104; // 使用短信登陆
+
+    const LOGOUT_SELF = 200;  // 自己主动登出
+    const LOGOUT_DELETE = 202; // 强制退出
+}

+ 20 - 0
app/Enums/System/Role/DataScope.php

@@ -0,0 +1,20 @@
+<?php declare(strict_types=1);
+
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/19 01:05
+ */
+namespace App\Enums\System\Role;
+
+use BenSampo\Enum\Enum;
+
+final class DataScope extends Enum
+{
+    const ALL = 1; // 全部数据权限
+    const DEPT_CUSTOM = 2; // 指定部门数据权限
+    const DEPT_ONLY = 3; // 部门数据权限
+    const DEPT_AND_CHILD = 4; // 部门及以下数据权限
+    const SELF = 5; // 仅本人数据权限
+}

+ 14 - 9
app/Exceptions/Handler.php

@@ -43,19 +43,24 @@ class Handler
         // 表单验证
         if ($e instanceof ValidationException) {
             $status = Response::HTTP_UNPROCESSABLE_ENTITY;
+//            $status = 200;
         }
 
         if ($e instanceof ApiException) {
-//            // 400 错误请求
-//            // 401 未授权
-//            // 403 禁止访问
-//            // 409 数据冲突
-              // 419 CSRF跨域伪造
-//            // 422 验证规则
-//            // 500 服务器内部错误
-            $status = $code;
+            $status = match ($code) {
+                Response::HTTP_UNPROCESSABLE_ENTITY => 200, // 表单验证
+                default => $code,
+            };
+            // 400 错误请求
+            // 401 未授权
+            // 403 禁止访问
+            // 409 数据冲突
+            // 419 CSRF跨域伪造
+            // 422 验证规则
+            // 500 服务器内部错误
+
         }
-        return response()->json(['code' => $status, 'message' => $message], $status);
+        return response()->json(['code' => $code, 'message' => $message], $status);
     }
 
 }

+ 3 - 1
app/Http/Controllers/Backend/Server/Controller.php

@@ -2,7 +2,9 @@
 
 namespace App\Http\Controllers\Backend\Server;
 
-abstract class Controller
+use Illuminate\Routing\Controller as baseController;
+
+abstract class Controller extends baseController
 {
     //
 }

+ 129 - 0
app/Http/Controllers/Backend/Server/System/AuthController.php

@@ -0,0 +1,129 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/23 10:15
+ */
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Exceptions\ApiException;
+use App\Http\Controllers\Controller;
+use App\Http\Requests\Backend\Server\System\AuthRequest;
+use App\Http\Services\Backend\Server\System\AuthService;
+use App\Models\System\Menu;
+use App\Models\System\Role;
+use App\Models\System\User;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Arr;
+use function PHPUnit\Framework\isEmpty;
+
+class AuthController extends Controller
+{
+    /**
+     * @throws ApiException
+     */
+    public function login(AuthRequest $request): JsonResponse
+    {
+        // 处理首页逻辑
+        $res = (new AuthService())->login($request);
+        return self::success($res);
+    }
+
+    public function getPermissionInfo()
+    {
+        $permissionInfo = ['user' => null, 'roles' => [], 'permissions' => [], 'menus' => []];
+
+        // 1.1 获得用户信息
+        $user = request()->user();
+        if (!$user || $user->status !== 0) return self::success($permissionInfo);
+        $permissionInfo['user'] = $user->only(['id', 'nickname', 'avatar']);
+
+        // 1.2 获得角色列表
+        $roles = $user->roles()->where('status', 0)->get();
+        if (empty($roles)) return self::success($permissionInfo);
+        $permissionInfo['roles'] = $roles->pluck('code');
+
+        // 1.3 获得菜单列表
+        $menus = Role::getMenus($roles->pluck('id')->all());
+        if ($menus->isEmpty()) return self::success($permissionInfo);
+
+//        $allPermissions = $user->getAllPermissions();
+//        if (empty($allPermissions)) return self::success($permissionInfo);
+        // 菜单树
+//        $menus = $allPermissions->where('status', 0)->whereIn('type', [1, 2])->whereIn('pivot.role_id', $roles->pluck('id'))->all();
+//        if (empty($menus)) return self::success($permissionInfo);
+        $permissionInfo['menus'] = self::buildMenuTree($menus->whereIn('type', [1, 2])->all());
+        // 权限标识信息
+        dd($user->getAllPermissions());
+        $permissions = $user->getAllPermissions()->where('status', 0)->whereIn('pivot.role_id', $roles->pluck('id'))->whereNotNull('permission')->pluck('permission');
+        $permissionInfo['permissions'] = $permissions;
+
+        return self::success($permissionInfo);
+    }
+
+    protected static function buildMenuTree(array $menuList)
+    {
+        // 移除按钮
+        $removeKeys = [];
+        $menuList = collect($menuList);
+        $menuList->each(function ($value, $key) use (&$removeKeys) {
+            if ($value['type'] === 3) $removeKeys[] = $key;
+        });
+        // 移除指定的项目
+        $menuList = $menuList->forget($removeKeys);
+
+        // 排序,保证菜单的有序性
+        $menuList = $menuList->sortBy('sort');
+
+        // 构建菜单树
+        $menuMap = collect([]);
+
+        $menuList->each(function ($value) use (&$menuMap) {
+            $menuMap[$value['id']] = [
+                "id" => $value['id'],
+                "parentId" => $value['parent_id'],
+                "name" => $value['name'],
+                "path" => $value['path'],
+                "component" => $value['component'],
+                "componentName" => $value['component_name'],
+                "icon" => $value['icon'],
+                "visible" => !!$value['visible'],
+                "keepAlive" => !!$value['keep_alive'],
+                "alwaysShow" => !!$value['always_show'],
+                "children" => null
+            ];
+        });
+
+        $menuMap->filter(function ($node) {
+            if ($node['parentId'] !== 0) {
+                return true;
+            }
+            return false;
+        })->each(function ($childNode) use (&$menuMap) {
+            // 获得父节点
+            $isExistKey = $menuMap->has($childNode['parentId']);
+            if (!$isExistKey) {
+//                LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
+//                 childNode.getId(), childNode.getParentId());
+                return;
+            }
+
+            $parentNode = $menuMap[$childNode['parentId']];
+
+            // 将自己添加到父节点中
+            if (empty($parentNode['children'])) {
+                $parentNode['children'] = [];
+            }
+            $parentNode['children'][] = $childNode;
+            $menuMap[$childNode['parentId']] = $parentNode;
+        });
+
+        // 获得到所有的根节点
+        return array_values($menuMap->filter(function ($node) {
+            if ($node['parentId'] === 0) return true;
+            return false;
+        })->toArray());
+    }
+}

+ 38 - 0
app/Http/Controllers/Backend/Server/System/CaptchaController.php

@@ -0,0 +1,38 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/22 17:19
+ */
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\Request;
+use App\Http\Services\Backend\Server\System\CaptchaService;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Arr;
+use function PHPUnit\Framework\assertTrue;
+
+class CaptchaController extends Controller
+{
+    public function index(Request $request): JsonResponse
+    {
+        assertTrue($request->server('REMOTE_ADDR') != null);
+        // 获取验证码
+        $params = $request->all();
+        $browserInfo = $this->getRemoteId($request);
+        Arr::pull($params, 'browserInfo', $browserInfo);
+        $res = (new CaptchaService())->get($params);
+        return self::success($res);
+    }
+
+    protected function getRemoteId(Request $request)
+    {
+        $ip = $request->getClientIp();
+        $ua = $request->header('User-Agent');
+        (empty($ip)) && ($ip = $_SERVER['REMOTE_ADDR']);
+        return $ip + $ua;
+    }
+}

+ 49 - 0
app/Http/Controllers/Backend/Server/System/DictDataController.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/26 11:48
+ */
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\Request;
+use App\Http\Services\Backend\Server\System\DictDataService;
+use App\Http\Services\Backend\Server\System\DictTypeService;
+use App\Models\System\DictData;
+use App\Models\System\DictType;
+use Illuminate\Http\JsonResponse;
+
+class DictDataController extends Controller
+{
+
+    private DictDataService $dictDataService;
+
+    public function __construct(DictDataService $dictDataService)
+    {
+        $this->dictDataService = $dictDataService;
+    }
+
+    public function index(Request $request)
+    {
+        $dictType = $request->get('dict_type');
+        $dictDataPage = DictData::query()->where('dict_type', $dictType)->where('status', 0)->paginate(10);
+        return self::success(['list' => $dictDataPage->items(), 'total' => $dictDataPage->total()]);
+    }
+
+    public function store(Request $request)
+    {
+        $params = $request->all();
+        $res = $this->dictDataService->createDictData($params);
+        return self::success($res);
+    }
+
+    public function simpleList()
+    {
+        $select = ["dict_type as dictType", "value", "label", "color_type as colorType", "css_class as cssClass"];
+        $dictData = DictData::query()->where('status', 0)->select($select)->get();
+        return self::success($dictData->toArray());
+    }
+}

+ 31 - 0
app/Http/Controllers/Backend/Server/System/DictTypeController.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/26 11:48
+ */
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Http\Controllers\Controller;
+use App\Http\Requests\Request;
+use App\Http\Services\Backend\Server\System\DictTypeService;
+use App\Models\System\DictType;
+use Illuminate\Http\JsonResponse;
+
+class DictTypeController extends Controller
+{
+    public function index(Request $request)
+    {
+        $dictTypePage = DictType::query()->paginate(10);
+        return self::success(['list' => $dictTypePage->items(),'total' => $dictTypePage->total()]);
+    }
+
+    public function store(Request $request)
+    {
+        $params = $request->all();
+        $res = (new DictTypeService())->createDictType($params);
+        return self::success($res);
+    }
+}

+ 44 - 9
app/Http/Controllers/Backend/Server/System/MenuController.php

@@ -10,20 +10,40 @@ namespace App\Http\Controllers\Backend\Server\System;
 
 use App\Exceptions\ApiException;
 use App\Http\Controllers\Controller;
+use App\Http\Requests\Backend\Server\System\MenuRequest;
 use App\Http\Services\Backend\Server\System\MenuService;
+use Illuminate\Http\Client\Response;
+use Illuminate\Http\JsonResponse;
 use Illuminate\Http\Request;
 
+
+
 class MenuController extends Controller
 {
-    public function index()
+    private MenuService $menuService;
+
+    function __construct(MenuService $menuService)
+    {
+        $this->menuService = $menuService;
+        $this->middleware('permission:test|system-menu-query|system-menu-create|system-menu-update|system-menu-delete', ['only' => ['index', 'show']]);
+//        $this->middleware('permission:system:menu:create', ['only' => ['create', 'store']]);
+//        $this->middleware('permission:system:menu:update', ['only' => ['edit', 'update']]);
+//        $this->middleware('permission:system:menu:delete', ['only' => ['destroy']]);
+    }
+
+    public function index(Request $request): JsonResponse
     {
         // 处理首页逻辑
-        return 'index';
+        $params = $request->all();
+        $res = $this->menuService->getMenuList($params);
+        return self::success($res);
     }
 
     public function show($id)
     {
         // 处理显示单个用户的逻辑
+        $res = $this->menuService->getMenu($id);
+        return self::success($res);
     }
 
     public function create()
@@ -34,11 +54,10 @@ class MenuController extends Controller
     /**
      * @throws ApiException
      */
-    public function store(Request $request)
+    public function store(MenuRequest $request): JsonResponse
     {
-        $params = $request->all();
-        $res = (new MenuService())->createMenu($params);
-
+        $params = $request->safe()->all();
+        $res = $this->menuService->createMenu($params);
         return self::success($res);
     }
 
@@ -47,13 +66,29 @@ class MenuController extends Controller
         // 显示编辑用户的表单
     }
 
-    public function update(Request $request, $id)
+    /**
+     * @throws ApiException
+     */
+    public function update(MenuRequest $request, int $id): JsonResponse
     {
-        // 更新用户逻辑
+        $params = [...$request->safe()->all(), 'id' => $id];
+        $this->menuService->updateMenu($params);
+        return self::success(true);
     }
 
-    public function destroy($id)
+    /**
+     * @throws ApiException
+     */
+    public function destroy($id): JsonResponse
     {
         // 删除用户逻辑
+        $res = $this->menuService->deleteMenu($id);
+        return self::success($res);
+    }
+
+    public function simpleList()
+    {
+        $res = (new MenuService())->getSimpleMenuList();
+        return self::success($res);
     }
 }

+ 19 - 0
app/Http/Controllers/Backend/Server/System/NotifyMessageController.php

@@ -0,0 +1,19 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/26 11:54
+ */
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Http\Controllers\Controller;
+
+class NotifyMessageController extends Controller
+{
+    public function getUnreadCount()
+    {
+        return self::success(0);
+    }
+}

+ 78 - 0
app/Http/Controllers/Backend/Server/System/PermissionController.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Http\Controllers\Backend\Server\System;
+
+use App\Exceptions\ApiException;
+use App\Http\Controllers\Controller;
+use App\Http\Services\Backend\Server\System\PermissionService;
+use App\Http\Services\Backend\Server\System\RoleService;
+use App\Models\System\Menu;
+use App\Models\System\Role;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+
+class PermissionController extends Controller
+{
+    private PermissionService $permissionService;
+
+    public function __construct(PermissionService $permissionService)
+    {
+        $this->permissionService = $permissionService;
+    }
+
+    public function index()
+    {
+        // 处理首页逻辑
+        return 'index';
+    }
+
+    public function show($id)
+    {
+        // 处理显示单个用户的逻辑
+        return self::success();
+    }
+
+    public function create()
+    {
+        // 显示创建用户的表单
+    }
+
+    public function store(Request $request)
+    {
+        // 创建分配逻辑
+    }
+
+    public function edit($id)
+    {
+        // 显示编辑用户的表单
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function update(Request $request, $id): JsonResponse
+    {
+        // 更新用户逻辑
+        $params = $request->all();
+        $this->permissionService->assignPermission($params, $id);
+        return self::success(true);
+    }
+
+    public function destroy($id)
+    {
+        // 删除用户逻辑
+    }
+
+    public function getRoleMenus($id)
+    {
+        $res = $this->permissionService->getMenus($id, Role::class);
+        return self::success($res);
+    }
+
+    public function assignRoleMenu(Request $request, $id)
+    {
+        $params = ['permission' => $request->post('menuIds', [])];
+        $this->permissionService->assignRolePermission($params, $id);
+        return self::success(true);
+    }
+}

+ 7 - 6
app/Http/Controllers/Controller.php

@@ -6,22 +6,23 @@ use App\Exceptions\ApiException;
 use \Illuminate\Http\Response;
 use Illuminate\Http\JsonResponse;
 use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
 
-abstract class Controller
+abstract class Controller extends \Illuminate\Routing\Controller
 {
-    public static function success($result = null, $message = null, $code = 0): Response|JsonResponse
+    public static function success($result = null, $message = null, $code = 0): JsonResponse
     {
-        if (is_null($result) && is_null($message)) return response()->noContent();
+        if (is_null($result) && is_null($message)) return response()->json();
         $data = ['code' => $code];
-        !is_null($result) && Arr::add($data, 'result', $result);
-        !is_null($message) && Arr::add($data, 'message', $message);
+        !is_null($result) && ($data = Arr::add($data, 'data', $result));
+        !is_null($message) && ($data = Arr::add($data, 'msg', $message));
         return response()->json($data);
     }
 
     public function fail($message = null, $code = -1): JsonResponse
     {
         $data = ['code' => $code];
-        !is_null($message) && Arr::add($data, 'message', $message);
+        !is_null($message) && ($data = Arr::add($data, 'msg', $message));
         return response()->json($data);
     }
 

+ 96 - 0
app/Http/Requests/Backend/Server/System/AuthRequest.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/23 12:24
+ */
+
+namespace App\Http\Requests\Backend\Server\System;
+
+use Illuminate\Auth\Events\Lockout;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\RateLimiter;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Support\Str;
+use Illuminate\Validation\ValidationException;
+
+class AuthRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     */
+    public function authorize(): bool
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
+     */
+    public function rules(): array
+    {
+        $rules = [];
+        $actionName = last(explode('@', Route::current()->getActionName()));
+        if ($actionName === 'login') {
+            $rules['username'] = ['required', 'string'];
+            $rules['password'] = ['required', 'string'];
+        }
+        return $rules;
+    }
+
+    /**
+     * Attempt to authenticate the request's credentials.
+     *
+     * @throws \Illuminate\Validation\ValidationException
+     */
+    public function authenticate(): void
+    {
+        $this->ensureIsNotRateLimited();
+        $credentials = ['name' => $this->get('username'), 'password' => $this->get('password')];
+        if (!Auth::attempt($credentials, $this->boolean('remember'))) {
+            RateLimiter::hit($this->throttleKey());
+
+            // 账号密码错误
+            throw ValidationException::withMessages([
+                'username' => __('auth.failed'),
+            ]);
+        }
+
+        RateLimiter::clear($this->throttleKey());
+    }
+
+    /**
+     * Ensure the login request is not rate limited.
+     *
+     * @throws \Illuminate\Validation\ValidationException
+     */
+    public function ensureIsNotRateLimited(): void
+    {
+        if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
+            return;
+        }
+
+        event(new Lockout($this));
+
+        $seconds = RateLimiter::availableIn($this->throttleKey());
+
+        throw ValidationException::withMessages([
+            'username' => trans('auth.throttle', [
+                'seconds' => $seconds,
+                'minutes' => ceil($seconds / 60),
+            ]),
+        ]);
+    }
+
+    /**
+     * Get the rate limiting throttle key for the request.
+     */
+    public function throttleKey(): string
+    {
+        return Str::transliterate(Str::lower($this->input('username')) . '|' . $this->ip());
+    }
+}

+ 29 - 13
app/Http/Requests/Backend/Server/System/MenuRequest.php

@@ -9,10 +9,11 @@
 namespace App\Http\Requests\Backend\Server\System;
 
 use App\Http\Requests\Request;
-use App\Models\System\User;
-use Illuminate\Validation\Rules;
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Support\Facades\Route;
+use Illuminate\Validation\Rule;
 
-class MenuRequest extends Request
+class MenuRequest extends FormRequest
 {
     /**
      * Get the validation rules that apply to the request.
@@ -21,24 +22,39 @@ class MenuRequest extends Request
      */
     public function rules(): array
     {
-        return [
-            'name' => ['required', 'string', 'max:50'],
-            'permission' => ['string', 'max:100'],
-            'type' => ['required', 'max:100'],
-//            'email' => ['string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
-            'password' => ['required', 'confirmed', Rules\Password::defaults()],
+        $rules = [
+            'name' => ['bail', 'required', 'string', 'max:50'],
+            'permission' => ['bail', 'nullable', 'string', 'max:100'],
+            'type' => ['bail', 'required', 'integer', Rule::in([1, 2, 3])],
+            'sort' => ['bail', 'integer'],
+            'parentId' => ['bail', 'integer'],
+            'path' => ['bail', 'nullable', 'string', 'max:200'],
+            'icon' => ['bail', 'nullable', 'string', 'max:100'],
+            'component' => ['bail', 'nullable', 'string', 'max:255'],
+            'componentName' => ['bail', 'nullable', 'string', 'max:255'],
+            'status' => ['bail', 'integer'],
+            'visible' => ['bail', 'bool'],
+            'keepAlive' => ['bail', 'bool'],
+            'alwaysShow' => ['bail', 'bool'],
+            //uniquewith-validator
+            //Rule::unique('users')->ignore($user->id),
 //            unique:table,column,except,idColumn
 //            Rule::unique('facilities', 'name')->ignoreModel(Facility::find($this->request->get('id')))
         ];
+        $actionName = last(explode('@', Route::current()->getActionName()));
+        if ($actionName === 'store') {
+//            $rules['name'] = 'required|unique:roles,name';
+        }
+        return $rules;
     }
 
     public function messages(): array
     {
         return [
-            'name.required' => '请输入用户账号!',
-            'name.unique' => '用户账号已存在!',
-            'password.required' => '请输入用户密码!',
-            'password.confirmed' => '两次输入密码不一致!',
+            'name.required' => '请输入菜单名称!',
+            'type.required' => '请选择菜单类型!',
+            'type.in' => '菜单类型错误!',
+            '*' => '1参数错误!'
         ];
     }
 }

+ 24 - 0
app/Http/Services/Backend/Server/System/AdminUserService.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/23 10:20
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Models\System\User;
+
+class AdminUserService
+{
+    public static function getUserByUsername($username)
+    {
+        return User::query()->where('username', $username)->first();
+    }
+
+    public static function isPasswordMatch($password, $matchPassword)
+    {
+
+    }
+}

+ 98 - 0
app/Http/Services/Backend/Server/System/AuthService.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/23 10:16
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Enums\Common\Status;
+use App\Enums\System\Login\LogType;
+use App\Exceptions\ApiException;
+use App\Http\Services\Service;
+use App\Models\System\User;
+use BadMethodCallException;
+use Exception;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Hash;
+use Illuminate\Support\Str;
+use Illuminate\Validation\ValidationException;
+use Symfony\Component\HttpFoundation\Response;
+
+class AuthService extends Service
+{
+    /**
+     * @throws ApiException
+     */
+    public function login($request)
+    {
+        // 校验验证码
+        // validateCaptcha($data)
+//        $verifyCodeResult = (new SmsService())->verifyCode($mobile, intval($code));
+//        if(!$verifyCodeResult) return $this->fail('验证码错误!', 400);
+
+        // 使用账号密码,进行登录
+        $user = $this->authenticate($request);;
+
+        // 创建 Token 令牌,记录登录日志
+        return $this->createTokenAfterLoginSuccess($user, LogType::LOGIN_USERNAME);
+    }
+
+    /**
+     * @throws ApiException
+     */
+    protected function authenticate($request)
+    {
+        $request->authenticate();
+
+        // 校验是否禁用
+        $user = $request->user();
+
+        if ($user->status === Status::DISABLE) {
+            //createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);
+            $this->error('AUTH_LOGIN_USER_DISABLED');
+        }
+        return $user;
+    }
+
+    /**
+     * @throws ApiException
+     */
+    protected function createTokenAfterLoginSuccess($user, $logType)
+    {
+//        $password = Hash::make($user['password']);
+//        $IsExistUser = User::query()->where('username',$user['name'])->where('password', $password)->exists();
+//        if(!$IsExistUser){
+//            //日志
+//            //登录次数限制
+//            $this->error('账号密码错误', 401);
+//        }
+        // 清理token历史
+        $user->tokens()->where('tokenable_type', $user::class)->where('name', $user->name)->delete();
+        $tokenResult = $user->createToken($user->name);
+        $token = $tokenResult->plainTextToken;
+        !$token && $this->error('授权错误', 401);
+        // 设置refreshToken
+//        $token->refreshToken = Str::random(40);
+//        $token->save();
+//        $tokenResult->last_used_at = time();
+//        $tokenResult->save();
+        $user->last_activity_at = time();
+        $user->ip_address = request()->getClientIp();
+        $user->save();
+
+//        id: number // 编号
+//  accessToken: string // 访问令牌
+//  refreshToken: string // 刷新令牌
+//  userId: number // 用户编号
+//  userType: number //用户类型
+//  clientId: string //客户端编号
+//  expiresTime: number //过期时间
+
+
+
+        return ['userId' => $user->id, 'userType' => 1, 'accessToken' => $token,'refreshToken' => $token];
+    }
+}

+ 31 - 0
app/Http/Services/Backend/Server/System/Captcha/AbstractCaptchaService.php

@@ -0,0 +1,31 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/22 18:48
+ */
+
+namespace App\Http\Services\Backend\Server\System\Captcha;
+
+use Illuminate\Support\Arr;
+
+class AbstractCaptchaService
+{
+
+    public function get($params)
+    {
+//        if ($this->limitHandler !== null) {
+//            Arr::pull($params, 'clientUid', $this->getValidateClientId($params));
+
+//            return $this->limitHandler->validateGet($captchaVO);
+//        } else {
+//            return null;
+//        }
+    }
+
+    protected function getValidateClientId($params): string
+    {
+        return md5($params['browserInfo']);
+    }
+}

+ 43 - 0
app/Http/Services/Backend/Server/System/Captcha/BlockPuzzleService.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/22 18:43
+ */
+
+namespace App\Http\Services\Backend\Server\System\Captcha;
+
+class BlockPuzzleService
+{
+//    public function get($params) {
+//        ResponseModel r = super.get(captchaVO);
+//        if (!this.validatedReq(r)) {
+//            return r;
+//        }
+
+//else {
+//    BufferedImage originalImage = ImageUtils . getOriginal();
+//            if (null == originalImage) {
+//                this . logger . error("滑动底图未初始化成功,请检查路径");
+//                return ResponseModel . errorMsg(RepCodeEnum . API_CAPTCHA_BASEMAP_NULL);
+//            } else {
+//                Graphics backgroundGraphics = originalImage . getGraphics();
+//                int width = originalImage . getWidth();
+//                int height = originalImage . getHeight();
+//                backgroundGraphics . setFont(this . waterMarkFont);
+//                backgroundGraphics . setColor(Color . white);
+//                backgroundGraphics . drawString(waterMark, width - getEnOrChLength(waterMark), height - HAN_ZI_SIZE / 2 + 7);
+//                String jigsawImageBase64 = ImageUtils . getSlidingBlock();
+//                BufferedImage jigsawImage = ImageUtils . getBase64StrToImage(jigsawImageBase64);
+//                if (null == jigsawImage) {
+//                    this . logger . error("滑动底图未初始化成功,请检查路径");
+//                    return ResponseModel . errorMsg(RepCodeEnum . API_CAPTCHA_BASEMAP_NULL);
+//                } else {
+//                    CaptchaVO captcha = this . pictureTemplatesCut(originalImage, jigsawImage, jigsawImageBase64);
+//                    return captcha != null && !StringUtils . isBlank(captcha . getJigsawImageBase64()) && !StringUtils . isBlank(captcha . getOriginalImageBase64()) ? ResponseModel . successData(captcha) : ResponseModel . errorMsg(RepCodeEnum . API_CAPTCHA_ERROR);
+//                }
+//            }
+//        }
+//}
+}

+ 35 - 0
app/Http/Services/Backend/Server/System/CaptchaService.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/22 18:17
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Exceptions\ApiException;
+use App\Http\Services\Backend\Server\System\Captcha\BlockPuzzleService;
+use App\Http\Services\Service;
+use Symfony\Component\HttpFoundation\Response;
+
+class CaptchaService extends Service
+{
+    /**
+     * @throws ApiException
+     */
+    public function get($params)
+    {
+        is_null($params) && self::error('MENU_EXISTS_CHILDREN', Response::HTTP_UNPROCESSABLE_ENTITY);
+        empty($params['captchaType']) && self::error('MENU_EXISTS_CHILDREN', Response::HTTP_UNPROCESSABLE_ENTITY);
+        $this->getService($params['captchaType'])->get($params);
+    }
+
+    protected function getService(String $captchaType) {
+//        return (CaptchaService)CaptchaServiceFactory.instances.get(captchaType);
+        return match ($captchaType) {
+            'blockPuzzle' => new BlockPuzzleService()
+        };
+    }
+
+}

+ 28 - 0
app/Http/Services/Backend/Server/System/DictDataService.php

@@ -0,0 +1,28 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/27 10:52
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Http\Services\Service;
+use App\Models\System\DictData;
+use App\Models\System\DictType;
+
+class DictDataService extends Service
+{
+    public function createDictData($data)
+    {
+        // 校验字典类型有效
+//        validateDictTypeExists(createReqVO.getDictType());
+        // 校验字典数据的值的唯一性
+//        validateDictDataValueUnique(null, createReqVO.getDictType(), createReqVO.getValue());
+
+        // 插入字典类型
+        $dictData = self::toModel($data, DictData::class);
+        return $dictData->create($dictData->getAttributes())->id;
+    }
+}

+ 25 - 0
app/Http/Services/Backend/Server/System/DictTypeService.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/27 10:52
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Http\Services\Service;
+use App\Models\System\DictType;
+
+class DictTypeService extends Service
+{
+    public function createDictType($data)
+    {
+        // 校验字典类型的名字的唯一性
+        // 校验字典类型的类型的唯一性
+
+        // 插入字典类型
+        $menu = self::toModel($data, DictType::class);
+        return $menu->create($menu->getAttributes())->id;
+    }
+}

+ 112 - 12
app/Http/Services/Backend/Server/System/MenuService.php

@@ -12,6 +12,8 @@ use App\Enums\System\MenuType;
 use App\Exceptions\ApiException;
 use App\Http\Services\Service;
 use App\Models\System\Menu;
+use Illuminate\Support\Arr;
+use Symfony\Component\HttpFoundation\Response;
 
 class MenuService extends Service
 {
@@ -19,17 +21,91 @@ class MenuService extends Service
     /**
      * @throws ApiException
      */
-    public function createMenu($params)
+    public function createMenu($data)
     {
         // 校验父菜单存在
-        self::validateParentMenu($params['parent_id'], null);
+        self::validateParentMenu($data['parentId'], null);
         // 校验菜单(自己)
-        self::validateMenu($params['parent_id'], $params['name'], null);
+        self::validateMenu($data['parentId'], $data['name'], null);
         // 插入数据库
+        $menu = self::toModel($data, Menu::class);
+        self::initMenuProperty($menu);
+        return Menu::create($menu->getAttributes())->id;
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function updateMenu($params)
+    {
+        // 校验更新的菜单是否存在
+        !self::isExistMenu($params['id']) && self::error('MENU_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
+        // 校验父菜单存在
+        self::validateParentMenu($params['parentId'], $params['id']);
+        // 校验菜单(自己)
+        self::validateMenu($params['parentId'], $params['name'], $params['id']);
+
+        // 更新到数据库
         $menu = self::toModel($params, Menu::class);
+
         self::initMenuProperty($menu);
-        $menu->save();
-        return $menu->id;
+        $menu->update($menu->getAttributes());
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function deleteMenu($id)
+    {
+        // 校验是否还有子菜单
+        self::validateChildrenMenu($id);
+
+        // 校验删除的菜单是否存在
+        !self::isExistMenu($id) && self::error('MENU_NOT_EXISTS',Response::HTTP_UNPROCESSABLE_ENTITY);
+
+        $menu = self::toModel(['id' => $id], Menu::class);
+
+        // 标记删除
+        return $menu->delete();
+    }
+
+    public function getMenu($id)
+    {
+        $menu = Menu::query();
+        $select = ['id','name','permission','type','sort','parent_id as parentId','path','icon','component','component_name as componentName','status','visible','keep_alive as keepAlive','always_show as alwaysShow'];
+        return $menu->select($select)->find($id)->toArray();
+    }
+    public function getMenuList($params = []): array
+    {
+        $menu = Menu::query();
+        $select = ['id','name','permission','type','sort','parent_id as parentId','path','icon','component','component_name as componentName','status','visible','keep_alive as keepAlive','always_show as alwaysShow'];
+        !empty($params['name']) && $menu->whereLike('name', "%{$params['name']}%");
+        !empty($params['status']) && $menu->where('status', $params['status']);
+        return $menu->orderBy('sort')->select($select)->get()->toArray();
+    }
+
+    public function getSimpleMenuList(): array
+    {
+        $menus = $this->getMenuList(['status' => 0]);
+        $list = self::filterDisableMenus($menus);
+//        list.sort(Comparator . comparing(MenuDO::getSort));
+        return $list;
+    }
+
+    protected static function isExistMenu(int|array $condition): bool
+    {
+        $menu = Menu::query();
+        is_array($condition) && $menu->where($condition);
+        is_numeric($condition) && $menu->where('id', $condition);
+        return $menu->exists();
+    }
+
+    /**
+     * @throws ApiException
+     */
+    protected static function validateChildrenMenu($parentId): void
+    {
+        Menu::query()->where('parent_id', $parentId)->count() && self::error('MENU_EXISTS_CHILDREN', Response::HTTP_UNPROCESSABLE_ENTITY);
     }
 
     /**
@@ -39,14 +115,14 @@ class MenuService extends Service
     {
         if (!$parentId) return;
         // 不能设置自己为父菜单
-        $parentId === $childId && self::error('MENU_PARENT_ERROR');
+        $parentId === $childId && self::error('MENU_PARENT_ERROR',Response::HTTP_UNPROCESSABLE_ENTITY);
 
-        $menu = Menu::query()->find($parentId);
+        $menu = Menu::query()->select('type')->find($parentId);
         // 父菜单不存在
-        !$menu && self::error('MENU_PARENT_NOT_EXISTS');
+        !$menu && self::error('MENU_PARENT_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
 
         // 父菜单必须是目录或者菜单类型
-        $menu->type != MenuType::DIR && $menu->type != MenuType::MENU && self::error('MENU_PARENT_NOT_DIR_OR_MENU');
+        $menu->type != MenuType::DIR && $menu->type != MenuType::MENU && self::error('MENU_PARENT_NOT_DIR_OR_MENU', Response::HTTP_UNPROCESSABLE_ENTITY);
     }
 
     /**
@@ -59,8 +135,8 @@ class MenuService extends Service
         if (is_null($menu)) return;
 
         // 如果 id 为空,说明不用比较是否为相同 id 的菜单
-        !$id && self::error('MENU_NAME_DUPLICATE');
-        $menu->id !== $id && self::error('MENU_NAME_DUPLICATE');
+        !$id && self::error('MENU_NAME_DUPLICATE', Response::HTTP_UNPROCESSABLE_ENTITY);
+        $menu->id !== $id && self::error('MENU_NAME_DUPLICATE', Response::HTTP_UNPROCESSABLE_ENTITY);
     }
 
     /**
@@ -78,7 +154,31 @@ class MenuService extends Service
             $menu->icon = null;
             $menu->path = null;
         }
+        (isset($menu->permission) && empty($menu->permission) || !isset($menu->permission)) && ($menu->permission = null);
+    }
 
-        isset($menu->permission) && ($menu->permission = $menu->permission ?: null);
+    private static function filterDisableMenus($menus): array
+    {
+        if (empty($menus)) return [];
+//        $menuMap = convertMap($menus,'id');
+//        $menuMap = collect($menus)->mapWithKeys(function ($menu) {
+//            return [$menu['id'] => $menu];
+//        })->all();
+
+        // 遍历 menu 菜单,查找不是禁用的菜单,添加到 enabledMenus 结果
+        $enabledMenus = [];
+        $disabledMenuCache = []; // 存下递归搜索过被禁用的菜单,防止重复的搜索
+
+        foreach ($menus as $menu) {
+            if($menu['status'] !== 0) continue;
+//            if($menu['type'] === 3) continue;
+//            if (isMenuDisabled($menu, $menuMap, $disabledMenuCache)) {
+//                continue;
+//            }
+            $enabledMenus[] = ['id' => $menu['id'],'status' => $menu['status'], 'name' => $menu['name'], 'parentId' => $menu['parentId'], 'type' => $menu['type']];
+        }
+
+        return $enabledMenus;
     }
+
 }

+ 71 - 0
app/Http/Services/Backend/Server/System/PermissionService.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/18 22:44
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Enums\System\Role\DataScope;
+use App\Exceptions\ApiException;
+use App\Http\Services\Service;
+use App\Models\System\Menu;
+use App\Models\System\Role;
+use Ramsey\Uuid\Type\Integer;
+use Spatie\Permission\Guard;
+use Spatie\Permission\Models\Permission;
+use Symfony\Component\HttpFoundation\Response;
+
+class PermissionService extends Service
+{
+
+    /**
+     * @throws ApiException
+     */
+    public function assignPermission($params, $id): void
+    {
+        match ($params['type']) {
+            'role' => $this->assignRolePermission($params, $id),
+            'model' => $this->assignModelPermission($params, $id),
+            default => self::error('PARAMS_TYPE_ERROR', Response::HTTP_UNPROCESSABLE_ENTITY)
+        };
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function assignRolePermission($params, $id): void
+    {
+        $params['guard_name'] = $params['guard_name'] ?? Guard::getDefaultName(static::class);
+        // 获取角色
+        $role = Role::findById($id, $params['guard_name']);
+        !$role && self::error('ROLE_NOT_EXISTS', Response::HTTP_UNPROCESSABLE_ENTITY);
+
+        // 获取多个权限
+        $permissions = Permission::query()->whereIn('id', $params['permission'])->get();
+        // 给角色添加多个权限
+        $role->syncPermissions($permissions);
+
+        // 记录操作日志上下文
+        // LogRecordContext.putVariable("role", role);
+        // return role.getId();
+    }
+
+    public function assignModelPermission($params, $id)
+    {
+
+    }
+
+    public function getMenus($id, $class)
+    {
+        $classInstance = app($class);
+        // 角色类型
+        if ($class === Role::class) {
+            $role = $classInstance::findById($id);
+            return $role->getAllPermissions()->pluck('id');
+        }
+        return [];
+    }
+}

+ 132 - 0
app/Http/Services/Backend/Server/System/RoleService.php

@@ -0,0 +1,132 @@
+<?php
+/**
+ * @Name
+ * @Description
+ * @Author 刘学玺
+ * @Date 2024/8/18 22:44
+ */
+
+namespace App\Http\Services\Backend\Server\System;
+
+use App\Enums\System\Role\DataScope;
+use App\Exceptions\ApiException;
+use App\Http\Services\Service;
+use App\Models\System\DictType;
+use App\Models\System\Menu;
+use App\Models\System\Role;
+use Ramsey\Uuid\Type\Integer;
+use Spatie\Permission\Guard;
+use Symfony\Component\HttpFoundation\Response;
+
+class RoleService extends Service
+{
+
+    /**
+     * @throws ApiException
+     */
+    public function createRole($params)
+    {
+        $params['guard_name'] = $params['guard_name'] ?? Guard::getDefaultName(static::class);
+
+        // 1. 校验角色
+        self::validateRoleDuplicate($params['name'], $params['guard_name']);;
+        $params['data_scope'] = DataScope::ALL;
+        // 2. 插入到数据库
+        $role = Role::create($params);
+
+        // 3. 记录操作日志上下文
+
+        return $role->id;
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function updateRole(array $params, int $id): bool|int
+    {
+        $params['guard_name'] = $params['guard_name'] ?? Guard::getDefaultName(static::class);
+        // 校验是否可以更新
+        $role = self::validateRoleForUpdate($id);
+        // 校验角色名称是否重复
+        self::validateRoleDuplicate($params['name'], $params['guard_name'], $id);
+        // 更新到数据库
+        return $role->update([...$params, 'id' => $id]);
+        // 记录操作日志上下文
+
+    }
+
+    public function deleteRole($id): ?bool
+    {
+        // 1. 校验是否可以更新
+        $role = self::validateRoleForUpdate($id);
+
+        // 2.1 标记删除
+        return $role->delete();
+        // 2.2 删除相关数据
+//        permissionService.processRoleDeleted(id);
+
+        // 3. 记录操作日志上下文
+//        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class));
+//        LogRecordContext.putVariable("role", role);
+    }
+
+    public function getRoleList($data): array
+    {
+        $role = Role::query();
+        isset($data['name']) && $role->whereLike('name', "%{$data['name']}%");
+        isset($data['status']) && $role->where('status', $data['status']);
+        $rolePage = $role->orderBy('sort')->paginate($data['pageSize'], ['*'], 'page', $data['pageNo']);
+        return ['list' => $rolePage->items(), 'total' => $rolePage->total()];
+    }
+
+//    public function getRolePermissions($id)
+//    {
+//        $role = Role::findById($id);
+//        return $role->getAllPermissions()->pluck('id');
+//    }
+
+    /**
+     * 校验角色的唯一字段是否重复
+     *
+     * 1. 是否存在相同名字的角色
+     * 2. 是否存在相同编码的角色
+     *
+     * @param String $name 角色名字
+     * @param String $guard_name
+     * @param int $id 角色编号
+     * @throws ApiException
+     */
+    public static function validateRoleDuplicate(string $name, string $guard_name, int $id = 0): void
+    {
+        // 0. 超级管理员,不允许创建
+//    if (RoleCodeEnum.isSuperAdmin(code)) {
+//        throw exception(ROLE_ADMIN_CODE_ERROR, code);
+//    }
+
+        // 1. 该 name 名字被其它角色所使用
+        $role = self::selectByName($name, $guard_name);
+        $role && $role->id !== $id && self::error('ROLE_NAME_DUPLICATE', Response::HTTP_UNPROCESSABLE_ENTITY);
+
+    }
+
+    public static function validateRoleForUpdate($id): Role
+    {
+        $role = Role::findById($id);
+
+//            throw exception(ROLE_NOT_EXISTS);
+
+        // 内置角色,不允许删除
+//        if (RoleTypeEnum.SYSTEM.getType().equals(role.getType())) {
+//            throw exception(ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
+//        }
+        return $role;
+    }
+
+    protected static function selectByName($name, $guard_name = null)
+    {
+        $attributes = ['name' => $name];
+        $attributes['guard_name'] = $guard_name ?? config('auth.defaults.guard');
+        return Role::query()->where($attributes)->first();
+    }
+
+}

+ 9 - 3
app/Http/Services/Service.php

@@ -9,16 +9,22 @@
 namespace App\Http\Services;
 
 use App\Exceptions\ApiException;
+use App\Models\System\Menu;
 use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
 
 class Service
 {
     protected static function toModel($attributes, $class)
     {
         $classInstance = app($class);
-        $oldAttributes = $classInstance->getAttributes();
-        $newAttributes = array_merge($oldAttributes, $attributes);
-        $classInstance->setRawAttributes($newAttributes);
+        isset($attributes['id']) && ($classInstance = $classInstance->find($attributes['id']));
+//        $oldAttributes = $classInstance->getAttributes();
+//        $newAttributes = array_merge($oldAttributes, $attributes);
+//        $classInstance->setRawAttributes($newAttributes);
+        collect($attributes)->each(function ($value, $key) use ($classInstance) {
+            $classInstance->setAttribute(Str::snake($key), $value);
+        });
         return $classInstance;
     }
 

+ 22 - 0
app/Models/System/DictData.php

@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Models\System;
+
+use App\Exceptions\ApiException;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Spatie\Permission\Contracts\Permission as PermissionContract;
+use Spatie\Permission\Exceptions\PermissionAlreadyExists;
+use Spatie\Permission\Guard;
+use Spatie\Permission\Models\Permission;
+use Symfony\Component\HttpFoundation\Response;
+
+class DictData extends Model
+{
+    protected $table = 'system_dict_data';
+    protected $fillable = [];
+    protected $guarded = [];
+
+}

+ 20 - 0
app/Models/System/DictType.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Models\System;
+
+use App\Exceptions\ApiException;
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Spatie\Permission\Contracts\Permission as PermissionContract;
+use Spatie\Permission\Exceptions\PermissionAlreadyExists;
+use Spatie\Permission\Guard;
+use Spatie\Permission\Models\Permission;
+use Symfony\Component\HttpFoundation\Response;
+
+class DictType extends Model
+{
+    protected $table = 'system_dict_type';
+
+}

+ 85 - 5
app/Models/System/Menu.php

@@ -2,27 +2,107 @@
 
 namespace App\Models\System;
 
+use App\Exceptions\ApiException;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\DB;
+use Spatie\Permission\Contracts\Permission as PermissionContract;
 use Spatie\Permission\Exceptions\PermissionAlreadyExists;
 use Spatie\Permission\Guard;
 use Spatie\Permission\Models\Permission;
+use Symfony\Component\HttpFoundation\Response;
 
 class Menu extends Permission
 {
+    protected $attributes = [
+        'title' => 'name'
+//        'name' => 'title',
+//        'permission' => 'name'
+    ];
+
+    public static $snakeAttributes = false;
+
+    protected $appends = ['title'];
+
+    protected $casts = [
+        'visible' => 'bool',
+        'keepAlive' => 'bool',
+        'alwaysShow' => 'bool',
+//        'parent_id' => 'camel'
+    ];
+
+    /**
+     * @throws ApiException
+     */
     public static function create(array $attributes = [])
     {
-        $attributes = [
-            'name' => '系统设置',
-            'type' => 1,
-        ];
         $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class);
         if (isset($attributes['permission']) && $attributes['permission']) {
             $permission = static::getPermission(['permission' => $attributes['permission'], 'guard_name' => $attributes['guard_name']]);
             if ($permission) {
-                throw PermissionAlreadyExists::create($attributes['permission'], $attributes['guard_name']);
+                throw new ApiException(['code' => Response::HTTP_UNPROCESSABLE_ENTITY, 'message' => 'PERMISSION_ALREADY_EXISTS']);
             }
         }
         return static::query()->create($attributes);
     }
+
+    /**
+     * @param array $attributes
+     * @param array $options
+     * @throws ApiException
+     */
+    public function update(array $attributes = [], array $options = []): void
+    {
+        $attributes['guard_name'] = $attributes['guard_name'] ?? Guard::getDefaultName(static::class);
+        if (isset($attributes['permission']) && $attributes['permission']) {
+            $permission = static::getPermission(['permission' => $attributes['permission'], 'guard_name' => $attributes['guard_name']]);
+            if ($permission && $permission->id !== $attributes['id']) {
+                throw new ApiException(['code' => Response::HTTP_UNPROCESSABLE_ENTITY, 'message' => 'PERMISSION_ALREADY_EXISTS']);
+            }
+        }
+        static::query()->where('id', $attributes['id'])->update($attributes, $options);
+        Cache::forget(config('permission.cache.key'));
+    }
+
+    public function setTitleAttribute($value)
+    {
+        $this->attributes['name'] =  $value;
+    }
+
+    public function getTitleAttribute()
+    {
+        return $this->attributes['name'];
+    }
+
+    public function setNameAttribute($value)
+    {
+        $this->attributes['permission'] =  $value;
+    }
+
+    public function getNameAttribute()
+    {
+        return $this->attributes['permission'];
+    }
+
+    public function setComponentNameAttribute($value)
+    {
+        $this->attributes['component_name'] =  $value;
+    }
+
+    public function setVisibleAttribute($value)
+    {
+        $this->attributes['visible'] =  $value ? 1 : 0;
+    }
+
+    public function setAlwaysShowAttribute($value)
+    {
+        $this->attributes['always_show'] =  $value ? 1 : 0;
+    }
+
+    public function setKeepAliveAttribute($value)
+    {
+        $this->attributes['keep_alive'] =  $value ? 1 : 0;
+    }
+
 }

+ 2 - 0
bootstrap/app.php

@@ -20,6 +20,7 @@ return Application::configure(basePath: dirname(__DIR__))
     )
     ->withMiddleware(function (Middleware $middleware) {
         $middleware->use([
+            \Illuminate\Http\Middleware\HandleCors::class,
             \App\Exceptions\Handler::class,
         ]);
 
@@ -29,6 +30,7 @@ return Application::configure(basePath: dirname(__DIR__))
 
         $middleware->alias([
             'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class,
+            'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,
         ]);
 
         //

+ 1 - 1
database/migrations/0001_01_01_000000_create_system_users_table.php

@@ -25,7 +25,7 @@ return new class extends Migration
             $table->string('ip_address', 45)->nullable()->comment('最后登录IP');
             $table->timestamp('last_activity_at')->nullable()->comment('最后登录时间');
             $table->text('remark')->nullable()->comment('备注');
-            $table->tinyInteger('status')->default(1)->comment('账户状态 (1正常 0停用)');
+            $table->tinyInteger('status')->default(0)->comment('账户状态 (1正常 0停用)');
             $table->bigInteger('creator')->nullable()->comment('创建者');
             $table->bigInteger('updater')->nullable()->comment('更新者');
             $table->rememberToken();

+ 10 - 7
database/seeders/DatabaseSeeder.php

@@ -2,10 +2,10 @@
 
 namespace Database\Seeders;
 
-use App\Models\User;
-// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
 use Illuminate\Database\Seeder;
 
+// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
+
 class DatabaseSeeder extends Seeder
 {
     /**
@@ -13,11 +13,14 @@ class DatabaseSeeder extends Seeder
      */
     public function run(): void
     {
-        // User::factory(10)->create();
-
-        User::factory()->create([
-            'name' => 'Test User',
-            'email' => 'test@example.com',
+        $this->call([
+            SystemUsersTableSeeder::class,
+            SystemRolesTableSeeder::class,
+            SystemModelHasRolesTableSeeder::class,
+            SystemMenusTableSeeder::class,
+            SystemPermissionsTableSeeder::class,
+            SystemRoleHasPermissionsTableSeeder::class,
+            // 其他Seeder类...
         ]);
     }
 }

+ 34 - 10
routes/web.php

@@ -1,6 +1,12 @@
 <?php
 
+use App\Http\Controllers\Backend\Server\System\AuthController;
+use App\Http\Controllers\Backend\Server\System\CaptchaController;
+use App\Http\Controllers\Backend\Server\System\DictDataController;
+use App\Http\Controllers\Backend\Server\System\DictTypeController;
 use App\Http\Controllers\Backend\Server\System\MenuController;
+use App\Http\Controllers\Backend\Server\System\NotifyMessageController;
+use App\Http\Controllers\Backend\Server\System\PermissionController;
 use App\Http\Controllers\Backend\Server\System\RoleController;
 use App\Http\Controllers\Backend\Server\System\UserController;
 use Illuminate\Support\Facades\Route;
@@ -15,21 +21,39 @@ Route::prefix('client')->group(function () {
     });
 });
 
-Route::prefix('server')->group(function () {
-    Route::get('/', function () {
-        return ['Laravel' => 'Backend server'];
-    });
+//Route::prefix('server')->group(function () {
 
-    # 注册
+# 注册
 //    Route::post('/register', [RegisteredUserController::class, 'store'])
 //        ->middleware('guest')
 //        ->name('register');
 
-    Route::prefix('system')->group(function () {
-        Route::resource('users', UserController::class);
-        Route::resource('menus', MenuController::class);
-        Route::resource('roles', RoleController::class);
-    });
+Route::prefix('system')->group(function () {
+    Route::post('auth/login', [AuthController::class, 'login']);
+    Route::get('auth/get-permission-info', [AuthController::class, 'getPermissionInfo']);
+
+
+    Route::get('menu/simple-list', [MenuController::class, 'simpleList']);
+
+    Route::get('notify-message/get-unread-count', [NotifyMessageController::class, 'getUnreadCount']);
+
+    Route::resource('users', UserController::class);
+    Route::resource('menu', MenuController::class);
+
+    Route::get('dict-data/simple-list', [DictDataController::class, 'simpleList']);
+    Route::resource('dict-type', DictTypeController::class);
+    Route::resource('dict-data', DictDataController::class);
+
+
+    Route::resource('role', RoleController::class);
+//    Route::get('role/permissions/{id}',[ RoleController::class , 'permissions']);
+    Route::resource('captcha', CaptchaController::class);
+    Route::resource('permissions', PermissionController::class);
+    Route::get('permission/role_menus/{id}',[PermissionController::class,'getRoleMenus'] );
+    Route::post('permission/assign_role_menu/{id}',[PermissionController::class,'assignRoleMenu'] );
+
+
 });
+//});
 
 require __DIR__ . '/auth.php';