SpringBoot + Ant Design Pro Vue实现动态路由和菜单的前后端分离框架
Ant Design Pro Vue默认路由和菜单配置是采用中心化的方式,在 router.config.js
统一配置和管理,同时也提供了动态获取路由和菜单的解决方案,并将在2.0.3版本中提供,因到目前为止,官方发布的版本为2.0.2,所以本文结合官方提供的解决方案结合SpringBoot后台权限管理进行修改,搭建一套完整的SpringBoot +Vue前后端分离框架。
项目具体代码示例可参看:
github: GitHub - wmz1930/Jeebase: Jeebase是一款前后端分离的开源开发框架,基于springboot+vue(vue-element-admin/Ant Design Pro Vue)开发,一套SpringBoot后台,两套前端页面,可以自由选择基于ElementUI或者AntDesign的前端界面。二期会整合react前端框架(Ant Design React)。在实际应用中已经使用这套框架开发了CMS网站系统,社区论坛系统,微信小程序,微信服务号等,后面会逐步整理开源。 本项目主要目的在于整合主流技术框架,寻找应用最佳项目实践方案,实现可直接使用的快速开发框架。
gitee: Jeebase: Jeebase是一款前后端分离的开源开发框架,基于springboot+vue(vue-element-admin/Ant Design Pro Vue)开发,一套SpringBoot后台,两套前端页面,可以自由选择基于ElementUI或者AntDesign的前端界面。二期会整合react前端框架(Ant Design React)。在实际应用中已经使用这套框架开发了CMS网站系统,社区论坛系统,微信小程序,微信服务号等,后面会逐步整理开源。 本项目主要目的在于整合主流技术框架,寻找应用最佳项目实践方案,实现可直接使用的快速开发框架。
本项目主要目的在于整合主流技术框架,寻找应用最佳项目实践方案,实现可直接使用的快速开发框架。一套SpringBoot后台可以同时支持 ElementUI 和 Ant Design Pro Vue两套前端框架。
一、Ant Design Pro Vue需要修改的几个文件:
1. main.js 去掉mock // import './mock'
2. request.js 修改请求参数
3. store/permission.js 修改动态路由生成方法
4. store/user.js 修改登录后获取菜单的处理
5. login.js 修改登录请求
6. Login.vue 修改登录页面参数并添加登录验证码
备注:报错babel eslint TypeError: Cannot read property 'range' of null ,执行 cnpm i babel-eslint@7.2.3即可。
二、下面详细介绍各模块修改,具体修改内容:
1. main.js注释掉mock ,使其请求后台
import '@babel/polyfill'import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store/'
import { VueAxios } from './utils/request'// mock
// import './mock' ---------这里注释掉import bootstrap from './core/bootstrap'
import './core/use'
import './permission' // permission control
import './utils/filter' // global filterVue.config.productionTip = false// mount axios Vue.$http and this.$http
Vue.use(VueAxios)new Vue({router,store,created () {bootstrap()},render: h => h(App)
}).$mount('#app')
2. request.js 修改请求参数
import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
import {VueAxios
} from './axios'
import notification from 'ant-design-vue/es/notification'
import {ACCESS_TOKEN
} from '@/store/mutation-types'// 创建 axios 实例
const service = axios.create({baseURL: 'http://127.0.0.1:8080/', // api base_urltimeout: 6000 // 请求超时时间
})const err = (error) => {if (error.response) {const data = error.response.dataconst token = Vue.ls.get(ACCESS_TOKEN)if (error.response.status === 403) {notification.error({message: 'Forbidden',description: data.message})}if (error.response.status === 401 && !(data.result && data.result.isLogin)) {notification.error({message: 'Unauthorized',description: 'Authorization verification failed'})if (token) {store.dispatch('Logout').then(() => {setTimeout(() => {window.location.reload()}, 1500)})}}}return Promise.reject(error)
}// request interceptor
service.interceptors.request.use(config => {const token = Vue.ls.get(ACCESS_TOKEN)if (token) {config.headers['Authorization'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改}return config
}, err)// response interceptor
service.interceptors.response.use((response) => {const res = response.dataif (res.code !== 200) {// 90000002:登录超时if (res.code === 90000002) {notification.error({message: '登录超时',description: '登录超时,请重新登录'})} else if (res.code === 10000007) { // 10000007:没有权限notification.error({message: '没有权限',description: '您没有权限执行此操作'})} else {notification.error({message: '操作失败',description: 'res.msg'})}return Promise.reject(new Error(res.message || 'Error'))} else {return response.data}
}, err)const installer = {vm: {},install (Vue) {Vue.use(VueAxios, service)}
}export {installer as VueAxios,service as axios
}
3. store/permission.js 修改动态路由生成方法
import { asyncRouterMap, constantRouterMap } from '@/config/router.config'
import { RouteView } from '@/layouts'/*** 过滤账户是否拥有某一个权限,并将菜单从加载列表移除** @param permission* @param route* @returns {boolean}*/
function hasPermission (roles, route) {if (route.meta && route.meta.roles) {return roles.some(role => route.meta.roles.includes(role))} else {return true}
}/*** 单账户多角色时,使用该方法可过滤角色不存在的菜单** @param roles* @param route* @returns {*}*/
// eslint-disable-next-line
function hasRole(roles, route) {if (route.meta && route.meta.roles) {return route.meta.roles.includes(roles.id)} else {return true}
}function filterAsyncRouter (routerMap, roles) {const accessedRouters = routerMap.filter(route => {if (hasPermission(roles, route)) {if (route.children && route.children.length) {route.children = filterAsyncRouter(route.children, roles)}return true}return false})return accessedRouters
}
/*** 递归组装路由表,返回符合用户角色权限的路由表(路由表后台配置时使用)* add by jeebase* @param resources*/
function assembleAsyncRoutes (resources) {const accessedRouters = []resources.forEach(resource => {var route = {}if (resource.resourceUrl.indexOf('Layout') >= 0) {route = {path: '/' + resource.resourcePath,component: RouteView,redirect: '/' + resource.resourceUrl,name: resource.resourcePageName,meta: {title: resource.resourceName,icon: resource.resourceIcon}}} else if (resource.resourceUrl.indexOf('nested') >= 0 && resource.children && resource.children.length) { // 包含子菜单的二级以下菜单route = {path: '/' + resource.resourcePath,component: RouteView,redirect: '/' + resource.children[0].resourceUrl,name: resource.resourcePageName,meta: {title: resource.resourceName,noCache: !resource.resourceCache,icon: resource.resourceIcon},hidden: !resource.resourceShow}} else { // 最后一层菜单route = {path: '/' + resource.resourcePath,component: () => import(`@/views/${resource.resourceUrl}`),name: resource.resourcePageName,meta: {title: resource.resourceName,keepAlive: resource.resourceCache,icon: resource.resourceIcon},hidden: !resource.resourceShow}}if (resource.children && resource.children.length) {route.children = assembleAsyncRoutes(resource.children)}accessedRouters.push(route)})return accessedRouters
}const permission = {state: {routers: constantRouterMap,addRouters: []},mutations: {SET_ROUTERS: (state, routers) => {state.addRouters = routersstate.routers = constantRouterMap.concat(routers)}},actions: {GenerateRouters ({ commit }, roles) {return new Promise(resolve => {const accessedRouters = filterAsyncRouter(asyncRouterMap, roles)commit('SET_ROUTERS', accessedRouters)resolve(accessedRouters)})},GenerateResourcesRouters ({ commit }, resources) { // add by jeebasereturn new Promise(resolve => {let accessedRoutersif (resources && resources.length > 0) {const basicMenus = asyncRouterMap.find(item => item.path === '/').childrenasyncRouterMap.find(item => item.path === '/').children = basicMenus.concat(assembleAsyncRoutes(resources))accessedRouters = asyncRouterMap} else {accessedRouters = filterAsyncRouter(asyncRouterMap, [])}accessedRouters.push({ path: '*', redirect: '/404', hidden: true })commit('SET_ROUTERS', accessedRouters)console.log(accessedRouters)resolve(accessedRouters)})}}
}export default permission
4. store/user.js 修改登录后获取菜单的处理
import Vue from 'vue'
import { login, getInfo, logout } from '@/api/login'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { welcome } from '@/utils/util'const user = {state: {token: '',name: '',welcome: '',avatar: '',roles: [],info: {}},mutations: {SET_TOKEN: (state, token) => {state.token = token},SET_NAME: (state, { name, welcome }) => {state.name = namestate.welcome = welcome},SET_AVATAR: (state, avatar) => {state.avatar = avatar},SET_ROLES: (state, roles) => {state.roles = roles},SET_INFO: (state, info) => {state.info = info},SET_PERMISSIONS: (state, permissions) => {state.permissions = permissions}},actions: {// 登录Login ({ commit }, userInfo) {return new Promise((resolve, reject) => {login(userInfo).then(response => {const token = response.dataVue.ls.set(ACCESS_TOKEN, token, 7 * 24 * 60 * 60 * 1000)commit('SET_TOKEN', token)resolve()}).catch(error => {reject(error)})})},// 获取用户信息GetInfo ({ commit }) {return new Promise((resolve, reject) => {getInfo().then(response => {// const result = response.resultconst { data } = responseif (!data) {reject(new Error('Verification failed, please Login again.'))}const { roles, stringResources, userName, headImgUrl } = data// roles must be a non-empty arrayif (roles && roles.length > 0) {commit('SET_ROLES', roles)commit('SET_PERMISSIONS', stringResources)} else {reject(new Error('getInfo: roles must be a non-null array !'))}commit('SET_NAME', { name: userName, welcome: welcome() })commit('SET_AVATAR', headImgUrl)// commit('SET_INTRODUCTION', data)// if (result.role && result.role.permissions.length > 0) {// const role = result.role// role.permissions = result.role.permissions// role.permissions.map(per => {// if (per.actionEntitySet != null && per.actionEntitySet.length > 0) {// const action = per.actionEntitySet.map(action => { return action.action })// per.actionList = action// }// })// role.permissionList = role.permissions.map(permission => { return permission.permissionId })// commit('SET_ROLES', result.role)commit('SET_INFO', data)// } else {// reject(new Error('getInfo: roles must be a non-null array !'))// }// commit('SET_NAME', { name: result.name, welcome: welcome() })// commit('SET_AVATAR', result.avatar)resolve(data)}).catch(error => {reject(error)})})},// 登出Logout ({ commit, state }) {return new Promise((resolve) => {commit('SET_TOKEN', '')commit('SET_ROLES', [])commit('SET_PERMISSIONS', [])Vue.ls.remove(ACCESS_TOKEN)logout(state.token).then(() => {resolve()}).catch(() => {resolve()})})}}
}
export default user
5. login.js 修改登录请求
import api from './index'
import { axios } from '@/utils/request'/*** login func* parameter: {* username: '',* password: '',* remember_me: true,* captcha: '12345'* }* @param parameter* @returns {*}*/
export function login (parameter) {return axios({url: '/auth/login',method: 'post',data: parameter})
}export function getSmsCaptcha (parameter) {return axios({url: api.SendSms,method: 'post',data: parameter})
}export function getInfo () {return axios({url: '/auth/user/info',method: 'get',headers: {'Content-Type': 'application/json;charset=UTF-8'}})
}export function logout () {return axios({url: '/auth/logout',method: 'post',headers: {'Content-Type': 'application/json;charset=UTF-8'}})
}/*** get user 2step code open?* @param parameter {*}*/
export function get2step (parameter) {return axios({url: api.twoStepCode,method: 'post',data: parameter})
}
6. Login.vue 修改登录页面参数,并添加登录验证码
<template><div class="main"><a-formid="formLogin"class="user-layout-login"ref="formLogin":form="form"@submit="handleSubmit"><a-tabs:activeKey="customActiveKey":tabBarStyle="{ textAlign: 'center', borderBottom: 'unset' }"@change="handleTabClick"><a-tab-pane key="tab1" tab="账号密码登录"><a-form-item><a-inputsize="large"type="text"placeholder="请输入帐户名或邮箱地址"v-decorator="['userAccount',{rules: [{ required: true, message: '请输入帐户名或邮箱地址' }, { validator: handleUsernameOrEmail }], validateTrigger: 'change'}]"><a-icon slot="prefix" type="user" :style="{ color: 'rgba(0,0,0,.25)' }"/></a-input></a-form-item><a-form-item><a-inputsize="large"type="password"autocomplete="false"placeholder="请输入密码"v-decorator="['userPassword',{rules: [{ required: true, message: '请输入密码' }], validateTrigger: 'blur'}]"><a-icon slot="prefix" type="lock" :style="{ color: 'rgba(0,0,0,.25)' }"/></a-input></a-form-item><a-row :gutter="0"><a-col :span="14"><a-form-item><a-inputv-decorator="['vcode', validatorRules.vcode]"size="large"type="text"placeholder="请输入验证码"><a-iconv-if="inputCodeContent == verifiedCode"slot="prefix"type="safety-certificate":style="{ fontSize: '20px', color: '#ffffff' }"/><a-iconv-elseslot="prefix"type="safety-certificate":style="{ fontSize: '20px',color: '#ffffff' }"/></a-input></a-form-item></a-col><a-col :span="10"><img :src="vcodeImg" class="v-code-img" @click="changeImgCode"></a-col></a-row></a-tab-pane><a-tab-pane key="tab2" tab="手机号登录"><a-form-item><a-input size="large" type="text" placeholder="手机号" v-decorator="['mobile', {rules: [{ required: true, pattern: /^1[34578]\d{9}$/, message: '请输入正确的手机号' }], validateTrigger: 'change'}]"><a-icon slot="prefix" type="mobile" :style="{ color: 'rgba(0,0,0,.25)' }"/></a-input></a-form-item><a-row :gutter="16"><a-col class="gutter-row" :span="16"><a-form-item><a-input size="large" type="text" placeholder="验证码" v-decorator="['captcha', {rules: [{ required: true, message: '请输入验证码' }], validateTrigger: 'blur'}]"><a-icon slot="prefix" type="mail" :style="{ color: 'rgba(0,0,0,.25)' }"/></a-input></a-form-item></a-col><a-col class="gutter-row" :span="8"><a-buttonclass="getCaptcha"tabindex="-1":disabled="state.smsSendBtn"@click.stop.prevent="getCaptcha"v-text="!state.smsSendBtn && '获取验证码' || (state.time+' s')"></a-button></a-col></a-row></a-tab-pane></a-tabs><a-form-item><a-checkbox v-decorator="['rememberMe']">自动登录</a-checkbox><router-link:to="{ name: 'recover', params: { user: 'aaa'} }"class="forge-password"style="float: right;">忘记密码</router-link></a-form-item><a-form-item style="margin-top:24px"><a-buttonsize="large"type="primary"htmlType="submit"class="login-button":loading="state.loginBtn":disabled="state.loginBtn">确定</a-button></a-form-item><div class="user-login-other"><span>其他登录方式</span><a><a-icon class="item-icon" type="alipay-circle"></a-icon></a><a><a-icon class="item-icon" type="taobao-circle"></a-icon></a><a><a-icon class="item-icon" type="weibo-circle"></a-icon></a><router-link class="register" :to="{ name: 'register' }">注册账户</router-link></div></a-form><two-step-captchav-if="requiredTwoStepCaptcha":visible="stepCaptchaVisible"@success="stepCaptchaSuccess"@cancel="stepCaptchaCancel"></two-step-captcha></div>
</template><script>
import md5 from 'md5'
import TwoStepCaptcha from '@/components/tools/TwoStepCaptcha'
import { mapActions } from 'vuex'
import { timeFix } from '@/utils/util'
import { getSmsCaptcha } from '@/api/login'export default {components: {TwoStepCaptcha},data () {return {customActiveKey: 'tab1',loginBtn: false,// login type: 0 email, 1 username, 2 telephoneloginType: 0,requiredTwoStepCaptcha: false,stepCaptchaVisible: false,form: this.$form.createForm(this),state: {time: 60,loginBtn: false,// login type: 0 email, 1 username, 2 telephoneloginType: 0,smsSendBtn: false},validatorRules: {userAccount: {rules: [{ required: true, message: '请输入用户名!', validator: 'click' }]},userPassword: {rules: [{ required: true, message: '请输入密码!', validator: 'click' }]},mobile: { rules: [{ validator: this.validateMobile }] },vcode: { rule: [{ required: true, message: '请输入验证码!' }] },inputCode: {rules: [{ required: true, message: '请输入验证码!' },{ validator: this.validateInputCode }]}},vcodeImg: '',verifiedCode: '',inputCodeContent: '',inputCodeNull: true,verkey: ''}},created () {this.vcodeImg = this.imgCode()// get2step({ })// .then(res => {// this.requiredTwoStepCaptcha = res.result.stepCode// })// .catch(() => {// this.requiredTwoStepCaptcha = false// })// this.requiredTwoStepCaptcha = true},methods: {...mapActions(['Login', 'Logout']),// handlerhandleUsernameOrEmail (rule, value, callback) {const { state } = thisconst regex = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((\.[a-zA-Z0-9_-]{2,3}){1,2})$/if (regex.test(value)) {state.loginType = 0} else {state.loginType = 1}callback()},gRandom () {return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1)},guid () {this.verkey = (this.gRandom() + this.gRandom() + '-' + this.gRandom() + '-' + this.gRandom() + '-' + this.gRandom() + '-' + this.gRandom() + this.gRandom() + this.gRandom())console.log(this.verkey)return this.verkey},imgCode () {return 'http://127.0.0.1:8080/auth/vcode?codeKey=' + this.guid() + '&n=' + Math.random()},changeImgCode () {this.vcodeImg = this.imgCode()},handleTabClick (key) {this.customActiveKey = key// this.form.resetFields()},handleSubmit (e) {e.preventDefault()const {form: { validateFields },state,customActiveKey,Login,verkey} = thisconsole.log(verkey)state.loginBtn = trueconst validateFieldsKey = customActiveKey === 'tab1' ? ['userAccount', 'userPassword', 'vcode', 'verkey'] : ['mobile', 'captcha', 'vcode', 'verkey']validateFields(validateFieldsKey, { force: true }, (err, values) => {if (!err) {console.log('login form', values)const loginParams = { ...values }delete loginParams.userAccountloginParams[!state.loginType ? 'email' : 'userAccount'] = values.userAccountloginParams.password = md5(values.userPassword)loginParams.userPassword = values.userPasswordloginParams.vcode = values.vcodeloginParams.verkey = verkeyLogin(loginParams).then((res) => this.loginSuccess(res)).catch(err => this.requestFailed(err)).finally(() => {state.loginBtn = false})} else {setTimeout(() => {state.loginBtn = false}, 600)}})},getCaptcha (e) {e.preventDefault()const { form: { validateFields }, state } = thisvalidateFields(['mobile'], { force: true }, (err, values) => {if (!err) {state.smsSendBtn = trueconst interval = window.setInterval(() => {if (state.time-- <= 0) {state.time = 60state.smsSendBtn = falsewindow.clearInterval(interval)}}, 1000)const hide = this.$message.loading('验证码发送中..', 0)getSmsCaptcha({ mobile: values.mobile }).then(res => {setTimeout(hide, 2500)this.$notification['success']({message: '提示',description: '验证码获取成功,您的验证码为:' + res.result.captcha,duration: 8})}).catch(err => {setTimeout(hide, 1)clearInterval(interval)state.time = 60state.smsSendBtn = falsethis.requestFailed(err)})}})},stepCaptchaSuccess () {this.loginSuccess()},stepCaptchaCancel () {this.Logout().then(() => {this.loginBtn = falsethis.stepCaptchaVisible = false})},loginSuccess (res) {console.log(res)this.$router.push({ name: 'dashboard' })// 延迟 1 秒显示欢迎信息setTimeout(() => {this.$notification.success({message: '欢迎',description: `${timeFix()},欢迎回来`})}, 1000)},requestFailed (err) {this.$notification['error']({message: '错误',description: ((err.response || {}).data || {}).message || '请求出现错误,请稍后再试',duration: 4})}}
}
</script><style lang="less" scoped>
.user-layout-login {label {font-size: 14px;}.getCaptcha {display: block;width: 100%;height: 40px;}.forge-password {font-size: 14px;}.v-code-img {height: 40px;float: right;margin-top: 2px;border-radius: 5px;cursor: pointer;opacity: 0.6;filter: alpha(opacity=60);}button.login-button {padding: 0 15px;font-size: 16px;height: 40px;width: 100%;}.user-login-other {text-align: left;margin-top: 24px;line-height: 22px;.item-icon {font-size: 24px;color: rgba(0, 0, 0, 0.2);margin-left: 16px;vertical-align: middle;cursor: pointer;transition: color 0.3s;&:hover {color: #1890ff;}}.register {float: right;}}
}
</style>
相关文章:

SpringBoot + Ant Design Pro Vue实现动态路由和菜单的前后端分离框架
Ant Design Pro Vue默认路由和菜单配置是采用中心化的方式,在 router.config.js统一配置和管理,同时也提供了动态获取路由和菜单的解决方案,并将在2.0.3版本中提供,因到目前为止,官方发布的版本为2.0.2,所以…...

robotframework自动化测试环境搭建
环境说明 win10 python版本:3.8.3rc1 安装清单 安装配置 selenium安装 首先检查pip命令是否安装: C:\Users\name>pipUsage:pip <command> [options]Commands:install Install packages.download Do…...

尚硅谷《Redis7》(小白篇)
尚硅谷《Redis7 》(小白篇) 02 redis 是什么 官方网站: https://redis.io/ 作者 Git Hub https://github.com/antirez 03 04 05 能做什么 06 去哪下 Download https://redis.io/download/ redis中文文档 https://www.redis.com.cn/docu…...

并非从0开始的c++ day6
并非从0开始的c day6二级指针练习-文件读写位运算位逻辑运算符按位取反 ~位于(AND):&位或(OR): |位异或: ^移位运算符左移<<右移>>多维数组一维数组数组名一维数组名传入到函数参数中数组指…...

PMP考前冲刺2.22 | 2023新征程,一举拿证
承载2023新一年的好运让我们迈向PMP终点一起冲刺!一起拿证!每日5道PMP习题助大家上岸PMP!!!题目1-2:1.在新产品开发过程中,项目经理关注到行业排名第一的公司刚刚发布同类型的产品。相比竞品&am…...

RxJava的订阅过程
要使用Rxjava首先要导入两个包,其中rxandroid是rxjava在android中的扩展 implementation io.reactivex:rxandroid:1.2.1implementation io.reactivex:rxjava:1.2.0首先从最基本的Observable的创建到订阅开始分析 Observable.create(new Observable.OnSubscribe<S…...

【2.22】MySQL、Redis、动态规划
认识Redis Redis是一种基于内存的数据库,对数据的读写操作都是在内存中完成的,因此读写速度非常快,常用于缓存,消息队列,分布式锁等场景。 Redis提供了多种数据类型来支持不同的业务场景,比如String(字符串…...

2年手动测试,裸辞后找不到工作怎么办?
我们可以从以下几个方面来具体分析下,想通了,理解透了,才能更好的利用资源提升自己。一、我会什么?先说第一个我会什么?第一反应:我只会功能测试,在之前的4年的中我只做了功能测试。内心存在一种…...

Leetcode6. N字形变换
一、题目描述: 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: 之后,你的输出需要从左往右逐行读取,产…...

将Nginx 核心知识点扒了个底朝天(十)
ngx_http_upstream_module的作用是什么? ngx_http_upstream_module用于定义可通过fastcgi传递、proxy传递、uwsgi传递、memcached传递和scgi传递指令来引用的服务器组。 什么是C10K问题? C10K问题是指无法同时处理大量客户端(10,000)的网络套接字。 Nginx是否支持将请求压…...

GPU显卡环境配置安装
前言 最近公司购买了一张RTX3090的显卡和一台新的服务器,然后对机器的GPU环境进行了安装和配置,然后简单记录一下 环境版本 操作系统:Centos7.8 显卡型号:RTX3090 Python版本:3.7.6 Tensorflow版本:2…...

CIMCAI super unmanned intelligent gate container damage detect
世界港航人工智能领军者企业CIMCAI中集飞瞳打造全球最先进超级智能闸口无人闸口ceaspectusG™视频流动态感知集装箱箱况残损检测箱况残损识别率99%以上,箱信息识别率99.95%以上World port shipping AI leader CIMCAIThe worlds most advanced super intelligent gat…...

web概念概述
软件架构:1. C/S: Client/Server 客户端/服务器端* 在用户本地有一个客户端程序,在远程有一个服务器端程序* 如:QQ,迅雷...* 优点:1. 用户体验好* 缺点:1. 开发、安装,部署,维护 麻烦…...

编译原理笔记(1)绪论
文章目录1.什么是编译2.编译系统的结构3.词法分析概述4.语法分析概述5.语义分析概述6.中间代码生成和后端概述1.什么是编译 编译的定义:将高级语言翻译成汇编语言或机器语言的过程。前者称为源语言,后者称为目标语言。 高级语言源程序的处理过程&#…...

MySQL(八)
服务器参数设置 general datadir/var/lib/mysql 数据文件存放的目录socket/var/lib/mysql/mysql.sock mysql.socket表示server和client在同一台服务器,并且使用localhost进行连接,就会使用socket进行连接pid_file/var/lib/mysql/mysql.pid 存储mysql的p…...

steam搬砖项目,小投入高回报,可放大操作,(内附教学资料)
我必须要说,steam搬砖项目就是全网门槛最低的副业,有手就行! 本人90后底层员工一枚,新入csgo搬砖项目,轻松翻身 什么做抖音、海外问卷、直播卖货,电商等等对比我这个都是小钱。我这个方法是利用了大部分人…...

华为OD机试真题Python实现【最多提取子串数目】真题+解题思路+代码(20222023)
最多提取子串数目 题目 给定由 [a-z] 26 个英文小写字母组成的字符串 A 和 B,其中 A 中可能存在重复字母,B 中不会存在重复字母 现从字符串 A 中按规则挑选一些字母,可以组成字符串 B。 挑选规则如下: 1) 同一个位置的字母只能被挑选一次 2) 被挑选字母的相对先后顺序不…...

day32 多线程(上)
文章目录相关概念codeThreadTest01ThreadTest02 编写一个类,直接继承java.lang.Thread,重写run方法ThreadTest03 实现线程的第二种方法ThreadTest04 采用匿名内部类的方式ThreadTest05 获取线程名字ThreadTest06 sleep方法sleep面试题ThreadTest08 终止线…...

【flink】 各种join类型对比
表定义 动态表(dynamic table):动态表是流的另一种表达方式,动态表作为一个逻辑的抽象概念,使我们更容易理解flink中将streaming发展到table这个层次的设计,本质都是对无边界、持续变更数据的表示形式,所以动态表与流之…...

常用正则表达式
一、校验数字的表达式 数字:^[0-9]*$ n位的数字:^\d{n}$ 至少n位的数字:^\d{n,}$ m-n位的数字:^\d{m,n}$ 零和非零开头的数字:^(0|[1-9][0-9]*)$ 非零开头的最多带两位小数的数字:^([1-9][0-9]*)(.[0…...

PMP考试有没有什么技巧可以介绍一下么?
一、试题形式 ——中英文对照 即每道题都是一遍英文,一遍翻译的中文,在审题的时候有一些小的技巧需要注意。首先如果你的英文水平足够好,建议直接阅读原文。PMP试题毕竟是美国人出的,语言的组织、思想的表达,肯定更符…...

2022-2023年营销报告(B站平台) | 5大行业势态、流量大盘全景洞察
一直以来,手持高活跃、高粘性用户群体的B站是行业用来观察年轻人消费习惯的重要平台。以至于用户群体的不断壮大带动了B站的商业价值。如今B站的商业舞台越来越大,不断地向外界招手,欢迎更多品牌积极加入到这个千万年轻人聚集的内容社区。为了…...

Python的异常与工具包
异常 当检测到一个错误时,python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的异常。 捕获异常 世界上没有完美的程序,任何程序在运行的过程中,都有可能出现异常,导致程序无法完美运行…...

基于SSM的婴幼儿商城
基于SSM的婴幼儿商城 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景介绍: …...

2023年新能源汽车行业研究报告
第一章 行业概况 新能源汽车,是指采用新型动力系统,完全或者主要依靠新型能源驱动的汽车,包括纯电动汽车、插电式混合动力汽车、增程式混合动力汽车和燃料电池汽车等。国际上,混合动力汽车(含中混、强混、插电式混动&…...

手写Promise方法(直击Promise A+规范)
前言:大家好,我是前端獭子。高效优雅是我的创作方式。学习是一个渐进的过程,要把知识串联起来才能解决某一方面的问题。 Promise 构造函数 我们先来写 Promise 构造函数的属性和值,以及处理new Promise()时会传入的两个回调函数。…...

GooglePlay SSL Error Handler
应用上架GooglePlay 收到邮件提示 出现这个原因是因为我在app中使用webview加载Https的H5界面,在onReceivedSslError()中处理SslErrorHandler时,出现白屏现象,原因是webview默认在加载有证书验证的url时,会默认使用handler.cancel…...

OpenStack手动分布式部署Keystone【Queens版】
目录 Keystone简介 1、登录数据库配置(在controller执行) 1.1登录数据库 1.2数据库里创建keystone 1.3授权对keystone数据库的正确访问 1.4退出数据库 2、数据库导入Keystone表(在controller执行) 2.1安装httpd mod_wsgi 2.2备…...

AAPT2
概念 AAPT2(Android 资源打包工具)是一种构建工具,Android Studio 和 Android Gradle 插件使用它来编译和打包应用的资源。AAPT2 会解析资源、为资源编制索引,并将资源编译为针对 Android 平台进行过优化的二进制格式。 Android Gradle 插件 3.0.0 及更高版本在默认情况下…...

kafka学习
概念: broker: 1台服务器的kafka进程,它是kafka的工作者。类似Hbase的regionServer,hdfs的Datanodetopic: 订阅发布模式的kafka中有消息主题producer:生产者customer:消费者 基础架构: 元数据:…...