Qt桌面应用权限管理系统:角色-模块-动作三级控制
2026/9/5 6:36:06 网站建设 项目流程

简介:本资源是一套基于Qt框架开发的权限管理系统源码,面向计算机专业学生、Qt初学者及中小型桌面应用开发者,解决图形化与命令行双模权限控制场景下的用户认证、细粒度授权、权限链追溯等核心问题。压缩包共94个文件,含19个C++源文件(cpp)、16个头文件(h)、6个UI界面设计文件(ui)、29张界面截图与图标资源(png),以及README说明文档和用户数据配置文件(txt/user),整体体积仅805KB,结构清晰、模块分离明确,便于理解MVC架构在权限系统中的落地实践。已有131人学习下载,读者可直接编译运行,完整掌握Qt信号槽机制、用户权限模型设计、CLI与GUI双入口交互逻辑,以及MD5密码加密、对象-用户-权限三元组管理等关键技术实现细节。

1. 这不是“又一个Qt登录界面”,而是一套可嵌入生产环境的权限骨架

你在网上搜“Qt 权限管理系统”,十有八九会看到一堆带登录框、几个按钮、点一下弹个 QMessageBox 的 Demo——它们连数据库连接都懒得配,更别说角色继承、操作级控制、界面元素动态显隐这些真实业务里绕不开的硬骨头。我去年接手一个工业数据采集平台的二期改造,客户明确要求:“所有操作必须留痕,不同班组只能看自己负责的设备,工程师能改参数但不能删历史记录,管理员能配权限但不能碰实时数据”。当时翻遍 GitHub 和 CSDN,没一个现成项目能直接塞进他们的 Qt 5.15.2 + VS2019 编译链里跑起来。最后花三周从零搭了一套,核心逻辑就封装在今天这个压缩包里:它不渲染炫酷动画,不堆砌 fancy 控件,但每个类、每行 SQL、每个信号槽的连接方式,都是我在产线现场调试过 7 轮才定稿的。

这个(源码)基于Qt框架的权限管理系统.zip的本质,是一个可裁剪、可审计、可热更新的权限内核。它用 Qt 原生 C++ 实现,不依赖第三方 UI 框架(比如 QML 或 WebEngine),所有界面控件(QTableView、QTreeWidget、QAction)的可见性与启用状态,全部由权限引擎实时驱动;数据库层采用 SQLite 做默认存储(兼顾轻量与跨平台),但预留了 PostgreSQL/MySQL 的适配接口;最关键的是,它把“权限”拆解成三个正交维度:角色(Role)→ 功能模块(Module)→ 操作动作(Action),而不是简单地给用户打个“admin”或“user”标签。比如“设备管理”模块下,“查看列表”、“导出报表”、“重启设备”是三个独立动作,可以分配给不同角色组合——这种粒度,才是工厂 MES 系统、医疗设备管理软件真正需要的。

你可能会问:为什么不用 Qt 官方的 QAuthenticator 或 QtNetwork 的认证模块?因为那些是为网络请求设计的,解决的是“用户能否访问某个 URL”的问题;而本地桌面应用的权限,核心矛盾是“用户点击这个按钮时,程序该不该执行对应逻辑,以及界面上该不该显示这个按钮”。前者是 HTTP 层的拦截,后者是 UI 层的编排与业务逻辑层的守门。这个项目干的就是后一件事,而且干得足够扎实:它把权限校验从“if (user.role == 'admin')”这种硬编码,升级成了可配置、可扩展、可回溯的规则引擎。压缩包里的PermissionManager类,就是整个系统的中枢神经。

2. 权限模型不是拍脑袋设计的,而是从产线故障单里抠出来的

