若依框架登录鉴权详解(动态路由)

news2024/9/20 22:52:22

若依框架登录鉴权:1.获取token(过期在响应拦截器中实现),2.基于RBAC模型获取用户、角色和权限信息(在路由前置守卫),3.根据用户权限动态生成(从字符串->组件,根据permission添加动态路由信息)和添加路由addRoutes(在路由前置守卫)

若依框架(Ruoyi)后端的登录权限身份认证流程是一个复杂但高效的过程,它确保了系统的安全性和数据的保护。以下是一个典型的若依框架后端登录权限身份认证流程,基于多个来源的信息进行归纳和整理:

1.发起请求获取认证凭证(token)

现象:用户未登录或者token过期,刷新页面将重定向至登录页面

  • 如果用户身份验证通过,系统会生成一个认证凭证(如JWT,即JSON Web Token)。
    • 认证凭证的生成过程可能包括生成一个唯一的UUID作为token的一部分,并设置token的有效期(这个有效期可能是通过Redis等缓存系统来控制的,因为JWT本身不直接支持自动刷新有效期)。
    • 生成的token会包含用户的基本信息和权限信息,以便后续进行权限控制和身份验证。
    • 后端通过验证后返回一个包含用户信息的令牌(Token),前端将这个令牌保存起来(如存储在Cookie、LocalStorage或SessionStorage中)以后的请求都需要带上这个令牌进行验证
