Blow water
I have not written an article for a long time, mainly because I still feel that his talent is shallow, if the things he learned in the form of a similar tutorial to write out, afraid of misrecognition. I don’t have a job yet, and I don’t have any good work experience to share. The last reason is that I really have no time. There are a lot of things at school, and I am as busy as a dog at the end of the semester. I didn’t learn much new stuff, and now I feel like I’m out, I can’t understand what the group leaders are talking about (actually it’s not that exaggerated), ha ha ha, this may be the pain of the front people. Then let me tell you why I want to write this article. It is because I am planning to do a mall project of my own recently. I will do the background first, the main front-end technology stack is Vue, and THE back-end I use PHP+Laravel. When I do the background, the first problem I face is the problem of permission. It may be too general to say the permission. What is the permission? In fact, there are two permissions involved in my project, one is the operation permission, the other is the route access permission (here refers to the front-end route). I’m going to talk about my thinking and step by step exploration (to put it crudely, it’s the process of stepping on the pit).
Train of thought to
First think about permissions who does it? Front end or back end, depending on the situation. I think the operation permission must be done by the back end. The operation permission is to restrict the permission for every operation, such as the super administrator can add background users, disable background users, and other background users cannot do these operations. The fine-grained control like this is best done by the back end. Is to do access control on every interface, if the front end to do it can be exhausting and unsafe. The second is access to routes. Even with fine-grained permissions, we expect different users to have different routes and different menus when they log in to the background. Because it actually improves the user experience, different roles have different menus, and it’s better to prevent misactions (because it’s not clear which actions have permissions) than to wait until the user performs a specific action and then be told they don’t have permissions. In fact this is not completely can be done, such as two users can access the same page, but their operation permissions on this page is different, is to summarize, after the validation is to ensure data integrity and safety of operation, and front end doing validation, whatever the validation is to improve the usability and user experience.
I also want to talk about the access permission of the route, because the operation permission is mostly done by the back end, and the front end responds to the user with the corresponding prompt according to the interface. My background is developed based on the open source project Vue-Element-Admin, and the routing access control of this project is done at the front end. According to the idea of the underdog, there is a dynamic routing table in the front end, and after the user logs in and gets the user’s role, the accessible routes are screened out according to the role of the currently logged in user. A customized routing table is formed and routes are dynamically mounted. The advantage of this is that the front end does not need to configure routing and permissions for each page it develops, thus avoiding being dominated by the back end, hahaha.
Back to my own project. First of all, this is a personal project (can be understood as a play), the front end and back end are all my own, only to torture myself, of course, this is not the main reason. Most importantly, in my project, administrators can add roles, including assigning permissions to roles, and then assigning roles to users. However, the routing table is again linked to roles. Consider a situation where, after the project goes live, the administrator adds a new role and assigns a menu to that role. If you put the routing table on the front end, the accessible role of each route is written dead. To assign a menu to a new role, you have to change the front end code, which is obviously not appropriate. So I didn’t use the latter, that is, put the routing information in the back-end, back-end routing information and the role, after the user login request to the corresponding interface to get the routing information belonging to this user (that is, the menu), then the front end of the returned data formatting, converted to conform to the vue – the router routing format, then dynamic mount. Put routing information in the back end so that you can configure the route. For example, if the super administrator is not happy today and does not want the user of a certain role to access a certain route, he can simply remove the role from the route (a graph will show this process dynamically later).
I don’t know if I have made myself clear. To summarize, my project involves both operation permissions and routing access permissions. The former is done completely in the back end, and permissions are added to the interface. The latter requires the front-end and back-end mutual cooperate, next will be primarily about me is how to achieve the latter, don’t put the complete implementation of the code, because there is no need, the first reason is that I think the code just tools, it is important to train of thought, the second reason I also mentioned the above, I don’t really want to write the classroom-style teaching program, so more is to share. I’m just going to talk a little bit about the implementation and some of the things that you should be aware of, both front and back, but mostly the front end.
The implementation process
To be honest, in fact, in the implementation process of the back end is the main role, such as role matching, routing screening need to be done by the back end. If you want to do this on the back end, you need a database and a table, so let’s take a look at my table.
The table diagram I present, in addition to the menu table, includes all the permission tables and the role tables, so to speak, supporting all the operations on the permission roles in the back end. Don’t be intimidated by the fact that there are only three roles, MENU, and menu_role that are relevant to what we’re going to talk about. The ROLES table is used to hold the roles of the entire system, the MENU table is used to hold the menu information (that is, routing to the front end), and the MENu_ROLE table is used as an intermediate table to connect the MENU table to the ROLES table. The user table is also related to the role table. After all, only user information is available when a user logs in. Therefore, the user, role, and menu must be related. In other words, the following content is based on the basic user role permissions function is complete. With that said, let’s talk about what the back-end interface returns
1. Back-end interface
To know what data the back end should return, you need to know what data the front end needs. The front end needs data that looks something like this:
[ { path: '/permission', component: Layout, redirect: '/permission/page', name: 'Permission', meta: { title: Roles: ['super_admin', 'editor']}, roles: [{path: 'role', component: () => import('@/views/permission/role'), name: 'RolePermission', meta: {title: 'role', roles: ['super_admin'] } } ] }, { path: '/icon', component: Layout, children: [ { path: 'index', component: () => import('@/views/icons/index'), name: 'Icons', meta: { title: 'Icons', icon: 'icon', noCache: true } } ] } ]Copy the code
The data above is the ideal data, that is, if the back end returns this data, then the front end should use it directly. But this is not possible, because as you can see I have a lot of fields, the database table above in order to cover most of the time, so the backend does not know what is useful for your front, and also not clear data combination way, to be together is also very troublesome, because the table is two-dimensional, cannot describe some of the hierarchy, The main thing is that the component field returned by the back end is just a path to the front end component, which must be converted to a custom component. So, in summary, the back end can only return all the fields as they are, while the front end has to filter and combine the data itself. Now know that the back-end to return all of the fields, there is a problem, is the nested routing problem, although the back-end to return all of the fields, but at least you have to tell front the nested relationship between the various routing (i.e., the parent routing and zi lu by), attentive friend could see I have a menu in the table above the pid field, This field describes the relationship between routes. The PID of the parent route is 0. The PID of the child route is the ID field of the parent route in the table. Now that I’ve got this all sorted out, I’m ready to write some code, so LET me just post some back end code.
public function index(Request $request)
{
$user = auth('admin')->user();
// Remove duplicate menu ids
$menu_ids = MenuRole::whereIn('rid'.$user->roles->pluck('id'))->distinct()->pluck('mid');
$menus = Menu::whereIn('id'.$menu_ids)->where('pid'.0)->with([
'children'= >function ($query) use($menu_ids) {
return $query->whereIn('id'.$menu_ids);
}
])->get();
return $this->response->array($menus->toArray());
}
Copy the code
If you’re familiar with Laravel, you should be able to read this code in no time. If you’re not familiar with it, pay attention to the data returned. This code first filters out all user role ids (because a user may have more than one role), then finds the corresponding menu ID based on the role ID, and then searches for records in the menu table. On the blackboard! Note that there is a possibility that multiple roles owned by the user have access to a particular route. In this case, duplicate records should be removed. Otherwise, duplicate data will be returned to the front end, resulting in duplicate menus on the page.
The data returned looks something like this:
2. The front-end format route data
Finally, I’m working on the front end. The front end does a very simple thing: request the interface, get the data, format the data, and mount the route. One step at a time.
Maybe you think it’s easy to get the data, but how to get the data, when to get the data, and where to save the data after you get it? In this project, I put the request in the Vuex action, because the final data will be saved in Vuex, so that the left menu component can get the routing information to render the corresponding menu, which is a little more convenient. As for the timing, The request is made in the Router beforeEach after the user logs in. I’m going to post the code
Export function getAsyncRoutes() {return request({url: '/admin/menus', method: 'get' }) } file: @/store/modules/permission.js import { getAsyncRoutes } from '@/api/user' import formatRoutes from '@/utils/formatRoutes' import Layout from '@/ Layout '// actions const Actions = {generateRoutes({ Commit}) {return new Promise(resolve => {// get routing table getAsyncRoutes().then(routes => {// format routing table const AccessedRoutes = formatRoutes(routes, Layout) // Add route to Vuex commit('SET_ROUTES', accessedRoutes) resolve(accessedRoutes) }) }) } } export default { namespaced: true, state, mutations, actions }Copy the code
The above code only defines the action. The next step is to get formatted data from the action defined by dispath in the vue-router route hook
import router from './router' import store from './store' import { Message } from 'element-ui' import NProgress from 'nprogress' import 'nprogress/nprogress.css' import { getToken } from '@/utils/auth' import getPageTitle from '@/utils/get-page-title' NProgress.configure({ showSpinner: false }) const whiteList = ['/login', '/auth-redirect'] router.beforeEach(async(to, from, Document.title = getPageTitle(to.meta. Title) const hasToken = getToken() If (hasToken) {if (to.path === '/login') {// logged in, jump to: '/' next({path: '/'}) nprogress.done () // Close the page progress bar} else {// Whether to get role information from user information const hasRoles = store.getters.roles && Store. Getters. Roles. Length > 0 if (hasRoles) {/ / login and has the role of information, Next ()} else {try {await store.dispatch('user/getInfo')... / / according to the role of generating routing table const accessRoutes = await store. Dispatch (' permission/generateRoutes') / / dynamically add routing router.addRoutes(accessRoutes) next({ ... to, replace: True})} catch (error) {console.log(error) // Clear token, Jump to login page await store. Dispatch (' user/resetToken) Message. The error (error | | 'from the error') next (` / login? redirect=${to.path}`) NProgress.done() } } } } else { if (whiteList.indexOf(to.path) ! == -1) {// The access path is in the whitelist next()} else {// No login, jump to the login page next(' /login? redirect=${to.path}`) NProgress.done() } } }) router.afterEach(() => { NProgress.done() })Copy the code
At this point, it is almost ready, because the routing formatted information is put into Vuex, and the menu component can also get the data, which will automatically render. One more thing to add is the formatRoutes function, which does something very simple, so let’s look at the code first
function loadView(component) { return (resolve) => require([`@/views/${component}`], resolve) } export default function formatRoutes(routes, Layout) { const formatRoutesArr = [] routes.forEach(route => { const router = { meta: {} } const { pid, title, path, redirect, component, keep_alive, icon, name, children } = route if (component === 'Layout') { router['component'] = Layout } else { router['component'] = loadView(component) } if (redirect ! == null) { router['redirect'] = redirect } if (icon ! == null) { router['meta']['icon'] = icon } if (children && children instanceof Array && children.length > 0) { router['children'] = formatRoutes(children) } if (name ! == null) { router['name'] = name } router['meta']['title'] = title router['path'] = path if (pid === 0) { router['alwaysShow'] = true } router['meta']['noCache'] = ! Push ({path: '*', redirect: '/404', hidden: keep_alive formatroutesarr. push(router)}) true }) return formatRoutesArr }Copy the code
For example, if attributes are null and we don’t want them to appear in the final routing table. This is a stupid way to do it. In fact, if we embed a for loop in the forEach, we need to do it. On the blackboard! You should not use import() to import components. This can cause problems. You should also use an extra layer of path when importing components. For example, don’t put the require ([‘ @ / views / ${component} ‘], resolve) written in the require ([‘ @ / ${component} ‘]. Resolve) because the component field I originally pulled from the database contains the /view path, and it keeps failing.
All of these processes are successful, but how the menu components render the data is irrelevant, and then it’s time to get excited and see what happens.
Results demonstrate
1. First, let’s see the effect of different users’ login background. First, let’s see the interface after the super administrator login
Then we change to another account, which has the roles of shipper and warehouse administrator
The super administrator I mentioned earlier can control which roles the route can be accessed by. I’m not going to talk about it in this article. For example, now the super administrator is super upset and doesn’t want the shipper to have access to the order management menu, so that’s fine, okay.
After the user logs in, the order management menu is no longer available
The last
This is the first time FOR me to do this kind of access control, or back end. Have never done before, also won’t, this is also a little bit of their own groping, problems emerge in endlessly, fall down again and again to stand up. It took a lot of time, but I felt a sense of accomplishment when I finally made it. I still have too many things to learn, to their own requirements is to be able to stick to it.
Hey, stranger. Your “like” may be the biggest encouragement to me!