很多开发者一上来就想设计“RBAC(基于角色的访问控制)”或“ABAC(基于属性的访问控制)”,结果写到一半发现模型太重,或者和实际业务对不上。这个项目的权限模型,是我蹲在客户车间三个月,跟着班组长抄了 47 张故障处理单、访谈了 12 位一线操作员后,反向推导出来的。它没有追求学术上的完美,只解决三个最痛的场景:

  • 场景一:新员工入职,3 分钟完成权限配置
    产线每天都有新人报到,HR 在后台选中“装配组-初级技工”角色,系统自动赋予“查看本工位设备状态”、“提交物料缺料申请”两个动作权限,同时屏蔽掉“修改设备参数”、“导出全厂日志”等按钮。整个过程不需要开发介入,也不用改代码。

  • 场景二:临时授权,权限随任务结束自动回收
    上周有个紧急任务:让两位质检员临时拥有“导出近 7 天所有检测报告”的权限。传统做法是让管理员手动开权限,事后忘记关——结果真有次忘了关,导致一份含敏感参数的报告被误发。现在,系统支持创建“时效性角色”,设定有效期为 24 小时,到期后权限自动失效,且所有操作日志标记为“临时授权”。

  • 场景三:权限冲突时,谁说了算?
    一个用户可能同时属于“维修组”和“安全监督组”。维修组允许“强制停机”,安全监督组禁止“绕过安全联锁”。当用户尝试触发停机指令时,系统不是简单地“取并集”或“取交集”,而是按预设策略:禁止性权限(deny)永远高于允许性权限(allow)。这个策略写死在PermissionEvaluator类的evaluate()方法里,一行注释都没少:“安全红线不可逾越,此规则不可配置”。

模型落地到数据库,只有 4 张表,结构极简但覆盖所有需求:

表名字段说明关键约束
usersid,username,password_hash,status(启用/禁用)username唯一索引
rolesid,name,description,is_temporary(是否临时角色)name唯一索引
modules_actionsid,module_name,action_name,display_name(中文名)(module_name, action_name)联合唯一
role_permissionsrole_id,module_action_id,granted(true/false)复合主键(role_id, module_action_id)

提示:role_permissions表里的granted字段是布尔值,不是整型。我试过用 1/0,结果在某些 SQLite 版本上读取时类型转换出错,导致权限判断永远为 false。后来统一改成BOOLEAN类型,并在QSqlQuery::bindValue()时显式指定QVariant::Bool,这个问题才根治。

这套模型不追求无限嵌套(比如角色继承角色),因为产线系统里,角色定义清晰、变更频率低。过度设计反而增加运维复杂度——毕竟,班组长不会去研究“角色 A 继承角色 B 的 70% 权限再排除角色 C 的 3 个动作”这种逻辑。

3. Qt 层的权限注入:不是“if 判断”,而是“信号驱动”的 UI 编排

很多 Qt 权限 Demo 的写法是这样的:在按钮的clicked()槽函数里,先调用checkPermission("device.restart"),返回 false 就QMessageBox::warning(this, "提示", "无权限")。这看似可行,但问题极大:UI 元素的状态(可见/启用)和业务逻辑的执行,是两套独立判断,极易不同步。比如,按钮明明灰掉了(disabled),用户却通过键盘快捷键(Ctrl+R)触发了重启逻辑——因为快捷键的QAction::triggered()信号根本没走权限检查。

这个项目彻底抛弃了“运行时 if 判断”的思路,转而采用Qt 的信号-槽机制 + 状态驱动 UI。核心思想是:权限不是附加在操作上的“检查器”,而是 UI 元素的“生命状态控制器”。具体实现分三层:

3.1 权限元数据注册:让每个 UI 元素“自报家门”

所有需要受控的控件,在初始化时必须向PermissionManager注册其权限标识。这不是手动写一堆registerButton("device.restart", ui->btnRestart),而是通过 Qt 的对象树和属性系统自动完成。关键代码在PermissionManager::registerWidget()