​
//login.vue
handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            //存储title至vuex
            this.$store.commit("settings/SET_HEADTITLE",this.title)
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    }
//store/modules/user.js
// 前端登录请求
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      const password = userInfo.password
      const code = userInfo.code
      const uuid = userInfo.uuid
      return new Promise((resolve, reject) => {
        login(username, password, code, uuid).then(res => {
           //!!!key!!!
          //获取并设置后端传过来的token
          setToken(res.data.token)
          commit('SET_TOKEN', res.data.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
//system/SysLoginController
//后端登录响应
    /**
     * 登录方法
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @SaIgnore
    @PostMapping("/login")
    public R<Map<String, Object>> login(@Validated @RequestBody LoginBody loginBody) {
        Map<String, Object> ajax = new HashMap<>();
        // 生成令牌
        String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
            loginBody.getUuid());
        ajax.put(Constants.TOKEN, token);
        return R.ok(ajax);
    }


​

若依框架前端怎样判断token是否过期,并且要重新登录获取token:响应拦截器

在前端应用中,使用响应拦截器(response interceptor)来判断token是否过期,并在需要时重新登录以获取新的token是一种常见的做法。这通常涉及到以下几个步骤:

  1. 设置响应拦截器:在发起HTTP请求的库(如axios)中设置拦截器,以便在接收到响应时执行自定义逻辑。

  2. 检查响应状态码:在拦截器中检查响应的状态码,特别是那些表明需要身份验证的状态码(如401、403等)。

  3. 判断token是否过期:虽然401状态码通常表示未授权,但具体是否因为token过期导致需要额外的逻辑来判断。有时候,后端会在响应头中返回一个特定的字段(如WWW-Authenticate)来指示这一点,但更常见的是通过解析响应体中的信息(如错误信息)来判断。不过,在某些情况下,你可能只是简单地根据收到了401就认为token已过期。

  4. 重新登录获取token:如果确定token已过期,你可以尝试使用某种形式的“刷新token”来自动获取新的access token,或者引导用户重新登录。这取决于你的应用是否支持无感知重新登录(使用refresh token)以及后端的实现。

  5. 更新本地存储和重试请求:如果成功获取了新的token,更新本地存储中的token,并根据需要重试之前的请求。

  6. 错误处理:处理在重新登录过程中可能出现的任何错误,并向用户显示适当的反馈。

//utils/request.js
// 响应拦截器
service.interceptors.response.use(res => {
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据则直接返回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
      return res.data
    }
    if (code === 401) {
      if (!isRelogin.show) {
        isRelogin.show = true;
        MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
          isRelogin.show = false;
          store.dispatch('LogOut').then(() => {
            location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
          })
      }).catch(() => {
        isRelogin.show = false;
      });
    }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    }
    // else if (code === 500) {
    //   Message({ message: msg, type: 'error' })//redis错误,待解决
    //   return Promise.reject(new Error(msg))
    // }
    else if (code === 601) {
      Message({ message: msg, type: 'warning' })
      return Promise.reject('error')
    } else if (code !== 200) {
      Notification.error({ title: msg })
      return Promise.reject('error')
    } else {
      return res.data
    }
  },
  error => {
    console.log('err' + error)
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    Message({ message: message, type: 'error', duration: 5 * 1000 })
    return Promise.reject(error)
  }
)

若报401错误表明token到期,401错误是一种常见的HTTP状态码,它表示“未授权”(Unauthorized)访问。当用户尝试访问受保护的资源时,如果未能提供有效的身份验证信息或提供的身份验证信息被拒绝,服务器就会返回401错误。这种错误通常出现在需要登录或授权才能访问的网站上,如银行网站、社交媒体网站等。

2. 获取认证信息(user、roles and permission)

不同用户的权限不同,右侧导航条【大层面】和页面中操作按钮【小层面】不同

  • 将生成的token和用户的相关信息(如用户ID、角色、权限等)缓存起来,以便后续快速验证和查询。
//store/modules/user.js

// 获取用户信息【前端请求】
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo().then(res => {
          const user = res.data.user
          const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : user.avatar;
          if (res.data.roles && res.data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
            commit('SET_ROLES', res.data.roles)
            commit('SET_PERMISSIONS', res.data.permissions)
          } else {
            commit('SET_ROLES', ['ROLE_DEFAULT'])
          }
          commit('SET_NAME', user.userName)
          commit('SET_AVATAR', avatar)
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    },

//system/SysLoginController
//后端响应返回用户信息【user、roles and permisssions】
    /**
     * 获取用户信息
     *
     * @return 用户信息
     */
    @GetMapping("getInfo")
    public R<Map<String, Object>> getInfo() {
        LoginUser loginUser = LoginHelper.getLoginUser();
        SysUser user = userService.selectUserById(loginUser.getUserId());
        Map<String, Object> ajax = new HashMap<>();
        ajax.put("user", user);
        ajax.put("roles", loginUser.getRolePermission());
        ajax.put("permissions", loginUser.getMenuPermission());
        return R.ok(ajax);
    }

管理员 

 普通员工

根据权限动态生成并添加路由
步骤 1: 定义静态路由和异步路由

首先,你需要定义你的路由,通常分为静态路由(不需要权限即可访问的路由,如登录页、404页面等)和异步路由(需要权限的路由,如用户管理、订单管理等)。

/**
 * Note: 路由配置项
 *
 * hidden: true                     // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
 * alwaysShow: true                 // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
 *                                  // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
 *                                  // 若你想不管路由下面的 children 声明的个数都显示你的根路由
 *                                  // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
 * redirect: noRedirect             // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
 * name:'router-name'               // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
 * roles: ['admin', 'common']       // 访问路由的角色权限
 * permissions: ['a:a:a', 'b:b:b']  // 访问路由的菜单权限
 * meta : {
    noCache: true                   // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
    title: 'title'                  // 设置该路由在侧边栏和面包屑中展示的名字
    icon: 'svg-name'                // 设置该路由的图标,对应路径src/assets/icons/svg
    breadcrumb: false               // 如果设置为false,则不会在breadcrumb面包屑中显示
    activeMenu: '/system/user'      // 当路由设置了该属性,则会高亮相对应的侧边栏。
  }
 */

// router/index.js  

import Vue from 'vue'  
import Router from 'vue-router'  
  
Vue.use(Router)  
  
// 静态路由  
const constantRoutes = [  
  {  
    path: '/login',  
    component: () => import('@/views/login/index'),  
    hidden: true  
  },  
  {  
    path: '/404',  
    component: () => import('@/views/404'),  
    hidden: true  
  }  
]  
  
// 异步路由(示例)  
const asyncRoutes = [  
  {  
    path: '/user',  
    component: () => import('@/views/user/index'),  
    meta: { title: '用户管理', roles: ['admin', 'editor'] }  
  },  
  // 更多路由...  
]  
  
const createRouter = () => new Router({  
  // mode: 'history', // 需要后端支持  
  scrollBehavior: () => ({ y: 0 }),  
  routes: constantRoutes  
})  
  
const router = createRouter()  
  
// 动态添加路由  
function addAsyncRoutes(routes) {  
  routes.forEach(route => {  
    router.addRoute(route)  
  })  
}  
  
export {  
  router,  
  addAsyncRoutes,  
  asyncRoutes  
}
步骤 2:  路由守卫与权限验证和路由添加

在用户登录后,你需要从后端获取用户的权限信息,并根据这些权限信息来过滤和添加路由。

你还需要设置路由守卫来确保用户在没有权限时不能访问某些页面。

//src/permission.js
//前端路由守卫
router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    //获取路由的mata.title属性,并存储在Vuex中
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          isRelogin.show = false
            //生成路由
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            //添加路由
            router.addRoutes(accessRoutes) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

获取与添加**路由** 

 

  //生成路由
   store.dispatch('GenerateRoutes').then(accessRoutes => {
      // 根据roles权限生成可访问的路由表
  //添加路由
     router.addRoutes(accessRoutes) // 动态添加可访问路由表

     next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
    })

 GenerateRoutes

请求得到的sdata与rdata

//store/module/permisssion.js
 // 生成路由
    GenerateRoutes({ commit }) {
      return new Promise(resolve => {
        // 向后端请求路由数据
        getRouters().then(res => {
          const sdata = JSON.parse(JSON.stringify(res.data))
          const rdata = JSON.parse(JSON.stringify(res.data))
          //console.log(sdata,rdata); //array
          const sidebarRoutes = filterAsyncRouter(sdata)
          const rewriteRoutes = filterAsyncRouter(rdata, false, true)
           // 动态路由,基于用户权限动态去加载
          const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
          rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
          router.addRoutes(asyncRoutes);
          commit('SET_ROUTES', rewriteRoutes)
          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
          commit('SET_DEFAULT_ROUTES', sidebarRoutes)
          commit('SET_TOPBAR_ROUTES', sidebarRoutes)
          resolve(rewriteRoutes)
        })
      })
    }


// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    //type标识sidebarRoutes(false)与rewriteRoutes(true)
    if (type && route.children) {
      route.children = filterChildren(route.children)
    }
    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else if (route.component === 'InnerLink') {
        route.component = InnerLink
      } else {
        //指定目录下找对应组件
        route.component = loadView(route.component)
      }
    }
    if (route.children != null && route.children && route.children.length) {
      //递归
      route.children = filterAsyncRouter(route.children, route, type)
    } else {
      delete route['children']
      delete route['redirect']
    }
    return true
  })
}

function filterChildren(childrenMap, lastRouter = false) {
  var children = []
  childrenMap.forEach((el, index) => {
    if (el.children && el.children.length) {
      if (el.component === 'ParentView' && !lastRouter) {
        el.children.forEach(c => {
          c.path = el.path + '/' + c.path
          if (c.children && c.children.length) {
            children = children.concat(filterChildren(c.children, c))
            return
          }
          children.push(c)
        })
        return
      }
    }
    if (lastRouter) {
      el.path = lastRouter.path + '/' + el.path
    }
    children = children.concat(el)
  })
  return children
}


export const loadView = (view) => {
  if (process.env.NODE_ENV === 'development') {
    return (resolve) => require([`@/views/${view}`], resolve)
  } else {
    // 使用 import 实现生产环境的路由懒加载
    return () => import(`@/views/${view}`)
  }
}

//权限判断
//store/modules/permission.js

// 动态路由遍历,验证是否具备权限

export function filterDynamicRoutes(routes) {
  const res = []
  routes.forEach(route => {
    if (route.permissions) {
      if (auth.hasPermiOr(route.permissions)) {
        res.push(route)
      }
    } else if (route.roles) {
      if (auth.hasRoleOr(route.roles)) {
        res.push(route)
      }
    }
  })
  return res
}

//plugins/auth.js

import store from '@/store'

function authPermission(permission) {
  const all_permission = "*:*:*";
  const permissions = store.getters && store.getters.permissions
  if (permission && permission.length > 0) {
    return permissions.some(v => {
      return all_permission === v || v === permission
    })
  } else {
    return false
  }
}

function authRole(role) {
  const super_admin = "admin";
  const roles = store.getters && store.getters.roles
  if (role && role.length > 0) {
    return roles.some(v => {
      return super_admin === v || v === role
    })
  } else {
    return false
  }
}

export default {
  // 验证用户是否具备某权限
  hasPermi(permission) {
    return authPermission(permission);
  },
  // 验证用户是否含有指定权限,只需包含其中一个
  hasPermiOr(permissions) {
    return permissions.some(item => {
      return authPermission(item)
    })
  },
  // 验证用户是否含有指定权限,必须全部拥有
  hasPermiAnd(permissions) {
    return permissions.every(item => {
      return authPermission(item)
    })
  },
  // 验证用户是否具备某角色
  hasRole(role) {
    return authRole(role);
  },
  // 验证用户是否含有指定角色,只需包含其中一个
  hasRoleOr(roles) {
    return roles.some(item => {
      return authRole(item)
    })
  },
  // 验证用户是否含有指定角色,必须全部拥有
  hasRoleAnd(roles) {
    return roles.every(item => {
      return authRole(item)
    })
  }
}

 addRoute

 router.addRoutes(accessRoutes) // 动态添加可访问路由表
 next({ ...to, replace: true }) // hack方法 确保addRoutes已完成

第一行:router.addRoutes(accessRoutes)

这行代码的作用是动态地向Vue Router的路由表中添加路由。addRoutes方法是Vue Router早期版本(Vue Router 2.x)中用于动态添加路由的方法。然而,需要注意的是,在Vue Router 3.x及更高版本中,官方推荐使用addRoutes的替代方案,即通过router.matcher.addRoutes或者完全重新创建router实例来动态添加路由(因为addRoutes方法在Vue Router 4.x中被移除)。但这里我们仍然以addRoutes为例进行说明。

  • router:这是Vue Router的实例,它包含了应用的所有路由配置。
  • addRoutes:这是Vue Router实例上的一个方法,用于向路由表中添加新的路由规则。
  • accessRoutes:这是一个数组,包含了要添加到路由表中的路由对象。每个路由对象都遵循Vue Router的路由配置规范,包括pathcomponentchildren等属性。

第二行:next({ ...to, replace: true })

这行代码通常出现在Vue Router的导航守卫(Navigation Guards)中,用于控制路由的跳转。next函数是导航守卫的回调函数,它决定了路由跳转的行为。

  • next:这是一个必须调用的函数,用于解决守卫中的钩子。调用它时,可以传入一个位置对象或者一个错误。如果不调用,则整个路由导航都将被“挂起”。
  • { ...to, replace: true }:这里使用了对象展开语法(...)来复制to对象的所有属性到一个新对象中,并添加或修改replace属性为trueto对象通常包含了即将要跳转到的路由信息,如pathquery等。
    • replace: true:这个选项的作用是,当使用next函数进行路由跳转时,不是将新路由添加到历史记录堆栈中,而是替换掉当前的路由。这通常用于避免在路由跳转后留下无法退回到当前路由的“死胡同”。

结合使用这两行代码的场景

在实际应用中,这两行代码经常一起使用在基于用户权限的路由控制中。比如,当用户登录后,后端返回了用户的权限信息,前端根据这些权限信息动态生成可访问的路由表(accessRoutes),并通过router.addRoutes(accessRoutes)将这些路由添加到路由表中。然后,在导航守卫中,使用next({ ...to, replace: true })来确保用户被重定向到目标路由,并且由于replace: true,这个跳转不会在历史记录中留下当前路由的入口,从而避免了用户通过浏览器的前进/后退按钮访问到未授权的路由。

使用路由守卫保证addRoute执行完成:你可以设置一个全局前置守卫(beforeEach)或后置守卫(afterEach),并在其中检查路由是否已经存在于路由表中。然而,这种方法比较复杂且可能不够直观,因为它依赖于路由守卫的多次调用和状态检查。

根据权限限制页面的操作按钮(新增、修改和删除操作等)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2108563.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux搭建深度学习平台

linux搭建深度学习平台&#xff08;Ubuntu&#xff09; /home/guangyao/anaconda3 我服务器的anaconda地址 ~/anaconda3 1 首先就是打开浏览器&#xff0c;我实验室的是火狐&#xff0c;搜索anaconda下载&#xff0c;找到下载目录&#xff0c;cd进去&#xff0c; 2安装 bas…

【佳学基因检测】在bagisto中,grouped products(同组产品)和bundled products(打包产品)有什么不同?

【佳学基因检测】在bagisto中&#xff0c;grouped products&#xff08;同组产品&#xff09;和bundled products&#xff08;打包产品&#xff09;有什么不同&#xff1f; 在Bagisto电商平台中&#xff0c;**grouped products&#xff08;同组产品&#xff09;和bundled prod…

iceberg存储结构详解

iceberg底层组织方式 下图是Iceberg中表格式&#xff0c;s0、s1代表的是表Snapshot信息&#xff0c;每个表示当前操作的一个快照&#xff0c;每次commit都会生成一个快照Snapshot&#xff0c;每个Snapshot快照对应一个manifest list 元数据文件&#xff0c;每个manifest list …

2024国赛数学建模预测算法-BP神经网络模型的预测精度分析(MATLAB 实现)

人工神经网络 第一节 人工神经网络概述 在您阅读这本书的时候&#xff0c;大约有个相互连接的神经元在帮助您阅读、呼吸、思考&#xff0c;以及完成各种各样的动作。这些神经元中&#xff0c;有些有着与生俱来的功能&#xff0c;比如呼吸、吮吸&#xff0c;有些则是由后天训练…

动态规划DP--背包问题

文章目录 0-1背包问题 -- 问题定义动态规划解法代码题目&#xff1a;分割等和子集题解 0-1背包问题 – 问题定义 在 0-1 背包问题中&#xff0c;给定一个背包的最大容量 W&#xff0c;以及 n 个物品&#xff0c;每个物品有两个属性&#xff1a; 重量&#xff1a;第 i 个物品的…

[数据集][目标检测]电动车入梯进电梯电单车入梯检测数据集VOC+YOLO格式7106张3类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;7106 标注数量(xml文件个数)&#xff1a;7106 标注数量(txt文件个数)&#xff1a;7106 标注…

广义回归神经网络(GRNN)

一、简介 广义回归神经网络 (General Regression Neural Network &#xff0c; GRNN) 的概念是由德 国科学家多纳德提出的&#xff0c;是径向基网络的其中一种 。因为其是以数理统计为基 础的&#xff0c;因此 GRNN 可以依据样本数据逼近其中包含的非线性映射关系。即使样本 数…

家里有猫用宠物空气净化器有用吗?希喂、米家、有哈哪款更好

在快节奏的现代生活中&#xff0c;越来越多的人选择宠物作为心灵的慰藉与生活的伴侣。起初&#xff0c;这份陪伴的需求简单而纯粹&#xff0c;但随着日子一天天过去&#xff0c;那份简单的情感逐渐生根发芽&#xff0c;成长为深厚的责任与爱。我在前两年养了两只猫&#xff0c;…

一款云笔记支持在线协同文档,脑图,白板演示的工具,多个设备同步,让灵感与你同行(附源码)

前言 在快节奏的工作环境中&#xff0c;如何高-效地记录、整理并分享工作笔记已经成为了一项重要的技能。传统的笔记方式往往难以满足跨设备、即时同步以及团队协作的需求&#xff0c;导致信息孤岛和工作效率低下。面对这样的挑战&#xff0c;我们迫切需要一种全新的工具来改变…

【ArcGIS Pro原理第一期】各种空间插值原理:GPI、LPI、IDW等

ArcGIS Pro原理第一期&#xff1a;空间插值原理 空间插值方法概述1.1 全局多项式&#xff08;global polynomial interpolation, GPI&#xff09;方法原理使用范围 1.2 局部多项式&#xff08;local polynomial interpolation, LPI&#xff09;方法原理精度测量&#xff08;Mea…

echarts进度

echarts图表集 let numdata["I级",II级,III级,IV级,V级,劣V级] let pricedata40 option {backgroundColor: #0f375f,title: {show: false,text: ,left: center,top: 30%,textStyle: {color: #00D5FF,fontSize: 16,},},tooltip: {show: false},grid: {// show: true,…

鸿蒙界面开发——组件(5):菜单Menu 绑定菜单

菜单组件Menu Menu组件需和bindMenu或bindContextMenu方法配合使用&#xff0c;不支持作为普通组件单独使用。 Menu 以垂直列表形式显示的菜单。包含MenuItem、MenuItemGroup子组件。 Menu()作为菜单的固定容器&#xff0c;无参数。 MenuItem(value?: MenuItemOptions| Cust…

DC-DC开关稳压电路

前面所讲的线性稳压电路具有结构简单、调节方便、输出电压稳定性强、纹波电压小等优点。但是&#xff0c;由于调整管始终工作在放大状态&#xff0c;自身功耗较大&#xff0c;故效率较低&#xff0c;甚至仅为30%~40%。而且&#xff0c;为了解决调整管散热问题&#xff0c;必须安…

Linux-(系统启动、用户管理)

目录 前言 关机&重启命令 基本介绍 注意细节 用户登录和注销 注意&#xff1a; 用户管理 基本介绍 添加用户 指定/修改密码 删除用户 查询用户信息 切换用户 查看当前用户登录用户 用户组 新增组 删除组 查看所有组 修改用户所属组 创建用户时指定用户…

如何在车载中控上进行UI自动化测试

说到车载测试&#xff0c;很多人都很好奇&#xff0c;车载中控是否需要UI自动化测试&#xff0c;从市场反馈来说&#xff0c;在6-7年之前的车载中控测试就已经介入UI自动化测试&#xff0c;那时候还是使用javaUIautomator框架。现在大部分都已经更新为PythonAppium框架进行自动…

Fiddler安卓设备抓包基础

Fiddler安卓设备抓包基础 一、下载二、Fiddler设置三、安卓设备设置四、Fiddler工具页面介绍 一、下载 工具名称&#xff1a;Fiddler 下载地址&#xff1a;官网 二、Fiddler设置 1.打开Fiddler 2.打开Tools > Options > Connections进行如下设置&#xff0c;Fiddler…

【C++ Primer Plus习题】10.7

问题: 解答: main.cpp #include <iostream> #include "plorg.h" using namespace std;int main() {plorg p("Plorga");p.showCI();p.setCI(10);p.showCI();return 0; }plorg.h #pragma once#define SIZE 19 class plorg { private:char m_fillName…

allegro约束管理器的学习

1&#xff09;差分线neck mode的使用 2&#xff09;走线区域规则和间距区域规是不同的 3&#xff09;创建区域规则的步骤 分为三个步骤 1在spacing中创建一个区域规则&#xff1b; 2在region中把区域规则赋予它&#xff1b; 3在pcb中&#xff0c;创建一个constraint region sha…

中国次生林林龄分布数据(2020年)

次生林年龄的全国分布对于了解中国森林生态系统和碳储量至关重要。该数据集采用了一种数据驱动的方法&#xff0c;用于提高植被变化跟踪器和连续变化检测与分类算法的性能&#xff0c;通过使用变化检测算法和密集的陆地卫星时间序列来确定次生林的建立时间。 该数据集主要以tif…

实践reflex:以Personalized Sales个人销售网站为例

reflex 是一个使用纯Python构建全栈web应用的库&#xff0c;但是需要使用node&#xff0c;所以你懂的。 官网&#xff1a;Reflex Web apps in Pure Python 手册&#xff1a;Introduction Pynecone: Pynecone 是一个全栈 Python 框架&#xff0c;可以使用纯 Python 构建高性…