1、前端访问控制的常规处理方法

  前端访问控制,一般针对界面元素dom element进行可见属性或enable属性进行控制,有权限的,相关元素可见或使能;没权限的,相关元素不可见或失能。这样用户可以明确哪些是无权访问的。可见属性要比使能属性更广泛,这是每个dom元素都有的属性。

  当然前端控制仅仅是整体访问控制的一部分,后端还需要进一步针对接口访问进行鉴权。因为通过编辑浏览器的界面元素的属性,可以绕过前端控制。

  在Vue中,也有通过控制路由来实现访问控制的,但没有控制界面元素的情况下,用户体验不是很好。

  本文给出了Vue框架下前端访问控制的整体方案。

2、总体方案

  在用户登录时,或权限变更时,后端通过接口将权限树发给前端。为了减少不必要的数据传输,后端发出的权限树仅包括有权限的功能项,即前端收到的权限树的各个节点都是有权限的功能项。

  权限树节点的数据部分即为功能项的权限信息,包括两个关键字段:url和domKey。url是后端自己使用,在AOP鉴权切面类中,拦截非法的接口访问。domKey是给前端使用的,即dom element的id值,domKey的确定需要前后端协商一致,不能搞错。

  domKey在同一个路径上,不允许重复;不同路径,允许重复。所谓路径,是从根节点开始,到该节点的一系列节点组成的树杈。当然,没有必要的话,domKey最好不重复。同一个界面视图范围的各子节点的domKey也不允许重复。

  前端本地存储用户token和权限树JSON字符串,如果本地这个存储信息存在,重新打开浏览器,可以免登录。(仅本地token有效,不能完全保证token真的有效,如后端重启服务器、token过期等导致token失效,前端通过HTTP访问时,仍然会跳到登录页面)。

  登录成功后,将token和权限树JSON字符串保存到本地存储。

  权限发生变更时,通过response拦截器,检查有无附加信息,如有需要,更新token和权限树JSON字符串。

  前端开发一个权限树的管理的js文件,用于权限树JSON对象的访问,权限树JSON字符串被转换成权限树JSON对象。

  开发前端页面vue文件时,需要进行权限控制的dom element,使用下列属性:

class="permissions" id="相关domKey"

  通过class来标识该界面元素是与访问控制相关的,目的是确定需要进行权限控制的组件范围,id即为该功能项对应的domKey。

  然后,使用一个公共权限设置方法,来统一处理权限相关的界面元素。

  由于Vue的组件style,可以有scoped属性设置,此时,在App.vue中,就不能访问到相关dom element的class,局部式样渲染后,在外部被改写,因此,在scoped限制的情况下,需要在scoped起作用的Vue组件中,也要调用公共权限设置方法。另外,scoped的限制,恰好使得相同domKey的节点,可以通过上级节点domKey来加以区分。这样,就用统一的方法,实现了前端页码的访问控制。

3、方案实现

3.1、功能项的表结构定义