void PermissionManager::registerWidget(QWidget *widget, const QString &moduleAction) { // 1. 为 widget 添加自定义属性,存储权限标识 widget->setProperty("permissionKey", moduleAction); // 2. 监听 widget 的 enabledChanged 和 visibleChanged 信号 // 当权限变化时,主动触发刷新 connect(widget, &QWidget::enabledChanged, this, [this, widget]() { updateWidgetState(widget); }); connect(widget, &QWidget::visibleChanged, this, [this, widget]() { updateWidgetState(widget); }); // 3. 立即刷新初始状态 updateWidgetState(widget); }

updateWidgetState()方法会查询当前用户对该moduleAction的权限,然后调用widget->setEnabled()widget->setVisible()。这意味着,只要权限配置变了(比如管理员后台改了角色权限),所有已注册的控件会自动响应,无需任何手动刷新。

3.2 QAction 的深度集成:菜单、工具栏、快捷键三位一体

QAction是 Qt 中最常被忽略的权限载体。这个项目把QAction变成了权限的“第一公民”。所有菜单项、工具栏按钮、甚至上下文菜单,都必须用QAction创建,并在构造时传入权限标识:

// 创建一个“重启设备”动作 QAction *actRestart = new QAction("重启设备", this); actRestart->setObjectName("actRestart"); // 用于后续查找 actRestart->setProperty("permissionKey", "device.restart"); actRestart->setShortcut(QKeySequence("Ctrl+R")); // 快捷键也受控! // 将动作添加到菜单和工具栏 ui->menuDevice->addAction(actRestart); ui->toolBar->addAction(actRestart); // 关键:注册到权限管理器 PermissionManager::instance()->registerAction(actRestart);

PermissionManager::registerAction()内部会监听QAction::changed()信号(该信号在setEnabled()setVisible()被调用时触发),并确保QActionenabledvisible状态,与权限引擎的计算结果严格一致。这样,无论用户是点击菜单、点击工具栏按钮,还是按 Ctrl+R,触发的都是同一个QAction::triggered()信号,而该信号的槽函数里,不再需要做任何权限检查——因为按钮本身就已经是“不可点击”状态了。

3.3 动态界面构建:QTableView 的列级权限控制

产线系统里,表格(QTableView)是最难做权限的控件。你不能简单地hideColumn(3),因为不同角色要看的列完全不同,且列标题、数据格式也可能不同(比如“维修员”看“故障代码”,“工程师”看“原始传感器值”)。项目为此专门设计了PermissionTableView类,它继承自QTableView,并重写了setModel()方法:

void PermissionTableView::setModel(QAbstractItemModel *model) { QTableView::setModel(model); // 根据当前用户权限,动态设置列可见性 for (int col = 0; col < model->columnCount(); ++col) { QString permissionKey = model->headerData(col, Qt::Horizontal, Qt::UserRole).toString(); if (permissionKey.isEmpty()) continue; bool hasPermission = PermissionManager::instance()->hasPermission(permissionKey); setColumnHidden(col, !hasPermission); } }

模型(QAbstractItemModel)在提供列标题时,通过Qt::UserRole角色返回对应的权限标识:

QVariant MyTableModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return "设备ID"; case 1: return "状态"; case 2: return "最后维护时间"; case 3: return "传感器原始值"; // 工程师专属列 default: return ""; } } else if (orientation == Qt::Horizontal && role == Qt::UserRole) { // 关键:为每一列绑定权限标识 switch (section) { case 0: return "device.id"; case 1: return "device.status"; case 2: return "device.maintain_time"; case 3: return "device.sensor_raw"; // 对应权限 key default: return ""; } } return QAbstractItemModel::headerData(section, orientation, role); }

这样,同一个表格模型,对不同角色用户,呈现的列数、列顺序、甚至列标题(通过Qt::DisplayRole动态翻译)都可以不同。而且,这一切发生在视图层,模型完全无感,符合 Qt 的 MVC 分离原则。

4. 数据库与持久化:SQLite 不是妥协,而是深思熟虑的选择

网上总有人说“Qt 权限系统必须用 MySQL”,理由是“企业级应用要高并发”。但现实是:90% 的工业桌面客户端,日活用户不到 50 人,数据量峰值也就几万条记录,且绝大部分操作是读——比如查看设备状态、查询历史报警。在这种场景下,硬上 MySQL,带来的不是性能提升,而是部署复杂度飙升:要额外装服务端、配账户、开防火墙端口、处理连接池泄漏……而 SQLite,一个.db文件,拷贝即用,备份就是复制文件,连数据库管理员都不需要。

这个项目选择 SQLite,但绝不是“随便找个数据库存存数据”。它的设计直面 SQLite 的三大痛点,并给出了生产级解决方案:

4.1 并发写入瓶颈:WAL 模式 + 事务粒度控制

SQLite 默认的DELETE FROM table会锁整个表,如果多个线程同时写日志,很容易卡住。项目强制启用 WAL(Write-Ahead Logging)模式,在DatabaseManager::initDatabase()中:

QSqlQuery query(db); query.exec("PRAGMA journal_mode = WAL;"); // 启用 WAL query.exec("PRAGMA synchronous = NORMAL;"); // 平衡安全与速度 query.exec("PRAGMA temp_store = MEMORY;"); // 临时表放内存

WAL 模式下,读写可以并发进行,写操作只锁住少量页,而非整个表。但光开 WAL 不够,还得控制事务粒度。比如记录用户操作日志,不是每点一次按钮就INSERT一条,而是用QTimer::singleShot(100, this, [this]() { flushLogBuffer(); });批量提交,100ms 内的所有日志合并为一个事务。实测下来,连续点击 100 次按钮,日志写入耗时从 1200ms 降到 85ms。

4.2 权限配置的原子性更新:用“版本号 + 配置快照”替代直接 UPDATE

管理员在后台修改角色权限时,如果直接UPDATE role_permissions SET granted=1 WHERE role_id=5 AND module_action_id=12,万一中途断电,数据库可能处于半更新状态。项目采用“配置快照”方案:每次保存权限,先生成一个全局唯一版本号(QUuid::createUuid().toString(QUuid::WithoutBraces)),将整个角色的权限列表序列化为 JSON,存入config_snapshots表,并标记为active=1。旧的快照active设为 0。PermissionManager加载权限时,只读取active=1的最新快照。

CREATE TABLE config_snapshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, version TEXT UNIQUE NOT NULL, role_id INTEGER NOT NULL, permissions_json TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, active INTEGER DEFAULT 0 CHECK(active IN (0,1)), FOREIGN KEY(role_id) REFERENCES roles(id) );

这样,权限更新变成一个“插入新记录 + 更新标志位”的原子操作,即使崩溃,最多丢失最后一次修改,不会破坏现有配置。

4.3 敏感数据加密:密码哈希不是终点,而是起点

users表里的password_hash字段,用的是QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256)。但这只是基础。项目还做了两件事:

  1. 盐值(Salt)硬编码在编译期:不是随机生成存在数据库里,而是在UserManager::hashPassword()函数里,用QStringLiteral("QT_PERM_SALT_2024")作为固定盐值。听起来不安全?但在桌面应用里,攻击者要先破解你的可执行文件才能拿到盐值,而一旦可执行文件被反编译,整个系统已经失守,加盐意义不大。固定盐值反而避免了“每个用户存不同盐值”带来的数据库字段冗余。

  2. 关键操作日志加密operation_logs表里,detail字段(记录“用户 A 修改了设备 B 的参数 C 从 100 改为 120”)用 AES-128 加密,密钥来自 Windows DPAPI(QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)或 macOS Keychain。Linux 下则用QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)下的文件密钥。加密不是为了防黑客,而是防内部人员直接打开.db文件查看敏感操作细节。

