ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LabVIEW与ACCESS数据库构建用户登录与权限管理系统的实践指南

LabVIEW与ACCESS数据库构建用户登录与权限管理系统的实践指南 1. 项目背景与整体设计思路1.1 为什么选择LabVIEW ACCESS这套组合接手这个项目的时候甲方只提了一个要求在LabVIEW里做一个像样的用户登录系统别像Demo那样糊弄人。其实这种需求在工控项目里很常见——上位机软件总不能谁打开都能改参数、看历史数据哪怕只是个界面程序最基本的权限还是要有的。选ACCESS数据库而不是MySQL或者SQL Server原因很简单。第一ACCESS是文件型数据库一个.mdb文件拷到哪里都能带走对labVIEW开发的单机版上位机来说部署成本几乎为零不用装数据库服务端客户那边也不用买授权。第二LabVIEW对ACCESS的访问路径非常成熟走ODBC或者ADO都行资料多遇到问题也好查。第三这套系统的用户量不会很大正常一个车间里十几个操作员ACCESS完全扛得住没必要为了一个小登录功能去塞一个重型数据库。但说实话LabVIEW写登录系统比C#或者Python要费劲不少。G语言里没有现成的登录控件所有东西都要自己搭。面板布局、事件触发、数据库读写、密码加密、会话状态管理每一块都要自己动手。这篇文章把我做这套用户登录源程序的完整思路、代码架构、踩过的坑都整理出来尽量做到你能照着复现。1.2 功能需求拆解与模块划分动工之前我把需求明确拆成了几块用户登录输入用户名和密码校验通过后进入主界面校验失败给出明确的错误提示。用户管理管理员登录后可以新增用户、删除用户、修改密码、重置密码。权限分级至少分两级管理员和普通操作员普通用户不能进入用户管理界面。会话记录记录登录时间、登录用户、操作动作方便事后追踪。这个功能拆解看着简单但实际开发中每一块都有坑。比如密码校验不能直接在SQL里拼明文至少要做个哈希再比如用户管理界面删用户的时候必须考虑当前登录的账号不能自删权限控制也不是简单藏一个按钮而是要在后台逻辑里做判断防止绕过界面直接调用VI。架构上我分了四层界面层Login.vi Main.vi UserManager.vi、业务逻辑层UserService.vi、数据访问层DatabaseConnection.vi UserDAO.vi、公共模块层MD5加密、配置读取、日志组件。这样分层的好处是以后如果要换数据库引擎只需要改数据访问层界面和逻辑都不用动。2. ACCESS数据库设计与连接配置2.1 数据表结构与字段设计数据库我设计了三张表没有更多了再多就属于过度设计。用户表tbl_User字段名数据类型含义备注UserID自动编号用户ID主键UserName文本(50)登录用户名唯一索引PasswordHash文本(64)密码哈希值存SHA256结果RealName文本(50)真实姓名显示用RoleID数字权限角色1管理员2操作员IsActive是/否账号是否启用停用后不能登录CreateTime日期/时间创建时间默认Now()LastLoginTime日期/时间最后登录时间登录成功时更新日志表tbl_Log字段名数据类型含义LogID自动编号日志IDUserName文本(50)操作人ActionType文本(20)登录/登出/新增/修改/删除ActionDetail文本(255)操作详情LogTime日期/时间操作时间这里有个细节密码不存明文存的是SHA256哈希值。有人可能会说ACCESS本身不算安全存哈希多此一举——但恰恰因为ACCESS文件容易被拷走密码才更不能裸奔。如果整个.mdb被拷走了对方拿到的只是一堆哈希值解不出原始密码。2.2 LabVIEW连接ACCESS的三种方式对比LabVIEW访问ACCESS数据库常见的有三种方式我都试过下面说下实际感受。方式一使用NI的Database Connectivity Toolkit。这是最省事的不是直接sql开小灶是连字符串拼接。用了参数化查询之后用户在用户名输入框里敲; DROP TABLE tbl_User;--这种内容只会被当成一个普通的字符串处理数据库中不会真的执行删表操作。我实际测试过参数化查询对ACCESS和MySQL都有效LabVIEW的数据库工具包在底层把参数安全地传递给了数据库驱动。如果你用的是固定字符串连接SQL建议无论如何都要改成参数化方式重新做一遍。3.2 登录成功与失败的逻辑分支处理校验通过后下面这几步一个都不能少更新LastLoginTime字段写入当前时间。查一次该用户的RealName和RoleID一起传给主界面。把当前用户名写进全局变量或功能全局变量后续所有操作都需要用它。写入登录日志。这里特别提一下功能全局变量FGV这个概念。LabVIEW里跨VI共享数据有很多办法用全局变量最简单但容易产生竞争条件多线程运行时数据会被覆盖。FGV是在一个非重入VI里加一个未初始化移位寄存器来存储数据利用LabVIEW单线程执行VI的特性天然避免竞争问题。我强烈建议用FGV保存当前登录用户信息不要用普通的全局变量。失败分支要做到三点密码错误和用户不存在要给出不同的提示但不要提示得太细。我实测过如果系统提示用户名不存在攻击者就可以通过不断试探来枚举有效用户名。统一提示用户名或密码错误更安全。连续输错5次锁定该账号15分钟。这个逻辑需要额外建一张锁定表或者在用户表加LockUntil字段实现不复杂但非常有效。记录失败日志日志里写明用户名、IP如果是本机就写Localhost、失败时间。我见过很多LabVIEW登录Demo登录失败只弹一个对话框就完了没有任何记录这在生产环境里是灾难。万一真的出事了连个排查线索都没有。3.3 登录状态保持与主界面会话管理登录成功之后用户身份信息要伴随整个软件生命周期。我的做法是登录成功那一刻把用户名、RealName、RoleID打包写入一个自定义簇存进Session FGV主界面和用户管理界面都从Session FGV读取当前身份。权限控制的落点在两个层次界面层管理员能看到用户管理按钮操作员看到的这个按钮是禁用或者隐藏的。逻辑层用户管理VI启动时再次检查当前会话的RoleID如果不是管理员直接弹窗拒绝并退出。防止有人通过其他方式直接调用这个VI。我在实际项目里就遇到过操作员通过Shift键调出了调试窗口然后直接运行UserManager.vi因为界面层按钮隐藏了但VI本身没有做权限验证结果操作员还是进了用户管理界面。从那以后我的所有受保护VI都在开头加了一小段权限检查代码这几行代码成本极低但非常关键。4. 用户管理功能开发4.1 用户列表展示与刷新机制用户管理界面核心是用户列表。我用的控件是LabVIEW自带的表格控件Table列设计为用户ID、用户名、真实姓名、角色、状态、创建时间。表格只用于展示数据的更改通过独立的对话框完成。读取用户列表的SQLSELECT UserID, UserName, RealName, RoleID, IsActive, CreateTime FROM tbl_User ORDER BY UserID;拿到查询结果后逐行写入表格控件。这里有一个显示层面的处理RoleID是整数但界面上要显示管理员或操作员需要在循环里做一次映射不能直接把数字显示出来。IsActive字段同理要显示启用/停用而不是TRUE/FALSE。刷新机制上每次打开用户管理界面以及每次完成新增/编辑/删除之后都要重新执行一次查询。一开始我用的是手动刷新按钮方案后来发现实操中经常有人忘了点刷新以为操作没成功又重复提交。后来我改成任何写操作完成后自动刷新并且刷新完成后状态栏提示数据已更新体验好很多。4.2 新增用户、编辑信息、删除与重置密码新增用户的逻辑是打开新增对话框输入用户名、真实姓名、密码、确认密码。前端校验用户名不能为空、密码不能少于6位、两次密码必须一致。后端校验查询SELECT COUNT(*) FROM tbl_User WHERE UserName ...如果大于0提示用户名已存在。通过后密码做SHA256哈希处理连同用户名、真实姓名、角色默认为操作员、启用状态默认为启用一起写入数据库。编辑用户的逻辑主要是修改真实姓名、角色、启用状态。这里注意用户名创建后尽量不允许修改。因为用户名是业务的唯一标识修改用户名意味着要同步修改日志表中的纪录还会碰到历史操作无法对应主身份的问题。如果确实需要改应该走禁用原账号新增新账号的流程而不是直接UPDATE用户名。删除用户的几个坑不能删除自己。当前登录账号是admin请求删除admin前端按钮要置灰。删除操作要二次确认用Two Button Dialog弹窗让用户确认文字写明即将删除用户 X该操作不可恢复。删除时用事务确保用户表和日志表要么同时成功要么同时回滚。LabVIEW的数据库工具包支持事务记得用DBT Insert包里的Transaction相关节点。重置密码功能也是实际操作中经常用到的用户忘记密码管理员在列表中选中该用户点击重置密码弹窗输入新密码然后UPDATEPasswordHash字段。重置不需要旧密码所以安全性要求更高这个操作必须写日志日志内容是管理员X重置了用户Y的密码。4.3 权限分级设计管理员与操作员的权限边界权限分级是这套系统里花心思最多的地方之一。系统只有两个角色但边界不能模糊操作管理员操作员登录系统允许允许修改自己的密码允许允许查看用户列表允许禁止新增用户允许禁止编辑用户允许禁止删除用户允许禁止重置密码允许禁止查看日志允许禁止操作员改自己的密码这个功能我放在主界面的修改密码按钮里不需要进用户管理界面。实现上述权限隔离时用户管理界面上每个按钮都加了一个条件判断读取Session FGV中的RoleID如果不是1管理员按钮置灰且不可用。这还不够VI本身还要做二次校验。我的习惯是受保护VI在开始处放一个判断框如果当前会话不是管理员给出提示并return false确保逻辑层的安全。权限还有个细节角色改变时机的处理。如果正在使用系统的操作员被管理员修改成本操作员他的界面上的权限不会即时刷新除非他重新登录。这种情况一般可以接受但可以在Session FGV里存一个权限版本号每次权限变更时版本号1客户端每次操作前校验版本号是否匹配。考虑到项目规模不大我没做这个但在文档里标注了这是已知的限制和改进方向。4.4 操作日志记录每一次关键动作日志写入策略是关键动作完成后异步写入。所谓异步就是Log写入VI用非阻塞调用或者说把写日志的VI设置为可重入并在调用方放到独立的循环中执行避免用户操作界面时因等待数据库写入而产生卡顿。日志的字段在2.1节已经列出来了。ActionType包括LOGIN_SUCCESS / LOGIN_FAILLOGOUTUSER_CREATEUSER_UPDATEUSER_DELETEPASSWORD_RESETPASSWORD_CHANGEActionDetail字段用固定格式用户名|操作描述|结果例如admin|重置了用户chen的密码|成功。日志的查看界面我做了简单的筛选功能按时间段筛选、按操作人筛选、按操作类型筛选。ACCESS数据库在数据量不超过几万条时筛选性能没问题但如果日志膨胀到几十万条建议定期手动清理SQL是DELETE FROM tbl_Log WHERE LogTime DateAdd(m, -6, Now())只保留最近6个月的日志。5. 常见问题与排查技巧实录5.1 ODBC数据源配置常见错误折腾这套系统时我遇到的最头疼的问题就是ODBC配置。具体表现为开发机上连接ACCESS没问题换一台电脑部署之后就报找不到数据源。排查过程如下确认目标机器上是否安装了ACCESS Runtime。纯ODBC连接可以不装ACCESS但Microsoft Access Driver是必须的Windows自带的ODBC驱动列表里如果没有Microsoft Access Driver (*.mdb, *.accdb)要从Microsoft官网安装Microsoft Access Database Engine。确认系统DSN和用户DSN的区别。如果你在开发机创建的是系统DSN部署的时候没有在目标机器上创建相同的DSN代码里用的就是无效的DSN名称。实操中最好用无DSN连接字符串在代码里直接写Driver{Microsoft Access Driver (*.mdb, *.accdb)};DbqC:\MyApp\Data\UserDB.mdb;这样就不需要预先配置ODBC数据源换机器部署时只要路径正确就能连上。32位和64位的驱动问题。这套系统用的是32位LabVIEW那ODBC驱动管理器也要用32位的路径是C:\Windows\SysWOW64\odbcad32.exe。用默认的64位odbcad32.exe创建的DSNLabVIEW根本看不到这个坑我当年踩了好久。5.2 中文乱码问题与Unicode转换LabVIEW处理字符串默认用本地编码中文环境下就是GBK/GB2312。而ACCESS驱动返回的中文默认用UnicodeUTF-16。直接显示从数据库读出来的中文经常会得到一串乱码。实际解决方案是在读取和写入的边界做编码转换。LabVIEW中可以用Unicode转换节点具体调用路径在函数面板 - 编程 - 文本 - Unicode转换。如果数据库工具包返回的是Unicode字符串显示前要转成当地字符串写入时反过来本地字符串转Unicode存储。这里提示一下项目热词里提到的labview中怎么把gbk转换成unicode——用LabVIEW的字节数组处理很容易实现。GBK字符串先通过字符串至字节数组转换得到一个字节数组再通过Unicode类相关节点解码。但工作中的经验是尽量别做手工转换直接用现成的Unicode转换节点避免边角情况时的编码错误。5.3 数据库连接占用与释放问题LabVIEW进程默认不会自动关闭数据库连接。很多人在开发过程中发现程序运行几次后ACCESS数据库无法打开了提示文件被占用。原因是运行中的VI建立了数据库连接但没有显式关闭。尤其是程序异常终止时连接没有释放ACCESS文件被锁住。这个问题在开发调试阶段特别常见因为调试时经常点击Abort按钮强制终止程序LabVIEW不会执行任何清理面板上的调试选项。我的解决方案所有数据库连接在Data Access层统一管理连接的生命周期放在一个引用中程序退出时通过DbTools Close Connection节点显式关闭。在程序主循环的退出分支中先关闭数据库连接再退出主界面。使用DBT Open Connection时注意Timeout属性的设置。连接字符串里附加;Initial CatalogUserDB这种属性时确保文件名路径中没有中文字符。我曾经把数据库放在桌面→新建文件夹→中文路径下结果其他机器上部署因为路径不一致而出问题所以部署时尽量约定一个固定的、纯英文的路径。5.4 数据库驱动版本导致的兼容性问题遇到过一种情况某台机器上连接ACCESS时报错未找到提供程序但明明已经安装了Microsoft Access Database Engine。查了半天发现是机器上同时装了Office的32位和64位版本导致驱动注册表冲突。解决办法卸载掉多余的Office版本或者采用64位Office 32位ACE驱动的标准组合但需要注意ACE驱动对Office版本有依赖。如果是纯LabVIEW项目不装Office直接装Access Runtime 2016或更高版本通常可以解决。另外提醒一下labview runtime engine 8.5这样的老版本运行时环境在较新的Windows系统上存在兼容性问题建议在部署时选用与团队开发环境一致的运行时版本并打上最新的服务补丁。5.5 密码忘记后的应急处理方案开发完成后自己把管理员密码忘了这个情况真实遇到过。因为存的是SHA256哈希无法直接逆推。应急方案是用LabVIEW写一个临时VI连接ACCESS数据库执行以下SQL将指定用户密码重置为临时密码123456哈希值写死进去。UPDATE tbl_User SET PasswordHash 哈希值占位符 WHERE UserName admin;注意这个操作属于应急后才用的方案在项目交付文档中不要写进去替换掉正式环境里的默认密码。生产环境中建议把哈希值和随机盐固化在代码中不要用123456这种默认值。实操时有个细节DESKTOP标签上的Reset按钮是给当前用户的而锁定用户、停用账号功能在外。用户登录时除了验证密码还要检查IsActive字段停用账号返回的提示要跟密码错误区分开写为账号已停用请联系管理员。6. 项目部署与交付注意事项6.1 数据库文件路径管理安装部署时数据库文件不能放在程序安装目录下。因为LabVIEW编译的EXE所在目录通常是在Program Filesx86下普通用户没有写入权限。如果程序路径下的.mdb文件无法写入就会出现无法更新数据库问题。我习惯的路径方案在系统公共文档目录下创建应用子目录数据库路径加密写在配置文件中。比如C:\ProgramData\MyApp\UserDB.mdb这样不同用户登录这台机器都能访问同一个数据库。同时数据库文件的权限要设置好避免操作员直接通过资源管理器把.mdb文件拷走。6.2 编译EXE时的关键设置项目开发完打包成EXE前需要做好几个设置在 项目Project 属性 - 运行时Run-Time - 主动加载库将项目依赖的控件和自定义类型都勾上。未正确包含依赖编译出的EXE换一台机器运行时可能会出现无法加载某个控件的错误。禁用调试项生成应用程序Build Application 对话框中取消勾选允许调试。否则生成的EXE可以被他人打开调试窗口从内存中直接查看数据库连接信息。6.3 交付文档中的注意事项交付时需要提供用户操作手册写清楚管理员怎么添加用户、重置密码数据库部署说明记录ODBC驱动安装步骤以及数据库文件默认位置日志字段说明方便事后追溯默认管理员账号提醒第一个管理员登录后立即修改密码我见过很多LabVIEW项目交付后因为文档没写清数据库部署细节客户换了电脑就抓瞎。这部分的成本花得非常值。7. 实操心得与扩展方向做这套系统的过程里几次踩坑后的体会最深第一LabVIEW写业务系统最大的障碍不是G语言语法而是数据结构和流程控制。UI层面的控件拖拽一天就能上手但数据的分层、线程之间的通信、异常处理这些才是真正决定项目质量的东西。标签语言用熟了逻辑都不会太差但因为G语言特殊的数据流执行方式不加约束的全局变量使用和多线程竞争问题反而比文本语言更容易被忽略。第二安全和权限是设计出来的不是添加出来的。最开始需求确认时甲方只提了能登录、能管理用户所有安全细节都是开发过程中逐步验证后加上的。参数化查询、密码哈希、权限二次校验这些现在已经固化成了我开发LabVIEW业务系统的基础组件下次做类似项目时可以直接复用不需要重新设计。第三这套系统的扩展方向其实很多。比如接入指纹识别或刷卡登录LabVIEW通过串口或USB读卡器就能实现增加登录失败后的通知机制登录多次失败时邮件通知管理员数据库升级到MySQL只需改数据访问层业务逻辑和界面层完全不用动。当时放下这个项目的时候我想到的结论是一套可以复用的登录管理系统关键不只是能跑通而是异常时知道怎么处理、部署时不容易出错、交付后用户能用得明白。按照这套思路做下来后面做任何工控上位机软件的权限管理模块都是在复用这套方案顶多就是改改界面换换表字段。这就是做基础组件的价值。
返回列表