DROP TABLE IF EXISTS `function_tree`;
CREATE TABLE `function_tree`
(
`func_id` INT(11) NOT NULL DEFAULT 0 COMMENT '功能ID',
`func_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '功能名称',
`parent_id` INT(11) NOT NULL DEFAULT 0 COMMENT '父功能ID',
`level` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '功能所在层级',
`order_no` INT(11) NOT NULL DEFAULT 0 COMMENT '显示顺序',
`url` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '访问接口url',
`dom_key` VARCHAR(80) NOT NULL DEFAULT '' COMMENT 'dom对象的id', `remark` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注', -- 记录操作信息
`operator_name` VARCHAR(80) NOT NULL DEFAULT '' COMMENT '操作人账号',
`delete_flag` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '记录删除标记,1-已删除',
`create_time` DATETIME(3) NOT NULL DEFAULT NOW(3) COMMENT '创建时间',
`update_time` DATETIME(3) DEFAULT NULL ON UPDATE NOW(3) COMMENT '更新时间',
PRIMARY KEY (`func_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='功能表';

  如有需要,可以增加icon字段,用于前端树节点的显示。

3.2、后端权限树的输出

  后端在登录成功后,给前端发送token和权限树JSON字符串。

  关于树节点的生成,可参阅:Java通用树结构数据管理---https://www.cnblogs.com/alabo1999/p/14928380.html。里面有关于权限树的例子。

  为了方便前端管理,这里修改权限树的输出,将根节点也一并输出到前端。

  在管理员修改用户权限后,动态权限更新,可通过附加信息,给前端发送token和权限树JSON字符串。参阅:Spring Boot动态权限变更实现的整体方案---https://www.cnblogs.com/alabo1999/p/14948914.html。

3.3、前端本地缓存

  vue项目中,新建/src/store目录,创建inde.js文件。代码如下:

import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex); const store = new Vuex.Store({ state: {
// 存储token
token: localStorage.getItem('token') ? localStorage.getItem('token') : '',
// 存储权限树
rights: localStorage.getItem('rights') ? localStorage.getItem('rights') : ''
}, mutations: {
// 修改token,并将token存入localStorage
changeLogin (state, user) {
if(user.token){
state.token = user.token;
localStorage.setItem('token', user.token);
}
if (user.rights){
state.rights = user.rights;
localStorage.setItem('rights', user.rights);
}
}
}
}); export default store;

3.4、创建权限管理模块

  vue项目中,新建/src/common目录,创建treeNode.js文件。代码如下:

/**
* 处理树结构数据,这里主要指功能权限树
* 权限树的结构如下:
* [
* {
* nodeData:{
* funcId:1, //功能ID
* funcName:"", //功能名称
* parentId:0, //父节点ID
* level:1, //功能所在层级
* orderNo:2, //显示顺序
* url:"", //访问接口url
* domKey:"" //dom对象的id
* },
* children:[
* nodeData:{...},
* children:[...]
* ]
* },
* {
* nodeData:{...},
* children:[...]
* }
* ]
*/ var TreeNode = {
//功能树
rightsTree:null, /**
* 将权限树的JSON字符串加载到树对象上
* @param {权限树的JSON字符串} rights
*/
loadData(rights){
//将缓存的JSON字符串,转为JSON对象,为一级树节点的数组
var treeNode = JSON.parse(rights);
return treeNode;
}, /**
* 在给定树上,找到上级domkey为superDomkey的给定domKey的树节点
* 不同子树如果存在子节点domKey重复的情况,也可以区分
* @param {给定树节点} rightsTree
* @param {上级的domkey} superDomkey
* @param {树节点的domkey} domKey
*/
lookupNodeByDomkeys(rightsTree,superDomkey,domKey){
var node = null;
var superNode = null;
//先寻找superDomkey
if(superDomkey != ""){
//如果上级对象的domkey非空
superNode = this.lookupNodeByDomkey(rightsTree,superDomkey);
}
if (superNode != null){
//如果上级节点非空,或已找到,则在子树上搜索,可加快搜索速度,并且可避免子节点domKey重复的情况
node = this.lookupNodeByDomkey(superNode,domKey);
}else{
node = this.lookupNodeByDomkey(rightsTree,domKey);
}
return node;
}, /**
* 在给定的子树中,搜索指定domKey的树节点
* @param {子树} rightsTree
* @param {domkey} domKey
*/
lookupNodeByDomkey(rightsTree,domKey){
var node = null;
var functionInfo = rightsTree.nodeData;
//先查找自身的数据
if (functionInfo.domKey == domKey){
//如果找到,则返回
return rightsTree;
}
//搜索子节点
for (var i = 0; i < rightsTree.children.length; i++){
var item = rightsTree.children[i];
node = this.lookupNodeByDomkey(item,domKey);
if (node != null){
break;
}
} return node;
}
} export default TreeNode;

  如果domKey确保唯一的话,使用Map可能是访问效率更高的方案。这里还是使用树型结构来管理权限树。

3.5、创建公共方法模块

  vue项目中,在/src/common目录下,创建commonFuncs.js文件。代码如下:

import TreeNode from './treeNode.js'