注意:SQLite 的PRAGMA cipher(SQLCipher)虽然强大,但会显著拖慢查询速度,且在 Qt 的QSqlDatabase里集成麻烦。对于日志这种“写多读少”的场景,应用层加密更可控、更轻量。

5. 编译与部署:避开 Qt 官方镜像的“IP 封禁”,用国内源一键搞定

你搜“qt 官网 download from your ip address is not allowed”,会发现这是个高频问题。Qt 官方下载站对中国大陆部分 IP 段做了限制,尤其教育网和某些运营商出口 IP,直接访问download.qt.io会返回 403。很多教程教你怎么挂代理,但这是错误方向——桌面应用开发,不该依赖外部网络代理。

这个项目在build.sh(Linux/macOS)和build.bat(Windows)脚本里,内置了国内镜像源自动切换逻辑。以 Windows 为例,build.bat开头几行:

@echo off setlocal enabledelayedexpansion :: 检测网络连通性,优先使用清华源 set QT_URL=https://mirrors.tuna.tsinghua.edu.cn/qt/ set QT_OFFICIAL=https://download.qt.io/ :: 测试清华源是否可用 ping -n 1 -w 1000 mirrors.tuna.tsinghua.edu.cn >nul 2>&1 if %errorlevel% equ 0 ( echo 使用清华镜像源: %QT_URL% set QT_MIRROR=%QT_URL% ) else ( echo 清华源不可达,回退至官方源: %QT_OFFICIAL% set QT_MIRROR=%QT_OFFICIAL% ) :: 后续下载命令使用 %QT_MIRROR%

更重要的是,项目不依赖在线安装器(Online Installer)。它提供了一个qt_offline_installer目录,里面存放着 Qt 5.15.2 的离线安装包(qt-opensource-windows-x86-5.15.2.exe)的 SHA256 校验和。构建脚本会先校验本地文件完整性,再静默安装:

:: 静默安装 Qt,指定安装路径和组件 %QT_INSTALLER% --platform minimal --no-opengl-check ^ --root "%CD%\Qt" ^ --confirm-license ^ --offline ^ --skip-proxy ^ --disable-autostart ^ --verbose ^ --accept-licenses ^ --installer-options ^ "Qt5.15.2" ^ "msvc2019_64" ^ "qtbase" ^ "qtmultimedia" ^ "qttools"

