Write the Code. Change the World.

9月 28

laravel 中 laravel-permission 对角色权限管理很好用。这里,就用这个玩意吧。突然想到一个额外的问题。项目中使用的是 Laravel Sanctum 来做认证。这样,仅仅是它自己可以使用了。如果想将 node.js 等和这个配合起来认证,就不好搞了。如果是用 jwt 来做,就不会出现这个问题。jwt 的实现都是一样的,各个语言都可以通用。岔开了,这里还是 laravel-permission 吧。

操作一波

文档:

https://spatie.be/docs/laravel-permission/v3/introduction

https://github.com/spatie/laravel-permission#installation

composer require spatie/laravel-permission

# 生成配置文件和迁移文件
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

# 如果之前做过缓存,需要这样处理下
php artisan config:clear

# 生成数据库表
php artisan migrate


可是,默认的迁移还不够接地气。因为我们的路由会展示在菜单中的,而且有的还有 icon,是否显示在菜单中,是否要显示面包屑。这个时候,多几个字段就很有必要了。如果结合 vue elemeui admin
的话,还需要控制一些其他参数,比如 path,redirect,alwaysShow,noCache 等。

https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/essentials/router-and-nav.html

想到这里,那么后端的 laravel-permission 和 前端的 vue element admin 的路由就耦合在一起了。这样有好处也有坏处。个人还是耦合在一起吧,官方不是这样弄的。

如果完全动态生成路由, 前端的角色控制路由就没必要了。直接根据接口生就好了。

还有其他问题。显示和非显示问题,显示的话至少是 get 方式的路由,但并不是所有 get 的路由都显示。权限不仅控制 get,也要控制一切可以访问的路由行为。好像这样又很麻烦,细节越多越麻烦。最终只是想控制路由和角色。貌似,如果对于前端,只关心菜单树,而不用关心 post,delete 等请求。如果权限表中不增加每一个的定义,就需要逻辑上去定义限制了。要不仅仅是前端路由限制,如果人家知道请路由和参数等,就可以模拟提交了。

还有一点,我们都是通过角色去控制权限的,不想直接给用户赋予权限。这样控制起来有意思又方便,还不容易混。

# permissions 表中增加 path, title, icon, hidden, breadcrumb,redirect,activeMenu, component 这几个字段。

为了适应中国国情, roles 中也需要加入 title 字段,来显示中文名称。为了方便生成无底线层级菜单,也需要加上 parent_id 字段,为了方便排序,也可以加上 sort 字段。

然后修改成这样子:

        Schema::create($tableNames['permissions'], function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('guard_name');
            $table->string('path')->comment('对应 vue path');
            $table->string('icon')->nullable()->comment('对应 vue icon');
            $table->string('title')->comment('对应 vue title');
            $table->string('component')->comment('对应 vue component');
            $table->boolean('breadcrumb')->default(1)->comment('对应 vue 是否显示面包屑中');
            $table->boolean('hidden')->default(0)->comment('对应 vue 是否不显示在侧边栏中');
            $table->boolean('noCache')->default(0)->comment('对应 vue 是否使用 keep-alive');
            $table->string('redirect')->nullable()->comment('对应 vue 重定向');
            $table->string('activeMenu')->nullable()->comment('对应 vue 需高亮相对应的侧边栏');
            $table->unsignedBigInteger('parent_id')->default(0);
            $table->unsignedTinyInteger('sort')->default(0)->comment('排序');
            $table->timestamps();
        });

        Schema::create($tableNames['roles'], function (Blueprint $table)                 {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('guard_name');
            $table->string('title')->comment('角色名称');
            $table->timestamps();
        });

其他的不变,然后执行:

php artisan migrate:rollback --step=1

php artisan migrate

建立 seeder 方便搞事情

既然表好了,我们建立一个 seeder,方便我们初始化一些权限模块。这里最多嵌套三级,不要太多了。

# 建立 seeder
php artisan make:seeder PermissionSeeder

# 填充
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Spatie\Permission\PermissionRegistrar;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use App\Models\User;

/**
 * 权限 seeder
 * https://spatie.be/docs/laravel-permission/v3/basic-usage/new-app
 */
class PermissionSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        // Reset cached roles and permissions
        // php artisan cache:forget spatie.permission.cache 清除缓存
        app()[PermissionRegistrar::class]->forgetCachedPermissions();

        $tableNames = config('permission.table_names');
        if (empty($tableNames)) {
            throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
        }

        // 先截断表
        DB::statement('SET FOREIGN_KEY_CHECKS = 0'); // 禁用外键约束
        DB::table($tableNames['role_has_permissions'])->truncate();
        DB::table($tableNames['model_has_roles'])->truncate();
        DB::table($tableNames['model_has_permissions'])->truncate();
        DB::table($tableNames['roles'])->truncate();
        DB::table($tableNames['permissions'])->truncate();
        DB::statement('SET FOREIGN_KEY_CHECKS = 1'); // 启用外键约束

        $permissions = [
            [
                'name'       => 'home',
                'path'       => '/',
                'icon'       => 'dashboard',
                'title'      => '主页',
                'component'  => 'Layout',
                'breadcrumb' => 0,
                'hidden'     => 0,
                'noCache'    => 0,
                'redirect'   => '/dashboard',
                'activeMenu' => '',
                'child' => [
                    [
                        'name'       => 'dashboard',
                        'path'       => 'dashboard',
                        'icon'       => 'dashboard',
                        'title'      => '仪表盘',
                        'component'  => 'dashboard/index',
                        'breadcrumb' => 0,
                        'hidden'     => 0,
                        'noCache'    => 0,
                        'redirect'   => '',
                        'activeMenu' => '',
                        'child' => [

                        ]
                    ]
                ]
            ],
            [
                'name'       => 'system',
                'path'       => '/system',
                'icon'       => 'system',
                'title'      => '系统管理',
                'component'  => 'Layout',
                'breadcrumb' => 0,
                'hidden'     => 0,
                'noCache'    => 0,
                'redirect'   => '/system/permission',
                'activeMenu' => '',
                'child' => [
                    [
                        'name'       => 'permission',
                        'path'       => 'permission',
                        'icon'       => '',
                        'title'      => '权限管理',
                        'component'  => 'system/index',
                        'breadcrumb' => 0,
                        'hidden'     => 0,
                        'noCache'    => 0,
                        'redirect'   => '',
                        'activeMenu' => '',
                        'child' => [
                            [
                                'name'       => 'permission.index',
                                'path'       => 'index',
                                'icon'       => '',
                                'title'      => '权限',
                                'component'  => 'system/permission/index',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/permission',
                            ],
                            [
                                'name'       => 'permission.create',
                                'path'       => 'create',
                                'icon'       => '',
                                'title'      => '添加权限',
                                'component'  => 'system/permission/create',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/permission',
                            ],
                            [
                                'name'       => 'permission.edit',
                                'path'       => 'edit',
                                'icon'       => '',
                                'title'      => '编辑权限',
                                'component'  => 'system/permission/edit',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/permission',
                            ],
                            [
                                'name'       => 'permission.destroy',
                                'path'       => 'destroy',
                                'icon'       => '',
                                'title'      => '删除权限',
                                'component'  => 'system/permission/destroy',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/permission',
                            ]
                        ]
                    ],
                    [
                        'name'       => 'role',
                        'path'       => 'role',
                        'icon'       => '',
                        'title'      => '角色管理',
                        'component'  => 'system/index',
                        'breadcrumb' => 0,
                        'hidden'     => 0,
                        'noCache'    => 0,
                        'redirect'   => '',
                        'activeMenu' => '',
                        'child' => [
                            [
                                'name'       => 'role.index',
                                'path'       => 'index',
                                'icon'       => '',
                                'title'      => '角色',
                                'component'  => 'system/role/index',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/role',
                            ],
                            [
                                'name'       => 'role.create',
                                'path'       => 'create',
                                'icon'       => '',
                                'title'      => '添加角色',
                                'component'  => 'system/role/create',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/role',
                            ],
                            [
                                'name'       => 'role.edit',
                                'path'       => 'edit',
                                'icon'       => '',
                                'title'      => '编辑角色',
                                'component'  => 'system/role/edit',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/role',
                            ],
                            [
                                'name'       => 'role.destroy',
                                'path'       => 'destroy',
                                'icon'       => '',
                                'title'      => '删除角色',
                                'component'  => 'system/role/destroy',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/role',
                            ],
                            [
                                'name'       => 'role.permission',
                                'path'       => 'permission',
                                'icon'       => '',
                                'title'      => '分配权限',
                                'component'  => 'system/role/permission',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/role',
                            ]
                        ]
                    ],
                    [
                        'name'       => 'manager',
                        'path'       => 'manager',
                        'icon'       => '',
                        'title'      => '管理员',
                        'component'  => 'system/index',
                        'breadcrumb' => 0,
                        'hidden'     => 0,
                        'noCache'    => 0,
                        'redirect'   => '',
                        'activeMenu' => '',
                        'child' => [
                            [
                                'name'       => 'manager.index',
                                'path'       => 'index',
                                'icon'       => '',
                                'title'      => '管理',
                                'component'  => 'system/manager/index',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/manager',
                            ],
                            [
                                'name'       => 'manager.create',
                                'path'       => 'create',
                                'icon'       => '',
                                'title'      => '添加管理',
                                'component'  => 'system/manager/create',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/manager',
                            ],
                            [
                                'name'       => 'manager.edit',
                                'path'       => 'edit',
                                'icon'       => '',
                                'title'      => '编辑管理',
                                'component'  => 'system/manager/edit',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/manager',
                            ],
                            [
                                'name'       => 'manager.destroy',
                                'path'       => 'destroy',
                                'icon'       => '',
                                'title'      => '删除管理',
                                'component'  => 'system/manager/destroy',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/manager',
                            ],
                            [
                                'name'       => 'manager.role',
                                'path'       => 'role',
                                'icon'       => '',
                                'title'      => '分配角色',
                                'component'  => 'system/manager/role',
                                'breadcrumb' => 0,
                                'hidden'     => 1,
                                'noCache'    => 0,
                                'redirect'   => '',
                                'activeMenu' => '/system/manager',
                            ]
                        ]
                    ],
                ]
            ]
        ];

        $role = Role::create([
            'name' => 'root',
            'title' => '站长'
        ]);

        foreach ($permissions as $pem1) {
            // 生成一级权限
            $p1 = Permission::create([
                    'name' => $pem1['name'],
                    'path' => $pem1['path'],
                    'icon' => $pem1['icon']??'',
                    'title' => $pem1['title'],
                    'component' => $pem1['component'],
                    'breadcrumb' => $pem1['breadcrumb']??1,
                    'hidden' => $pem1['hidden']??0,
                    'noCache' => $pem1['noCache']??0,
                    'redirect' => $pem1['redirect']??'',
                    'activeMenu' => $pem1['activeMenu']??'',
                ]);

            // 为角色添加权限
            $role->givePermissionTo($p1);
            // 为用户添加权限(用户不直接分配权限,需要通过角色来实现权限控制)
            // $user->givePermissionTo($p1);
            if (isset($pem1['child'])) {
                foreach ($pem1['child'] as $pem2) {
                    //生成二级权限
                    $p2 = Permission::create([
                        'name' => $pem2['name'],
                        'path' => $pem2['path'],
                        'icon' => $pem2['icon']??'',
                        'title' => $pem2['title'],
                        'component' => $pem2['component'],
                        'breadcrumb' => $pem2['breadcrumb']??1,
                        'hidden' => $pem2['hidden']??0,
                        'noCache' => $pem2['noCache']??0,
                        'redirect' => $pem2['redirect']??'',
                        'activeMenu' => $pem2['activeMenu']??'',
                        'parent_id' => $p1->id,
                    ]);
                    // 为角色添加权限
                    $role->givePermissionTo($p2);
                    // 为用户添加权限(用户不直接分配权限,需要通过角色来实现权限控制)
                    // $user->givePermissionTo($p2);
                    if (isset($pem2['child'])) {
                        foreach ($pem2['child'] as $pem3) {
                            //生成三级权限
                            $p3 = Permission::create([
                                'name' => $pem3['name'],
                                'path' => $pem3['path'],
                                'icon' => $pem3['icon']??'',
                                'title' => $pem3['title'],
                                'component' => $pem3['component'],
                                'breadcrumb' => $pem3['breadcrumb']??1,
                                'hidden' => $pem3['hidden']??0,
                                'noCache' => $pem3['noCache']??0,
                                'redirect' => $pem3['redirect']??'',
                                'activeMenu' => $pem3['activeMenu']??'',
                                'parent_id' => $p2->id,
                            ]);
                            // 为角色添加权限
                            $role->givePermissionTo($p3);
                            // 为用户添加权限(用户不直接分配权限,需要通过角色来实现权限控制)
                            // $user->givePermissionTo($p3);
                        }
                    }

                }
            }
        }

        // 如果用户不存在,才创建用户
        $user = User::where('account', 13671638524)->first();
        if (! $user) {
            $phone = 136xxxxxxxx;
            $faker = \Faker\Factory::create('zh_CN');
            $user = User::create([
                'account' => $phone,
                'password' => Hash::make('111111'),
                'nickname' => '七月羽歌',
                'phone' => $phone,
                'gender' => 1,
                'avatar' => $faker->imageUrl,
                'email' => 'lichking_lin86@qq.com',
                'signature' => '虚幻之物对应着冥冥之路。',
                'email_verified_at' => now(),
                'remember_token' => Str::random(10)
            ]);
        }
        //为用户添加角色
        $user->assignRole($role);
    }
}