var commonFuncs =  {
checkRights(superDomkey){
//先加载权限树
if (TreeNode.rightsTree == null){
let rights = localStorage.getItem('rights');
if (rights === null || rights === ''){
//没有权限树
return;
}
//加载权限树
TreeNode.rightsTree = TreeNode.loadData(rights);
} //获取class包含permissions的所有dom对象
var elements = document.getElementsByClassName('permissions');
for(var i = 0; i < elements.length; i++){
var element = elements[i];
if (element.id != undefined)
{
var node = null;
//如果对象有id,检查权限
if (superDomkey == null || superDomkey == undefined){
//如果未指定上级domkey,直接查找
node = TreeNode.lookupNodeByDomkey(TreeNode.rightsTree,element.id);
}else{
//指定上级domkey
node = TreeNode.lookupNodeByDomkeys(TreeNode.rightsTree,superDomkey,element.id)
}
if (node != null && node != undefined){
//包含节点
if (element.style.display == "none"){
element.style.display = "";
}
console.log('has rights :'+element.id);
}else{
element.style.display="none";
console.log('has not rights :'+element.id);
}
}
}
}
}; export default commonFuncs;

  checkRights方法,参数为superDomkey,即指定上级节点的domKey,允许为空或空串,相当于不指定。其查找当前页面或scoped范围的文档中,class名称包含permissions的所有dom元素。取得dom的id,即功能节点的domKey,如果在权限树中存在对应节点,则表示有权限;否则表示无权限。(注意:前端的权限树都是有权限的功能节点)。

3.6、修改main.js

  修改main.js文件,使得公共模块生效。代码如下:

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import md5 from 'js-md5';
import axios from 'axios'
import VueAxios from 'vue-axios'
import TreeNode_ from './common/treeNode.js'
import CommonFuncs_ from './common/commonFuncs.js'
import instance_ from './api/index.js'
import global_ from '../config/global.js' Vue.use(VueAxios,axios)
Vue.prototype.$md5 = md5
Vue.prototype.TreeNode = TreeNode_
Vue.prototype.$baseUrl = process.env.API_ROOT
Vue.prototype.instance = instance_ //axios实例
Vue.prototype.global = global_
Vue.prototype.commonFuncs = CommonFuncs_ Vue.use(ElementUI)
Vue.config.productionTip = false /* eslint-disable no-new */
var vue = new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>',
render:h=>h(App)
}) export default vue

  引入了commonFuncs和TreeNode全局对象,可以在vue文件中使用。

3.7、组件示例

  侧边导航栏,与权限控制相关,可以作为示例。文件为Left.vue,代码如下:

<template>
<div class="left-sidebar">
<el-menu :default-openeds="['1']" style="background:#F0F6F6;">
<el-submenu index="1">
<el-menu-item-group >
<el-menu-item index="1-1">
<router-link class="menu" tag="li" to="/home" exact-active-class="true"
id="homeMenu" active-class="_active">
<i class="el-icon-s-home"></i>首页
</router-link>
</el-menu-item>
<el-submenu index="1-2" id="userManagementMain">
<template slot="title" ><i class="el-icon-user-solid"></i>用户管理</template>
<el-menu-item index="1-2-1" class="permissions" id="userManagementSub">
<router-link class="menu" tag="li" to="/userManagement">
<i class="el-icon-user"></i>用户管理
</router-link>
</el-menu-item>
<el-menu-item index="1-2-2" class="permissions" id="changePassword">
<router-link class="menu"tag="li" to="/changePassword">
<i class="el-icon-key"></i>修改密码
</router-link>
</el-menu-item>
</el-submenu>
<el-menu-item index="1-3" class="permissions" id="questionnaireManagement">
<router-link class="menu" tag="li" to="/questionnaireManagement">
<i class="el-icon-document"></i>问卷内容管理
</router-link>
</el-menu-item>
<el-submenu index="1-4" class="permissions" id="issueManagementMain">
<template slot="title"><i class="el-icon-message"></i>问卷发布管理</template>
<el-menu-item index="1-4-1" class="permissions" id="issueManagementSub">
<router-link class="menu" tag="li" to="/issueManagement">
<i class="el-icon-phone"></i>发布问卷查询
</router-link>
</el-menu-item>
<el-menu-item index="1-4-2" class="permissions" id="issueTaskQuery">
<router-link class="menu" tag="li" to="/issueTaskQuery">
<i class="el-icon-tickets"></i>发布任务查询
</router-link>
</el-menu-item>
</el-submenu>
<el-menu-item index="1-5" class="permissions" id="answerSheetManagement">
<router-link class="menu" tag="li" to="/answerSheetManagement">
<i class="el-icon-receiving"></i>答卷管理
</router-link>
</el-menu-item>
</el-menu-item-group>
</el-submenu>
</el-menu>
</div>
</template> <style>
/* 去掉右边框 */
.el-menu {
border-right: none;
} .el-submenu {
background-color: rgb(231, 235, 220) ;
}
</style>

  注意那些:class="permissions" id=“XXX”的dom元素,基本都是el-menu-item。这里,将scoped去掉了,因为菜单项,目前只有侧边导航栏在使用。