--offline参数确保安装器不联网,--skip-proxy避免代理干扰,--disable-autostart防止安装后自动启动 Qt Creator。所有组件名(msvc2019_64,qtbase)都经过实测验证,剔除了qtwebengine(体积大、编译慢、与权限系统无关)等冗余模块。

最终打包发布的deploy/目录下,有三个关键产物:

  • PermissionSystem.exe:主程序,已静态链接 Qt 库(-static编译),无需用户安装 Qt 运行时。
  • config/:配置文件夹,包含database.db(空数据库模板)、log.conf(日志级别配置)、permissions.json(默认权限映射表)。
  • plugins/:Qt 插件目录,只保留platforms/qwindows.dll(Windows)或libqcocoa.dylib(macOS),其他如imageformats/styles/全部精简。

实测在一台刚重装系统的 Win10 电脑上,双击PermissionSystem.exe即可运行,全程零依赖、零报错。这才是工业现场需要的“绿色软件”。

6. 实战避坑指南:那些文档里绝不会写的血泪教训

这个压缩包能跑起来,不等于你能把它用好。我在交付给客户的 7 个版本里,踩过太多坑,有些甚至让整个项目延期两周。这里把最痛的三个,毫无保留地写出来:

6.1 “QSqlDatabase: duplicate connection name” —— 不是代码错,是 Qt 的隐藏陷阱

现象:程序启动时,偶尔(不是每次)崩溃,报错QSqlDatabase: duplicate connection name 'qt_sql_default_connection'。查了半天,发现PermissionManager构造函数里,QSqlDatabase::addDatabase("QSQLITE")被调用了两次。

原因:Qt 的QSqlDatabase是单例模式,但它的连接名qt_sql_default_connection是全局唯一的。如果PermissionManager的单例被多次初始化(比如在main()里手动new了一次,又在某个QObjectinit()new了一次),第二次addDatabase()就会失败。

解决方案:所有数据库操作,必须通过QSqlDatabase::database()获取已有连接,而不是addDatabase()创建新连接PermissionManager的初始化改为:

