当前位置: 首页 > article >正文

RBAC 权限控制:深入到按钮级别的实现

RBAC 权限控制:深入到按钮级别的实现

一、前端核心思路

1. 大致实现思路

后端都过SELECT连表查询把当前登录的用户对应所有的权限返回过来,前端把用户对应所有的权限 存起来to(vuex/pinia) 中 ,接着前端工程师需要知道每个按钮对应的权限代号如:
在这里插入图片描述

。至此为止,我们即知道了用户对应所有的权限也知道了每个权限的名字(或代号),现在我们可以从前端的层面区控制某个权限按钮的显示与否如:

	<a-buttontype="primary"v-if="userPermiss_vuex.includes('user_add')"@click="add_user()">添加用户</a-button><!-- userPermiss_vuex:存储在vuex中的用户对应所有的权限 -->

2. 具体实现

首先在用户登录成功的时候调用获取用户所以权限的方法,方法如下:

// 获取用户对应的权限
const userPermiss = async () => {try {const results = await http.get("/api/users/userPermiss")const userPermiss = results.data.results// console.log('获取用户对应的权限::',results.data)// 将用户权限存到vuex中store.commit("SET_PERMISS", userPermiss);} catch (error) {console.log(error);message.error(error.response.data.message);}
};

后端运用到的数据表:在这里插入图片描述

对应的后端接口代码:

// model/user.js
static async userPermiss(userId) {  try {const sql = `SELECT Roles.id, Roles.name, Permissions.name as permission_name, Permissions.permission_codeFROM UserRolesJOIN Roles ON UserRoles.role_id = Roles.idJOIN RolePermissions ON Roles.id = RolePermissions.role_idJOIN Permissions ON RolePermissions.permission_id = Permissions.idWHERE UserRoles.user_id = ?`;const roleRows = await query(sql, [userId]);if (!roleRows || roleRows.length === 0) {return []; // 返回空数组而不是undefined或null}const userPermissions = roleRows.map(role => role.permission_code);return [...new Set(userPermissions)]; // 去重} catch (error) {console.error('获取用户权限失败:', error);throw error; // 或者返回空数组: return [];}}

在具体页面中控制权限按钮的显示与隐藏:

方式一:传统的方式:

	<a-buttontype="primary"v-if="userPermiss_vuex.includes('user_add')"@click="add_user()">添加用户</a-button><script setup>
import { useStore } from "vuex";
const store = useStore();
// 用户对应的权限名
const userPermiss_vuex = store.state.userPermiss;
</script>

方式二:自定义指令的方式(推荐):
vue中自定义指令问题:
在这里插入图片描述

<template><a-buttontype="primary"@click="add_user()">添加用户</a-button><a-buttontype="primary"dangerv-checkpermission:foo.bar="'user_delete'"@click="delete_user()">删除用户</a-button>
</template><script setup>
// 创建一个自定义指令,来统一处理权限存在与否
const vCheckpermission = {mounted: (el, binding) => {if (!userPermiss_vuex.includes(binding.value)) {// 移除元素el.parentNode?.removeChild(el);}console.log(el, binding);// el.classList.add("is-highlight");},
};
</script>

二、后端核心思路

当然了,只在前端去实现权限的控制肯定是不安全的,还需要在后端请求接口的时候进行无权限阻止访问的操作,这里就需要运用到中间件了,下面只讲解了加入权限限制的代码。

1. 首先,我们创建一个中间件文件夹middleware

2. 接着,在middleware下创建 js 文件 auth.js ,代码如下:

// 引入jsonwebtoken模块,用于处理JWT(JSON Web Token)相关操作
const jwt = require('jsonwebtoken');// 定义一个名为auth的中间件函数,用于验证请求的认证信息
const auth = (req, res, next) => {// 打印请求头中的Authorization字段,用于调试查看是否有token传递过来console.log(req.header('Authorization'));// 判断请求头中是否包含Authorization字段if (!req.header('Authorization')) {// 如果没有Authorization字段,返回400状态码,并提示缺少tokenres.status(400).json({ message: '缺少请求头:token为空' });} else {// 如果有Authorization字段,去掉字段值前面的"Bearer ",提取出tokenconst token = req.header('Authorization').replace('Bearer ', '');// 打印提取出的token,用于调试查看token的值console.log('token:::', token);try {// 使用jsonwebtoken的verify方法验证token是否有效// 第一个参数是token,第二个参数是签名密钥(这里写的是'39qw89r23890',实际项目中应使用环境变量等安全方式存储)const decoded = jwt.verify(token, '39qw89r23890');// 打印解码后的token内容,用于调试查看解码结果console.log("decoded:::", decoded);// 将解码后的用户信息存储到req.user中,方便后续的路由处理函数使用req.user = decoded;// 如果token验证通过,调用next函数,继续执行后续的中间件或路由处理函数next();} catch (error) {// 如果token验证失败(例如token过期、被篡改等),捕获错误// 返回400状态码,并提示认证失败res.status(400).json({ message: 'Authentication failed' });}}
};// 导出auth中间件,供其他模块使用
module.exports = auth;

这段代码实现了一个 JWT (JSON Web Token) 认证中间件,主要用于验证和保护 Node.js 后端 API 路由。以下是详细解析:


核心功能解析
1. 检查请求头中的 Token
console.log(req.header('Authorization')) // 调试输出
if(!req.header('Authorization')){res.status(400).json({ message: '缺少请求头:token为空' });
}

作用:检查请求头是否包含 Authorization 字段
失败处理:如果不存在,返回 400 状态码和错误消息
安全提示:实际生产环境建议返回 401 Unauthorized 更符合 HTTP 规范

2. 提取并清理 Token
const token = req.header('Authorization').replace('Bearer ', '');
console.log('token:::',token) // 调试输出

处理逻辑:从 Authorization: Bearer <token> 格式中提取纯 Token
格式要求:前端需按标准格式发送 Token,例如:

GET /api/protected-route
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
3. JWT 验证
const decoded = jwt.verify(token, '39qw89r23890');
console.log("decoded:::",decoded)
req.user = decoded;
next();

验证过程
• 使用密钥 '39qw89r23890' 解密 Token
• 验证 Token 的有效性(是否过期、是否被篡改)
成功处理
• 将解码后的用户数据挂载到 req.user(通常包含用户ID、角色等)
• 调用 next() 进入下一个中间件或路由处理器
密钥注意:生产环境应使用环境变量存储密钥,而非硬编码

4. 错误处理
catch (error) {res.status(400).json({ message: 'Authentication failed' });
}

捕获场景
• Token 过期(TokenExpiredError
• Token 被篡改(JsonWebTokenError
• 其他验证错误
改进建议

res.status(401).json({ code: 'INVALID_TOKEN',message: 'Token验证失败',error: error.message // 可选:开发环境返回具体错误
});

完整工作流程
  1. 前端请求 → 携带 Authorization
  2. 中间件检查 → 无Token → 立即拒绝
  3. Token验证 → 无效 → 返回错误
  4. 验证通过 → 附加用户数据到 req.user → 放行到后续路由

安全注意事项
  1. 密钥管理

    // 推荐从环境变量读取
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
  2. Token 存储
    • 前端应使用 HttpOnly Cookie 或 localStorage 安全存储
    • 避免在 URL 中传递 Token(会记录到日志)

  3. 增强验证

    // 可选:检查Token是否在黑名单(如用户登出后)
    if (await isTokenRevoked(token)) {return res.status(401).json({ message: 'Token已失效' });
    }
    
  4. 日志安全
    • 实际生产环境应移除 console.log(token),避免敏感信息泄露


典型使用场景
const express = require('express');
const auth = require('./middleware/auth');const app = express();
app.get('/protected', auth, (req, res) => {// 只有通过认证的请求能进入这里res.json({ data: '敏感数据',user: req.user // 来自解码的Token信息});
});

与其他中间件的协作
中间件执行顺序功能补充
express.json()需在 auth 之前解析请求体供后续使用
helmetauth 之前增强HTTP头安全性
rate-limiterauth 之前防止暴力破解

总结
功能模块关键点
Token检查验证 Authorization 头是否存在
JWT解密使用密钥验证Token有效性
数据传递通过 req.user 共享用户信息
错误处理拦截非法请求并返回标准错误

此中间件是保护API路由的基础安全层,确保只有携带有效Token的请求能访问受保护资源。

3. 然后,在middleware下创建 js 文件 checkPermission.js ,代码如下:

// 引入数据库连接池模块,用于执行数据库查询操作
const pool = require('../db');// 定义一个名为checkPermission的高阶函数,用于创建权限检查中间件
// requiredPermission参数表示需要检查的权限代码
const checkPermission = (requiredPermission) => {// 返回一个异步中间件函数return async (req, res, next) => {try {// 根据req.user.id查询用户信息const [userRows] = await pool.query('SELECT * FROM Users WHERE id = ?', [req.user.id]);// 如果用户不存在,返回404状态码if (userRows.length === 0) {return res.status(404).json({ message: 'User not found' });}// 查询用户所拥有的角色及其对应的权限// 通过多表连接查询,获取用户的角色和权限信息const [roleRows] = await pool.query(`SELECT Roles.id, Roles.name, Permissions.name as permission_name, Permissions.permission_codeFROM UserRolesJOIN Roles ON UserRoles.role_id = Roles.idJOIN RolePermissions ON Roles.id = RolePermissions.role_idJOIN Permissions ON RolePermissions.permission_id = Permissions.idWHERE UserRoles.user_id = ?`, [req.user.id]);// 提取用户所拥有的权限代码const userPermissions = roleRows.map(role => role.permission_code);// 打印用户所拥有的权限代码,用于调试console.log("userPermissions:::", userPermissions);// 将用户权限存入res.locals,方便后续使用(注释掉了)// res.locals.userPermissions = userPermissions;// 检查用户是否拥有所需的权限if (userPermissions.includes(requiredPermission)) {// 如果用户拥有权限,调用next函数继续执行后续中间件或路由处理函数return next();} else {// 如果用户没有权限,返回400状态码并提示拒绝访问return res.status(400).json({ message: '拒绝访问:当前角色暂未有此权限' });}} catch (error) {// 如果发生错误,返回500状态码并提示内部服务器错误return res.status(500).json({ message: 'Internal server error' });}};
};// 导出checkPermission函数,供其他模块使用
module.exports = checkPermission;

这段代码实现了一个 动态权限检查中间件,用于在 Node.js 后端中对 API 请求进行细粒度的权限控制。以下是详细解析:


核心功能解析
1. 中间件工厂模式
const checkPermission = (requiredPermission) => {return async (req, res, next) => { /*...*/ }
}

作用:接收一个权限码参数(如 'user_delete'),生成对应的权限检查中间件
灵活度:可以针对不同路由要求不同权限(例如:checkPermission('user_read')checkPermission('admin_access')

2. 用户存在性验证
const [userRows] = await pool.query('SELECT * FROM Users WHERE id = ?', [req.user.id]);
if (userRows.length === 0) {return res.status(404).json({ message: 'User not found' });
}

前置条件:要求 req.user.id 必须存在(通常由前面的 JWT 中间件设置)
安全设计:即使 Token 有效,也会二次确认用户是否存在

3. 多表联合查询权限
SELECT Roles.id, Roles.name, Permissions.name as permission_name, Permissions.permission_code
FROM UserRoles
JOIN Roles ON UserRoles.role_id = Roles.id
JOIN RolePermissions ON Roles.id = RolePermissions.role_id
JOIN Permissions ON RolePermissions.permission_id = Permissions.id
WHERE UserRoles.user_id = ?

查询逻辑

  1. 通过 UserRoles 表找到用户关联的角色
  2. 通过 RolePermissions 表找到角色关联的权限
  3. 最终获取权限代码(permission_code
    数据结构
// 查询结果示例
[{id: 1,name: '管理员',permission_name: '删除用户',permission_code: 'user_delete'
}]
4. 权限校验逻辑
const userPermissions = roleRows.map(role => role.permission_code);
if (userPermissions.includes(requiredPermission)) {return next(); // 放行
} else {return res.status(400).json({ message: '拒绝访问:当前角色暂未有此权限' });
}

校验方式:检查用户权限列表中是否包含所需权限码
改进建议
• 使用 403 Forbidden 状态码更符合 HTTP 规范(400 表示客户端错误)
• 返回更详细的错误信息:
javascript res.status(403).json({ code: 'PERMISSION_DENIED', required: requiredPermission, owned: userPermissions });

5. 错误处理
catch (error) {return res.status(500).json({ message: 'Internal server error' });
}

捕获范围:数据库查询错误、网络问题等
改进建议

console.error('权限检查错误:', error);
res.status(500).json({code: 'SERVER_ERROR',message: '权限验证服务不可用'
});

完整工作流程
  1. 请求进入 → 携带已认证的用户信息(req.user.id
  2. 数据库查询 → 获取用户关联的所有权限
  3. 权限比对 → 检查是否包含所需权限
  4. 结果处理
    • 通过 → 调用 next() 进入业务逻辑
    • 拒绝 → 返回 403 错误

典型使用场景
const express = require('express');
const checkPermission = require('./middleware/checkPermission');const app = express();// 删除用户接口需要 user_delete 权限
app.delete('/users/:id', checkPermission('user_delete'), (req, res) => {// 只有有权限的用户能执行到这里res.json({ success: true });}
);

安全增强建议
  1. 权限缓存

    // 使用Redis缓存用户权限,减少数据库查询
    const cachedPerms = await cache.get(`user:${req.user.id}:perms`);
    if (cachedPerms) {return cachedPerms.includes(requiredPermission) ? next() : deny();
    }
    
  2. 权限继承

    // 实现权限继承(如admin自动拥有所有权限)
    if (userRoles.some(role => role.is_admin)) {return next();
    }
    
  3. 批量权限检查

    // 扩展支持多权限检查(需满足任意一个或全部)
    checkPermissions({any: ['user_read', 'admin_access'],all: ['log_export']
    })
    

与其他中间件的协作
执行顺序中间件功能
1express.json()解析请求体
2auth (JWT验证)用户认证
3checkPermission权限控制
4业务逻辑处理实际请求

总结
功能模块关键点优化方向
中间件工厂动态生成权限检查器支持更复杂权限逻辑
数据库查询多表联合查询权限添加缓存机制
权限校验简单包含检查支持权限继承/组合
错误处理基础错误捕获更精细的错误分类

此中间件实现了 RBAC (基于角色的访问控制) 的核心功能,是保护敏感 API 的关键安全层。

4. 最后,在接口配置中增加上面两个中间件

// router/user.jsconst express = require("express")
const router = express.Router()// 引入控制层
const User = require("../controllers/users")// 引入中间件
const checkPermission = require('../middleware/checkPermission');
const auth = require('../middleware/auth');// User.create这个方法就是添加用户的,所以及对应的权限就是user_add。
// 因此就把user_add传给中间件checkPermission,让他进行判断当前用户是否有此权限router.post('/create', auth, checkPermission('user_add'), User.create) 
router.post('/delet', auth, checkPermission('user_delete'), User.delet) 
router.post('/edit', auth, checkPermission('user_edit'), User.edit) 
// 等等....
// 导出
module.exports = route

三、出现的问题(BUG)

我在后续的测试中,发现两个问题:

  1. 在退出一个账号后登录另一个账号的时候,先显示的是上一个用户的权限?

    次要原因:这是因为你没有清除vuex中的数据

    主要原因:是你在向vuex中存储数据出了问题

  2. 在登陆成功的时候用户的所有权限没有及时存储到vuex中,就直接跳转到主页了,这就导致先显示的是你上一个用户存储的权限/如果你退出时清除了vuex中的数据 显示的就是没有权限,此时必须要重新刷新一下才能恢复正常

    主要原因:是你在向vuex中存储数据出了问题

您应该 先获取用户权限 (userPermiss())再跳转页面 (router.push()),否则可能会导致权限未正确加载的问题。

问题分析

  1. 权限未加载就跳转
    • 如果先跳转页面,再异步获取权限,可能会导致:
    ◦ 页面已经渲染,但权限未加载完成,导致权限指令(如 vCheckpermission)无法正确判断权限。
    ◦ 动态路由可能依赖权限数据,如果权限未加载,动态路由可能不会正确生成。

  2. 动态路由依赖权限
    • 您的 generateDynamicRoutes() 可能依赖 userPermiss 数据,如果权限未加载,动态路由可能不会正确生成,导致页面访问异常。


修正后的代码

const submitForm = async () => {try {await formRef.value?.validate();const response = await axios.post("/api/users/login", {username: ruleForm.value.usermobile,password: ruleForm.value.userpwd,});message.info(response.data.msg);const token = response.data.data.token;const userid = response.data.data.userInfo.id;const username = response.data.data.userInfo.username;const roleid = response.data.data.userInfo.role_id;const role_code: string = response.data.data.userInfo.role_code;localStorage.setItem("token", token);localStorage.setItem("userid", userid);localStorage.setItem("username", username);localStorage.setItem("roleid", roleid);localStorage.setItem("role_code", role_code);localStorage.setItem("token_is_exp", "0");let role_code_arr = ref<string[]>([]);role_code_arr.value.push(role_code);store.commit("SET_ROLES", role_code_arr);// 1. 先获取权限(确保权限加载完成)await userPermiss(); // 使用 await 确保权限加载完成// 2. 生成动态路由(依赖权限数据)await generateDynamicRoutes(); // 如果 generateDynamicRoutes 是异步的,也要 await// 3. 最后跳转页面if (token) {await router.push("admin/user"); // 使用 await 确保跳转完成}console.log(token);} catch (error: any) {console.error("请求失败:", error);message.error(error.response.data.msg);}
};

关键改进

  1. await userPermiss()
    • 确保权限加载完成后再执行后续操作,避免权限未加载导致的权限指令失效问题。

  2. await generateDynamicRoutes()
    • 如果 generateDynamicRoutes() 是异步的(例如依赖 API 请求),也要 await 确保动态路由生成完成。

  3. await router.push()
    • 使用 await 确保页面跳转完成,避免跳转过程中权限数据未完全加载。


额外优化建议

  1. userPermiss() 里返回 Promise
    • 确保 userPermiss() 返回 Promise,以便 await 能正确等待其完成:

    const userPermiss = async () => {
    try {const results = await http.get("/api/users/userPermiss");const userPermiss = results.data.results;store.commit("SET_PERMISS", userPermiss);return userPermiss; // 返回数据,方便后续处理
    } catch (error) {console.log(error);message.error(error.response.data.message);throw error; // 抛出错误,让外层 catch 捕获
    }
    };
    
  2. generateDynamicRoutes() 里也依赖 userPermiss
    • 如果动态路由依赖权限数据,可以确保权限加载完成后再生成路由:

    const generateDynamicRoutes = async () => {
    const permiss = store.state.userPermiss;
    if (!permiss.length) {await userPermiss(); // 如果权限未加载,先加载权限
    }
    // 生成动态路由的逻辑...
    };
    

总结

正确顺序

  1. 登录成功 → 存储 token 和用户信息
  2. await userPermiss()(确保权限加载完成)
  3. await generateDynamicRoutes()(确保动态路由生成)
  4. await router.push()(跳转页面)

这样可以避免权限未加载导致的页面权限控制失效或动态路由生成异常问题。

相关文章:

RBAC 权限控制:深入到按钮级别的实现

RBAC 权限控制&#xff1a;深入到按钮级别的实现 一、前端核心思路 1. 大致实现思路 后端都过SELECT连表查询把当前登录的用户对应所有的权限返回过来&#xff0c;前端把用户对应所有的权限 存起来to(vuex/pinia) 中 &#xff0c;接着前端工程师需要知道每个按钮对应的权限代…...

大模型格式化输出的几种方法

大模型格式化输出的几种方法 在开发一些和LLM相关的应用的时候,如何从大模型的反馈中拿到结构化的输出数据是非常重要的,那么本文就记录几种常用的方法。 OpenAI提供的新方法 在 OpenAI 的 Python 库中,client.beta.chat.completions.parse 是一个用于生成结构化输出的方法…...

【区间贪心】合并区间 / 无重叠区间 / 用最少数量的箭引爆气球 / 俄罗斯套娃信封问题

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;贪心算法 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 合并区间无重叠区间用最少数量的箭引爆气球俄罗斯套娃信封问题 合并区间 合并区间 class Solution { public:vector<vecto…...

JBDC java数据库连接(2)

目录 JBDC建立 获得PrepareStatement执行sql语句 形式&#xff1a; PrepareStatement中的方法: 实例 PreparedStatement和Statement 基于以下的原因: JBDC建立 获得PrepareStatement执行sql语句 在sql语句中参数位置使用占位符,使用setXX方法向sql中设置参数 形式&…...

es --- 集群数据迁移

目录 1、需求2、工具elasticdump2.1 mac安装问题解决 2.2 elasticdump文档 3、迁移 1、需求 迁移部分新集群没有的索引和数据 2、工具elasticdump Elasticdump 的工作原理是将输入发送到输出 。两者都可以是 elasticsearch URL 或 File 2.1 mac安装 前置&#xff1a;已经安装…...

Redis高频面试题及深度解析(20大核心问题+场景化答案)

摘要&#xff1a;Redis作为高性能缓存与内存数据库&#xff0c;是后端开发的核心技术栈之一。本文整理20大高频Redis面试题&#xff0c;结合真实场景与底层源码逻辑&#xff0c;助你彻底掌握Redis核心机制。涵盖单线程模型、集群方案、分布式锁、持久化等核心知识点。 一、Redi…...

事件处理程序

事件处理程序 一、事件处理程序的定义 事件处理程序是一段代码&#xff0c;用于响应特定的事件。在网页开发中&#xff0c;事件是在文档或浏览器窗口中发生的特定交互瞬间&#xff0c;如用户点击按钮、页面加载完成等。事件处理程序则是针对这些事件执行的函数&#xff0c;它能…...

stable diffusion部署ubuntu

stable-diffusion webui: https://github.com/AUTOMATIC1111/stable-diffusion-webui python3.10 -m venv venv&#xff08;3.11的下torch会慢得要死&#xff09; source venv/bin/activate 下载checkpoint模型放入clip_version"/home/chen/软件/stable-diffusion-webu…...

Qt的window注册表读写以及删除

Qt的window注册表读写以及删除 1. 使用 QSettings&#xff08;Qt推荐方式&#xff09;基本操作关键点限制 2. 调用Windows原生API示例&#xff1a;创建/读取键值常用API注意事项 3. 高级场景(1) 递归删除键(2) 注册表权限修改 4. 安全性建议总结其他QT文章推荐 在Qt中操作Windo…...

聊一聊接口测试时遇到上下游依赖时该如何测试

目录 一、手工测试时的处理方法 1.1沟通协调法 1.2模拟数据法 二、自动化测试时的处理方法 2.1 数据关联法&#xff08;变量提取&#xff09; 2.2 Mock数据法 2.3自动化框架中的依赖管理 三、实施示例&#xff08;以订单接口测试为例&#xff09; 3.1Mock依赖接口&…...

C++ 排序(1)

以下是一些插入排序的代码 1.插入排序 1.直接插入排序 // 升序 // 最坏&#xff1a;O(N^2) 逆序 // 最好&#xff1a;O(N) 顺序有序 void InsertSort(vector<int>& a, int n) {for (int i 1; i < n; i){int end i - 1;int tmp a[i];// 将tmp插入到[0,en…...

【有啥问啥】深入浅出讲解 Teacher Forcing 技术

深入浅出讲解 Teacher Forcing 技术 在序列生成任务&#xff08;例如机器翻译、文本摘要、图像字幕生成等&#xff09;中&#xff0c;循环神经网络&#xff08;RNN&#xff09;以及基于 Transformer 的模型通常采用自回归&#xff08;autoregressive&#xff09;的方式生成输出…...

zk基础—zk实现分布式功能

1.zk实现数据发布订阅 (1)发布订阅系统一般有推模式和拉模式 推模式&#xff1a;服务端主动将更新的数据发送给所有订阅的客户端。 拉模式&#xff1a;客户端主动发起请求来获取最新数据(定时轮询拉取)。 (2)zk采用了推拉相结合来实现发布订阅 首先客户端需要向服务端注册自己关…...

mySQL数据库和mongodb数据库的详细对比

以下是 MySQL 和 MongoDB 的详细对比&#xff0c;涵盖优缺点及适用场景&#xff1a; 一、核心特性对比 特性MySQL&#xff08;关系型数据库&#xff09;MongoDB&#xff08;文档型 NoSQL 数据库&#xff09;数据模型结构化表格&#xff0c;严格遵循 Schema灵活的文档模型&…...

ubuntu wifi配置(命令行版本)

1、查询当前设备环境的wifi列表 nmcli dev wifi list2、连接wifi nmcli dev wifi connect "MiFi-SSID" password "Password" #其中MiFi-SSID是wifi的密码&#xff0c;Password是wifi的密码3、查看连接情况 nmcli dev status...

Docker与Kubernetes在ZKmall开源商城容器化部署中的应用

ZKmall开源商城作为高并发电商系统&#xff0c;其容器化部署基于DockerKubernetes技术栈&#xff0c;实现了从开发到生产环境的全流程标准化与自动化。以下是核心应用场景与技术实现&#xff1a; 一、容器化基础&#xff1a;Docker镜像与微服务隔离 ​服务镜像标准化 ​分层构建…...

华为AI-agent新作:使用自然语言生成工作流

论文标题 WorkTeam: Constructing Workflows from Natural Language with Multi-Agents 论文地址 https://arxiv.org/pdf/2503.22473 作者背景 华为&#xff0c;北京大学 动机 当下AI-agent产品百花齐放&#xff0c;尽管有ReAct、MCP等框架帮助大模型调用工具&#xff0…...

MYSQL数据库语法补充

一&#xff0c;DQL基础查询 DQL&#xff08;Data Query Language&#xff09;数据查询语言&#xff0c;可以单表查询&#xff0c;也可以多表查询 语法&#xff1a; select 查询结果 from 表名 where 条件&#xff1b; 特点&#xff1a; 查询结果可以是&#xff1a;表中的字段…...

Elasticsearch单节点安装手册

Elasticsearch单节点安装手册 以下是一份 Elasticsearch 单节点搭建手册&#xff0c;适用于 Linux 系统&#xff08;如 CentOS/Ubuntu&#xff09;&#xff0c;供学习和测试环境使用。 Elasticsearch 单节点搭建手册 1. 系统要求 操作系统&#xff1a;Linux&#xff08;Cent…...

在Windows搭建gRPC C++开发环境

一、环境构建 1. CMake Download CMake 2. Git Git for Windows 3. gRPC源码 git clone -b v1.48.0 https://github.com/grpc/grpc 进入源码目录 cd grpc 下载依赖库 git submodule update --init 二、使用CMake生成工程文件 三、使用vs2019编译grpc库文件 四、使用…...

[Python] 企业内部应用接入钉钉登录,端内免登录+浏览器授权登录

[Python] 为企业网站应用接入钉钉鉴权&#xff0c;实现钉钉客户端内自动免登授权&#xff0c;浏览器中手动钉钉授权登录两种逻辑。 操作步骤 企业内部获得 开发者权限&#xff0c;没有的话先申请。 访问 钉钉开放平台-应用开发 创建一个 企业内部应用-钉钉应用。 打开应用…...

编程题学习

acwing 826. 单链表 #include <iostream>using namespace std;const int N 100010;int idx, e[N], ne[N], head;void init() {head -1;idx 0; }void insert_head(int x) {e[idx] x;ne[idx] head;head idx ; }void delete_k_pos(int x, int k) {e[idx] x;ne[idx…...

Dev C++单个源文件和项目两种编程方式介绍

Dev C单个源文件和项目两种编程方式介绍 Dev-C 是一款免费、开源的 C/C 集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为初学者和中级程序员设计&#xff0c;具有简单易用、功能丰富等特点。 Dev C 支持单文件编程和项目编程两种方式。它们之间的主要区别在于如何组…...

用AbortController取消事件绑定

视频教程 React - &#x1f914; Abort Controller 到底是什么神仙玩意&#xff1f;看完这个视频你就明白了&#xff01;&#x1f4a1;_哔哩哔哩_bilibili AbortController的好处之一是事件绑定的函数已无需具名函数,匿名函数也可以被取消事件绑定了 //该代码2秒后点击失效…...

解决:Fontconfig head is null, check your fonts or fonts configurat

文章目录 问题解决方案安装字体依赖包强制刷新字体缓存验证是否生效 个人简介 问题 在使用 Java 环境部署或运行图形相关应用时&#xff0c;比如图片验证码&#xff0c;偶尔会遇到如下报错&#xff1a; Fontconfig head is null, check your fonts or fonts configurat意味当…...

this指针 和 类的继承

一、this指针 Human类的属性fishc与Human&#xff08;&#xff09;构造器的参数fishc同名&#xff0c;但却是两个东西。使用this指针让构造器知道哪个是参数&#xff0c;哪个是属性。 this指针&#xff1a;指向当前的类生成的对象 this -> fishc fishc当前对象&#xff08;…...

无锡无人机驾驶证培训费用

无锡无人机驾驶证培训费用&#xff0c;随着科技的迅速发展&#xff0c;无人机在众多行业中发挥着举足轻重的作用。从影视制作到农业监测&#xff0c;再到物流运输与城市规划&#xff0c;无人机的应用场景不断扩展&#xff0c;因此越来越多的人开始意识到学习无人机驾驶技能的重…...

反向查询详解以Django为例

以下给出两张表格 class User(AbstractUser):mobilemodels.CharField(max_length11,default0,uniqueTrue,verbose_name手机号)email_activemodels.BooleanField(defaultFalse,verbose_name邮箱验证状态)default_address models.ForeignKey(Address, related_nameusers, nullT…...

我们如何思考AI创业投资

&#x1f3ac; Verdure陌矣&#xff1a;个人主页 &#x1f389; 个人专栏: 《C/C》 | 《转载or娱乐》 &#x1f33e; 种完麦子往南走&#xff0c; 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ 声明&#xff1a;本文作者转载&#xff0c;原文出自…...

详解在 MySQL 中建索引时的注意事项

MySQL 中建索引时的注意事项 1. 索引的必要性与设计2. 复合索引与列顺序3. 索引数量与维护4. 索引类型选择5. 特殊注意事项 1. 索引的必要性与设计 使用场景&#xff1a;优先为在 WHERE、JOIN、ORDER BY 和 GROUP BY 中频繁使用的列创建索引。合理的索引设计能显著提升查询效率…...