3.7、修改App.vue

  App.vue,作为应用页面组件的总成,在里面进行总的权限控制。代码如下:

<template>
<div id="app">
<!-- 其他页 -->
<el-container style="min-height: calc(100% - 50px);" v-if="$route.meta.keepAlive">
<!-- 无头部导航栏 -->
<el-container>
<el-aside :style="{width:collpaseWidth}">
<!-- 侧边栏 -->
<keep-alive>
<left></left>
</keep-alive>
</el-aside>
<el-main>
<!-- Body -->
<router-view></router-view>
</el-main>
</el-container>
<!-- 无足部 -->
</el-container> <!-- 登录页 -->
<router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
</template> <script>
import left from './components/Left.vue' export default {
name: 'App',
components: {
left: left
},
data(){
return {
collpaseWidth:200
}
},
mounted:function(){
this.commonFuncs.checkRights();
},
methods: { }
}
</script> <style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

  在页码加载时,调用commonFuncs.checkRights()方法,进行权限控制。

3.8、测试一下

3.8.1、获取权限树数据

  登录成功后,后端输出的权限树数据如下:

{
rights = {
"nodeData": {
"funcId": 0,
"funcName": "root",
"parentId": -1,
"level": 0,
"orderNo": 0,
"url": "",
"domKey": ""
},
"children": [{
"nodeData": {
"funcId": 1,
"funcName": "用户管理一级菜单",
"parentId": 0,
"level": 1,
"orderNo": 0,
"url": "",
"domKey": "userManagementMain"
},
"children": [{
"nodeData": {
"funcId": 3,
"funcName": "修改密码",
"parentId": 1,
"level": 2,
"orderNo": 1,
"url": "/userMan/changePassword",
"domKey": "changePassword"
},
"children": []
}]
}, {
"nodeData": {
"funcId": 10,
"funcName": "问卷内容管理一级菜单",
"parentId": 0,
"level": 1,
"orderNo": 1,
"url": "",
"domKey": "questionnaireManagement"
},
"children": [{
"nodeData": {
"funcId": 11,
"funcName": "新增问卷",
"parentId": 10,
"level": 2,
"orderNo": 0,
"url": "/questionnaireMan/addQuestionnaire",
"domKey": "addQuestionnaire"
},
"children": []
}, {
"nodeData": {
"funcId": 12,
"funcName": "编辑问卷",
"parentId": 10,
"level": 2,
"orderNo": 1,
"url": "/questionnaireMan/editQuestionnaire",
"domKey": "editQuestionnaire"
},
"children": []
}, {
"nodeData": {
"funcId": 13,
"funcName": "查询问卷",
"parentId": 10,
"level": 2,
"orderNo": 2,
"url": "/questionnaireMan/queryQuestionnaires",
"domKey": "queryQuestionnaire"
},
"children": []
}, {
"nodeData": {
"funcId": 14,
"funcName": "复制新建问卷",
"parentId": 10,
"level": 2,
"orderNo": 3,
"url": "",
"domKey": "copyAddQuestionnaire"
},
"children": []
}, {
"nodeData": {
"funcId": 15,
"funcName": "浏览问卷",
"parentId": 10,
"level": 2,
"orderNo": 4,
"url": "/questionnaireMan/previewQuestionnaire",
"domKey": "browseQuestionnaire"
},
"children": []
}, {
"nodeData": {
"funcId": 16,
"funcName": "提交审核",
"parentId": 10,
"level": 2,
"orderNo": 5,
"url": "/questionnaireMan/submitAduit",
"domKey": "submitAudit"
},
"children": []
}, {
"nodeData": {
"funcId": 18,
"funcName": "作废问卷",
"parentId": 10,
"level": 2,
"orderNo": 7,
"url": "/questionnaireMan/cancelQuestionnaire",
"domKey": "cancelQuestionnaire"
},
"children": []
}]
}, {
"nodeData": {
"funcId": 20,
"funcName": "问卷发布管理一级菜单",
"parentId": 0,
"level": 1,
"orderNo": 2,
"url": "",
"domKey": "issueManagementMain"
},
"children": [{
"nodeData": {
"funcId": 21,
"funcName": "发布管理二级菜单",
"parentId": 20,
"level": 2,
"orderNo": 0,
"url": "",
"domKey": "issueManagementSub"
},
"children": []
}, {
"nodeData": {
"funcId": 22,
"funcName": "发布任务查询",
"parentId": 20,
"level": 2,
"orderNo": 1,
"url": "",
"domKey": "issueTaskQuery"
},
"children": []
}]
}, {
"nodeData": {
"funcId": 40,
"funcName": "答卷管理一级菜单",
"parentId": 0,
"level": 1,
"orderNo": 3,
"url": "",
"domKey": "answerSheetManagement"
},
"children": [{
"nodeData": {
"funcId": 41,
"funcName": "查询答卷记录",
"parentId": 40,
"level": 2,
"orderNo": 0,
"url": "/answerSheetMan/queryAnswerTask",
"domKey": "queryAnswerSheet"
},
"children": []
}, {
"nodeData": {
"funcId": 42,
"funcName": "回收记录明细",
"parentId": 40,
"level": 2,
"orderNo": 1,
"url": "/answerSheetMan/getAnswerSubmitDetail",
"domKey": "recoveryDetail"
},
"children": []
}, {
"nodeData": {
"funcId": 43,
"funcName": "答卷统计",
"parentId": 40,
"level": 2,
"orderNo": 2,
"url": "/answerSheetMan/queryStatResult",
"domKey": "answerSheetStat"
},
"children": []
}, {
"nodeData": {
"funcId": 44,
"funcName": "答卷原始记录",
"parentId": 40,
"level": 2,
"orderNo": 3,
"url": "/answerSheetMan/queryOriginalAnswer",
"domKey": "queryOriginalAnswer"
},
"children": []
}]
}]
}, token = 873820BA39E64005BCCE3E54A830AB2C
}

  这些功能项中,有些与导航栏有关,还有一些是页面的按钮或链接,在示例中没有用到。