一级目录 path 一定要带上 / , component 千万不要和将前端的 views 目录也包含进去了。因为前端路由懒加载是需要 views 这个的。

完善

Call to undefined method App\Models\User::assignRole() 处理

App\Models\User 模型处理:

use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    ……
}

再重新迁移填充

php artisan migrate:refresh --seed

增加一个生成路由和权限的方法。

    // 获取用户的菜单
    public function getMenu($permissions, $parent_id = 0) 
    {
        $array = [];
        foreach($permissions as $key => $permission)
        {
            if ($permission['parent_id'] === $parent_id) {
                $permission['children'] = $this->getMenu($permissions, $permission['id']);
                $array[] = $permission;
            }
        }
        return $array;    
    }

    public function test() 
    {
        $user = User::first();

        $user->menus = $this->getMenu($user->getAllPermissions());

        return response()->json($user, 200);
    }

# 有了 user 就可以获取对应用户的 permissions

路由和权限也生了,现在就在前端来处理这个数据了。

  1. 通过接口获取 userInfo 中可以携带
  2. 在 permission.js 中 通过 addRoutes 来操作。

接口完善如下:

/**
* 获取当前用户信息
*/ 
public function userInfo(Request $request)
{
    $user = $request->user();

    $user->menus = $this->getMenu($user->getAllPermissions());

    return response()->json($user, 200);
}