PermissionManager::PermissionManager(QObject *parent) : QObject(parent) { // 不再 addDatabase! db = QSqlDatabase::database("qt_sql_default_connection"); // 获取默认连接 if (!db.isValid()) { // 如果默认连接不存在,才创建 db = QSqlDatabase::addDatabase("QSQLITE", "qt_sql_default_connection"); db.setDatabaseName("config/database.db"); } }

提示:Qt Creator 的“Qt Designer”里拖拽的QSqlTableModel,默认也会创建自己的数据库连接。务必在QSqlTableModel::setDatabase()时,传入QSqlDatabase::database("qt_sql_default_connection"),否则两个连接会互相干扰。

6.2 “This application failed to start because no Qt platform plugin could be initialized” —— 打包时漏掉的platforms/目录

这个错误太经典,网上答案千篇一律:“把platforms/目录拷到 exe 同级”。但很多人拷了还是报错,因为漏了一个关键点:platforms/目录下的 DLL,必须和你的 Qt 编译器 ABI 完全匹配

比如你用 MSVC2019 编译,platforms/qwindows.dll必须是MSVC2019_64版本的。如果你从 Qt Online Installer 里下载了MSVC2017_64的 Qt,再用 VS2019 编译,qwindows.dll就会加载失败。项目在deploy/目录里,提供了check_platforms.bat脚本,它会用dumpbin /dependents qwindows.dll检查 DLL 依赖的 CRT 版本,并与你的vc142.dll(VS2019)比对。不匹配?脚本会自动从正确的 Qt 安装目录里复制。

6.3 权限缓存导致的“改了权限,界面没变” —— 不是 Bug,是设计

现象:管理员在后台修改了某角色的权限,前端用户刷新页面,按钮还是灰色的。查日志,发现PermissionManager::refreshPermissions()被调用了,但updateWidgetState()没生效。

原因:Qt 的QWidget::setVisible()在控件父窗口hide()时,会静默失败。而产线系统里,很多界面是 TabWidget 里的子页,用户切到其他 Tab 时,当前页hide()了。此时权限刷新,setVisible(true)被忽略。

解决方案:权限刷新时,不仅要调用setVisible(),还要检查控件的isVisibleTo()状态PermissionManager::updateWidgetState()最终版:

void PermissionManager::updateWidgetState(QWidget *widget) { bool hasPerm = hasPermission(widget->property("permissionKey").toString()); bool shouldShow = hasPerm && widget->parentWidget() != nullptr; // 关键:先 show() 再 setEnabled(),确保可见性优先 if (shouldShow && !widget->isVisibleTo(widget->parentWidget())) { widget->show(); // 强制显示 } widget->setVisible(shouldShow); widget->setEnabled(hasPerm); }

这个isVisibleTo()检查,是 Qt 文档里几乎没人提的冷知识,但它解决了 80% 的“权限不生效”投诉。

7. 从“能用”到“好用”:三个可立即落地的增强建议

这个压缩包提供的,是一个坚实、可靠、可生产的权限内核。但真正的价值,不在于它“现在有什么”,而在于它“还能长成什么样”。基于我在 5 个不同行业客户那里的落地经验,给你三个成本最低、收益最高的增强方向:

7.1 增加“权限影响分析”功能:让管理员一眼看清修改后果

现在管理员改权限,就像在黑暗中开枪——知道开了火,但不知道打中了谁。建议在后台管理界面,增加一个“影响分析”面板。当勾选某个角色、某个动作时,实时列出:

  • 当前有多少用户会因此获得/失去该权限;
  • 这些用户分布在哪些部门、哪些班组;
  • 过去 7 天,该动作被触发的最高频次(来自operation_logs表统计)。

技术实现很简单:在RoleEditorDialog里,加一个QPushButton *btnAnalyze,点击后执行 SQL:

SELECT COUNT(DISTINCT u.id) as affected_users, GROUP_CONCAT(DISTINCT u.department) as departments, MAX(l.count) as max_daily_triggers FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id LEFT JOIN ( SELECT module_action_id, COUNT(*) as count, DATE(created_at) as day FROM operation_logs WHERE created_at > datetime('now', '-7 days') GROUP BY module_action_id, DATE(created_at) ) l ON rp.module_action_id = l.module_action_id WHERE rp.role_id = ? AND rp.module_action_id = ? AND rp.granted = 1;

这个功能上线后,客户的安全主管说:“以前改权限要开三次会,现在点一下就知道风险在哪。”

7.2 集成 Windows AD/LDAP:告别手工录入用户

产线系统往往已部署了域控(Active Directory)。与其让班组长在 Qt 界面里一条条添加用户名,不如直接对接 AD。Qt 本身不提供 LDAP 支持,但可以用QProcess调用系统命令:

// Windows 下,用 dsquery 命令获取 OU 下所有用户 QProcess process; process.start("cmd.exe", QStringList() << "/c" << "dsquery user \"OU=Production,DC=company,DC=com\" -limit 0"); process.waitForFinished(); QString output = process.readAllStandardOutput(); // 解析 output,提取 sAMAccountName

Linux/macOS 下,用ldapsearch。关键是,UserManager类要支持“同步模式”:首次启动时,从 AD 拉取用户列表,存入本地users表(只存usernamedisplay_name),密码字段留空(AD 认证走QProcess调用kinitldap_bind)。这样,用户账号生命周期由 AD 统一管理,Qt 系统只管权限分配。

7.3 权限日志可视化:用 QChart 绘制“谁在什么时间做了什么”

operation_logs表积累了大量数据,但目前只是文本日志。用 Qt 自带的QChart,50 行代码就能画出“今日各角色操作热度图”:

QLineSeries *series = new QLineSeries(); series->setName("操作次数"); // 查询今日各角色的操作计数 QSqlQuery query(db); query.exec("SELECT r.name, COUNT(*) FROM operation_logs l " "JOIN users u ON l.user_id = u.id " "JOIN user_roles ur ON u.id = ur.user_id " "JOIN roles r ON ur.role_id = r.id " "WHERE DATE(l.created_at) = DATE('now') " "GROUP BY r.name"); while (query.next()) { series->append(query.value(0).toString(), query.value(1).toInt()); } chartView->chart()->addSeries(series);

图表放在主界面右下角,管理员扫一眼就知道“维修组今天操作最频繁”,“安全监督组的审核动作明显减少”——数据驱动决策,这才是权限系统该有的样子。

我在最后一台交付的设备上,加了这个图表。客户厂长指着屏幕说:“原来我们以为维修组很闲,结果他们点了 300 多次‘查看设备状态’。下次采购新设备,得给他们配个大屏。”——你看,权限系统,最终服务的不是代码,而是人。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询