3.8.2、制作首页

  制作一个简单的首页Home.vue,代码如下:

<template>
<div id="home">
<h4>欢迎使用</h4>
<h3>XX系统</h3>
</div>
</template>

3.8.3、简单设置路由导航文件

  修改/src/router/index.js文件,代码如下:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Home from '@/components/Home.vue'
import Login from '@/components/login/Login.vue' Vue.use(Router) const router = new Router({
routes: [
{
path: '/home',
name: 'home',
component: Home,
meta: {
keepAlive: true
}
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
keepAlive: false
}
},
]
}) // 导航守卫
// 使用 router.beforeEach 注册一个全局前置守卫,判断用户是否登陆
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
next();
} else {
let token = localStorage.getItem('token'); if (token === null || token === '') {
next('/login');
} else {
if (to.path === '/'){
next('/home');
}else{
next();
}
}
}
}); export default router;

3.8.4、导航栏效果测试

  现在运行Vue,"npm run dev",然后显示首页,并用F12显示调式信息:

  侧边栏页面显示如下:

  浏览器的调试器的控制台输出信息为:

  说明,domKey为userMangementSub的dom元素没有操作权限,与侧边栏的效果一致。

3.8.5、scoped情况测试

  Login.vue,使用了scoped,作为示例,现在将登录按钮,进行权限控制,修改如下:

      <el-form-item>