先修改 permission.js

# 修改成下边这样,其实 role 也可以完全不要,因为路由数据都是完全有后端生的。
          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
          const { roles, menus } = await store.dispatch('user/getInfo')

          // generate accessible routes map based on roles
          const accessRoutes = await store.dispatch('permission/generateRoutes', {roles, menus})

src/store/modules/permission.js 修改成这样

import { constantRoutes } from '@/router'
import Layout from '@/layout'

export function generateMenu(routes, menus) {
  menus.forEach(item => {
    const menu = {
      path: item.path,
      component: item.component === 'Layout' ? Layout : resolve => require([`@/views/${item.component}`], resolve),
      hidden: item.hidden,
      children: [],
      name: item.name,
      redirect: item.redirect ? item.redirect : '',
      meta: { title: item.title, icon: item.icon, activeMenu: item.activeMenu, noCache: item.noCache }
    }

    if (item.children && item.children.length > 0) {
      generateMenu(menu.children, item.children)
    } else {
      delete menu.children
    }
    routes.push(menu)
  })
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, data) {
    const { roles, menus } = data
    return new Promise(resolve => {
      let accessedRoutes = []
      generateMenu(accessedRoutes, menus)
      accessedRoutes.push({ path: '*', redirect: '/404', hidden: true });
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

然后,框架自身的路由可以简化成这样子。

src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'
/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error-page/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error-page/401'),
    hidden: true
  }
]

export const asyncRoutes = [{ path: '*', redirect: '/404', hidden: true }]

const createRouter = () => new Router({
  mode: 'history', // require service support
  base: '/admin/',
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

到此,侧边栏完全有服务端生成,和前端配置毫无关系。当然,你可以默认使用一些前端配置路由。

view 页面填充。用个能连起来多就好
src/views/system/index.vue

<template>
  <div>
    <router-view />
  </div>
</template>

src/views/system/permission/index.vue

<template>
  <div style="padding:30px;">
    <span>
     permission
    </span>
    <router-view />
  </div>
</template>

其他, create, edit 等也是这样。

遗忘了 icon 的处理了。因为侧边导航 icon 就那么几个,所以后端动态生成的时候,可以手动编辑赋值或 seed 赋值就好了。前端再手动添加控制 svg 就好。

然后编译,登录进去看效果。发现已经 ok 了。

下一步

就是操作完善系统管理的各个功能。权限管理啥的。

发表评论

电子邮件地址不会被公开。 必填项已用*标注