<el-button type="primary" class="permissions" id="login" style="width:160px" @click="submitForm('form')">登录</el-button>
</el-form-item>

  在Login.vue的script的mounted方法中,增加权限控制代码:

  mounted:function(){
//页面加载时,显示验证码
this.getVerifyCode();
this.commonFuncs.checkRights();
},

  由于domKey为login的,没有在权限树中,故其加入权限控制集合,又没有被授权,则该按钮应该不可见。

  运行测试,显示登录页,效果图如下:

  登录按钮不可见了,与预期效果一致。

3.9、登录成功后保存信息

  登录成功后,将后端发生过来的token和权限树保存起来,并将JSON字符串转为JSON对象。

  代码如下:

   submitForm(formName) {
let _this = this;
this.$refs[formName].validate(valid => {
// 验证通过为true,有一个不通过就是false
if (valid) {
// 通过的逻辑
let passwd = this.$md5(this.form.password);
this.instance.userLogin(this.$baseUrl,{
loginName:_this.form.username,
password:passwd,
verifyCode:_this.form.verifyCode
}).then(res => {
console.log(res.data);
if (res.data.code == this.global.SucessRequstCode){
//如果登录成功
_this.userToken = res.data.data.token;
_this.rights = res.data.data.rights;
//更新权限树
this.TreeNode.rightsTee = this.TreeNode.loadData(_this.rights);
console.log(this.TreeNode.rightsTee)
// 将用户token和权限树保存到vuex中
_this.changeLogin({ token: _this.userToken, rights: _this.rights});
_this.$router.push('/home');
//alert('登陆成功');
}else{
alert(res.data.message);
}
}).catch(error => {
alert('账号或密码错误');
console.log(error);
});
} else {
console.log('验证失败');
return false;
}
});
},

3.10、权限动态更新的拦截处理

  根据权限动态更新方案,管理员修改用户权限后,该用户第一次访问后端接口,返回信息中可能会携带附加信息。这个可能在任何返回JSON格式数据的接口中发生。因此,可使用拦截器,来进行统一处理。

import axios from 'axios';
import router from '../router'
import Vue from 'vue';
import Vuex from 'vuex';
import TreeNode from '../common/treeNode.js' const instance = axios.create({
timeout: 60000,
headers: {
'Content-Type': "application/json;charset=utf-8"
}
}); //token相关的response拦截器
instance.interceptors.response.use(response => {
if (response) {
switch (response.data.code) {
case 3: //token为空
case 4: //token过期
case 5: //token不正确
localStorage.clear(); //删除用户信息
//要跳转登陆页
alert('token失效,请重新登录!');
router.replace({
path: '/login',
});
break;
default:
break;
}
if(response.data.additional){
//如果包含附加信息
var data = {};
if(response.data.additional.token){
//如果包含token
data.token = response.data.additional.token;
localStorage.setItem('token', data.token);
}
if(response.data.additional.rights) {
data.rights = response.data.additional.rights;
localStorage.setItem('rights', data.rights);
//刷新权限树
TreeNode.rightsTree = TreeNode.loadData(data.rights);
}
}
}
return response;
}, error => {
return Promise.reject(error.response.data.message) //返回接口返回的错误信息
})

Vue前端访问控制方案的更多相关文章

  1. Vue 前端权限控制的优化改进版

    1.前言   之前<Vue前端访问控制方案 >一文中提出,使用class="permissions"结合元素id来标识权限控制相关的dom元素,并通过公共方法check ...

  2. vue前端开发那些事——后端接口.net core web api

    红花还得绿叶陪衬.vue前端开发离不开数据,这数据正来源于请求web api.为什么采用.net core web api呢?因为考虑到跨平台部署的问题.即使眼下部署到window平台,那以后也可以部 ...

  3. Vue 前端配置多级目录实践(基于Nginx配置方式)

    前情提要 有阵子没更新博客了,因为快年结了工作比较多,这不,最近公司的对外演示环境出现问题这个活儿也落到了我的头上-- 事情是这样的,原来演示环境有很多服务,每个服务都是对外单独开一个端口,比如 ht ...

  4. vue 前端框架 (三)

    VUE 生命周期 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> < ...

  5. vue 前端框架 目录

    vue 前端框架 目录   vue-目录 ES6基础语法 vue基础语法 Vue.js的组件化思想 —上 Vue.js的组件化思想 —下 Vue + Vue-Router结合开发 SublimeSer ...

  6. vue前端+java后端 vue + vuex + koa2开发环境搭建及示例开发

    vue + vuex + koa2开发环境搭建及示例开发 https://segmentfault.com/a/1190000012918518 vue前端+java后端 https://blog.c ...

  7. 基于vue+springboot+docker网站搭建【五】部署vue前端项目

    部署vue前端项目  一.下载项目到本地   https://github.com/macrozheng/mall-admin-web 二.npm install 三.修改api配置,改为你接下来要部 ...

  8. vue前端实战注意事项

    1. vue前端实战注意事项 1.1. 预备 1.1.1. Eslint 这是个语法检查工具,我用webstorm作为开发的ide,这个语法检查还是太严格了,一个空格啥的都会报错,对新手来讲还是建议关 ...

  9. beego-vue URL重定向(beego和vue前后端分离开发,beego承载vue前端分离页面部署)

    具体过程就不说,是搞这个的自然会动,只把关键代码贴出来. beego和vue前后端分离开发,beego承载vue前端分离页面部署 // landv.cnblogs.com //没有授权转载我的内容,再 ...

随机推荐

  1. kubernetes dashboard延长自动超时注销

    方法1:部署清单时,修改yaml文件,添加 container.Args 增加 --token-ttl=43200 其中43200是设置自动超时的秒数.也可以设置 token-ttl=0 以完全禁用超 ...

  2. traefik ingress Controller使用

    Kubernetes Ingress Kubernetes Ingress是路由规则的集合,这些规则控制外部用户如何访问Kubernetes集群中运行的服务. 在Kubernetes中,有三种方式可以 ...

  3. gparted 当分区空间大于1T 用gparted分区

    lsblkfdisk -lparted -s /dev/sdb mklabel msdos parted -s /dev/sdb mkpart primary 0 100%lsblk dfparted ...

  4. Linux Test Project(一)

    http://www.vimlinux.com/lipeng/2014/09/12/ltp/ Testing Linux, one syscall at a time. LTP是从SGI开始的,后由I ...

  5. Spring Cloud(Dalston.SR1)

    Spring Cloud 示例项目地址:https://github.com/Yanshaoshuai/microservicecloud Eureka 集群搭建 microservicecloud- ...

  6. 76-Java安装Eclipse并创建第一个HelloWorld.md

    76-Java安装Eclipse并创建第一个HelloWorld.md 首先确定已经安装Java系统环境,若未安装,请参考博客Java环境windows搭建 访问Eclipse官网 下载完成直接发送快 ...

  7. 1.3Linux 终端命令格式

    Linux 终端命令格式 目标 了解终端命令格式 知道如何查阅终端命令帮助信息 01. 终端命令格式 bashcommand [-options] [parameter] 说明: command:命令 ...

  8. Linux 使用命令发送邮件

    1.关闭本机的sendmail服务或者postfix服务 #执行下面的命令,关闭sendmail和postfix服务 #sendmial [root@db-backup ~]# service sen ...

  9. 10.4 route:显示或管理路由表

    route命令 可以显示或管理Linux系统的路由表,route命令设置的路由主要是静态路由. 路由的概念     计算机与计算机之间的数据传输必须得经由网络,而网络可以通过直接连接两台计算机的方式或 ...

  10. 利用MathType快速提取论文中的公式

    首先随便打开一个论文,比如下图,我们想提取公式(2.2.7) 第一步:按截图快捷键:Win+Shift+S ,把公式截取下来 第二步:打开大佬开发的网站:https://mathf.itewqq.cn ...