ARTICLE DETAIL

资讯详情

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

JSDBC实战:用ActiveX控件在浏览器中直连MySQL做内网调试

JSDBC实战:用ActiveX控件在浏览器中直连MySQL做内网调试 简介在网页开发中有时需要绕过后台服务让JavaScript直接读取或修改MySQL数据库中的数据而这份资源介绍的JSDBC组件正是解决该需求的工具它通过安装一个OCX对象即可在浏览器脚本环境中建立与MySQL的连接。JSDBC不仅免去了部署Java运行环境和编写复杂JDBC调用的麻烦还支持MySQL、SQLite、ACCESS等数据库能很好地集成到应用程序中适合调试和轻量应用。资源以单个doc文档打包大小仅31KB却系统整理了从连接、增删改查到错误处理、关闭连接的全套函数用法包括connectMySQL、insertMySQL、execDMLMySQL、selectMySQL、getLastError与closeMySQL等并说明了字符集配置及查询结果的行分隔、字段分隔处理细节附有可直接套用的JavaScript示例代码。文档内容精简实用能帮助开发者在AJAX项目中快速集成数据库访问无需复杂的后台服务支撑。目前该资源已有6926人学习浏览是入门JS直连MySQL的一份简洁参考。1. JSDBC 是张过期牌但在内网调试场景里依然能打后端接口还没写好、数据库表已经建好时联调最难受。你只想确认一条 SQL 在真实数据上跑出来的结果长什么样却得先起一个服务、写一段 DAO、再调一次 HTTP。JSDBCJavaScript DataBase Connector就是干这个的它用 ActiveX 控件把 MySQL 驱动塞进浏览器让 JS 直接访问数据 Mysql不需要部署 Java 运行环境也不用一行 JDBC。这个方案诞生于 2009 年前后今天看技术栈很老但作为内网调试工具、教学演示和快速原型它依然能打。适合做内部工具和本地联调的开发者面向公网的 Web 应用请直接绕道这不是给生产环境用的东西。下面分几章讲清它的原理、封装方法和实战中踩过的坑。2. 原理先立住OCX 控件、ClassID 与 JS 封装函数的调用链2.1 浏览器为什么会允许 JS 碰数据库ActiveX 控件把数据库驱动“注入”页面JSDBC for MySQL 的核心是一个 OCX 组件文件名叫JSDBC_MySQL.ocx。页面加载时通过document.writeln输出一段OBJECT标签让 IE 按注册表里的 CLSID 实例化这个控件。控件一旦创建成功JavaScript 里就多了一个mysql对象所有数据库操作都走这个对象的方法本质上是在 JS 和 MySQL 之间架了一条直连通道。script languagejavascript document.writeln(OBJECT idmysql classidclsid:9C579403-6745-4695-B14C-96212D319F18); document.writeln( codebaseJSDBC_MySQL.ocx#Version1,0,0,000); document.writeln( WIDTH0 HEIGHT0); document.writeln( /OBJECT); /script这段代码有两个关键点。classid是控件的全球唯一标识机器上必须注册过相同的 OCX浏览器才能按这个 ID 找到控件实现codebase指向控件安装包的下载地址和版本号页面运行时如果检测到本机没注册会尝试从这个地址拉取安装。WIDTH0 HEIGHT0说明控件不需要任何可见界面它只提供编程接口。老 IE 下这套机制是通的Chrome 和现代 Edge 早就移除了 ActiveX 支持所以这个方案的使用场景天然被限定在 IE 内核浏览器或双内核浏览器的兼容模式里。提示ActiveX 控件权限极大等于把数据库凭据直接暴露给了浏览器页面。JSDBC 只适合在内网、可信环境或纯本地工具里用不要把它放进任何面向公网的页面。2.2 JSDBC 暴露给 JS 的方法调用面一张表看清所有能力这个组件提供的接口不多但覆盖了日常增删改查、事务、存储过程和错误获取。连数据库之前先把方法记熟后面封装才不会写乱。方法参数返回约定用途connecteip, port, db, user, password, charset1 成功 / 0 失败建立连接close无1 成功 / 0 失败关闭连接insertDatasql1 成功 / 0 失败插入updateDatasql1 成功 / 0 失败更新deleteDatasql1 成功 / 0 失败删除execDMLsql原版成功返回 0通用 DMLselectDatasql, cnum分隔符拼接字符串查询execProduceproname, inparas, outparas, cnum分隔符拼接字符串存储过程execTranscationsql1 成功 / 0 失败事务getLastError无错误信息字符串取错误selectData和execProduce返回的不是数组而是一个用特殊控制字符拼接的长字符串这点是 JSDBC 最容易踩坑的地方。connecte的字符集参数影响非常大2009 年时大家普遍用 GBK现在库基本是 utf8mb4参数选错中文必乱码。还有execDML的返回约定跟其他方法不一致稍后避坑章单独说。2.3 结果集的黑匣子0x06 行分隔符与 0x05 字段分隔符selectData(sql, cnum)把查询结果拼成一个字符串返回行之间用 0x06 分隔字段之间用 0x05 分隔。为什么不用逗号、竖线这种可见字符因为查询出来的数据本身就可能包含这些符号一旦撞上按普通字符拆分必然错位。控制字符几乎不会出现在正常业务数据里这是 2009 年时最省事的序列化方案。var rowsplit String.fromCharCode(6); // 行分隔符等价于 0x06 var fieldsplit String.fromCharCode(5); // 字段分隔符等价于 0x05 function selectMySQL(sql, cnum) { var rs mysql.selectData(sql, cnum); if (rs.length 0) { var array rs.split(rowsplit); var DataSet new Array(); for (var i 0; i array.length; i) { var DataRow array[i].split(fieldsplit); DataSet[i] DataRow; } return DataSet; } else { lasterr mysql.getLastError(); return null; } }原版封装里rowsplit和fieldsplit用的是复制过来的不可见字符在代码编辑器里显示为空白非常容易在复制粘贴时丢失或变成空字符串。我自己的习惯是改用String.fromCharCode(6)和String.fromCharCode(5)在运行时生成这样代码无论怎么复制都不会坏也方便别人阅读。cnum必须和 SELECT 出的列数完全一致否则拆出来的每一行会多或少一个元素数组长度对不上取值时就会踩越界。解析逻辑本身不复杂复杂的是你永远不知道不可见字符在传输链路里会不会被吃掉所以能运行时生成就别硬拷。3. 把可复用的 MySQL 访问层写出来封装、参数与存储过程调用3.1 一个能跑的 HTML 调试页注册控件、连接、查询一气呵成下面给一个最简但完整的页面骨架照抄后把 IP、库名、账号密码改成自己的就能跑。这里把分隔符改成运行时生成并加了错误收集算是原版封装的一个改良版。!DOCTYPE html html head meta charsetGBK titleJSDBC 调试页/title script languagejavascript // 注册 OCX 控件 document.writeln(OBJECT idmysql classidclsid:9C579403-6745-4695-B14C-96212D319F18); document.writeln( codebaseJSDBC_MySQL.ocx#Version1,0,0,000); document.writeln( WIDTH0 HEIGHT0); document.writeln( /OBJECT); var lasterr ; var rowsplit String.fromCharCode(6); var fieldsplit String.fromCharCode(5); function connectMySQL(ip, port, db, user, pwd, charset) { var execFlag mysql.connecte(ip, port, db, user, pwd, charset); if (execFlag 1) return 1; lasterr mysql.getLastError(); return 0; } function query(sql, cnum) { var rs mysql.selectData(sql, cnum); if (rs null || rs.length 0) { lasterr mysql.getLastError(); return null; } var rows rs.split(rowsplit); var result []; for (var i 0; i rows.length; i) { result[i] rows[i].split(fieldsplit); } return result; } function run() { if (connectMySQL(127.0.0.1, 3306, testdb, root, 123210, GBK) 0) { alert(连接失败: lasterr); return; } var data query(SELECT id, name, age FROM users, 3); if (data) { var txt ; for (var i 0; i data.length; i) { txt data[i].join( | ) \n; } alert(txt); } else { alert(查询失败: lasterr); } mysql.close(); } /script /head body onloadrun() /body /html这段代码里有几个参数要说清楚。connectMySQL的六个参数顺序是 IP、端口、数据库名、用户名、密码、字符集字符集必须和页面编码以及 MySQL 表结构实际编码对齐页面是GBK时这里传GBK页面改成utf-8时这里要跟着改成utf8或utf8mb4。msg charset写在 meta 里三者不一致轻则乱码重则连接直接报字符集错误。query里的cnum3是因为 SQL 查了id, name, age三列多一列少一列都会导致解析结果行列错乱。每次调试完记得调mysql.close()。这组件是老实现连接资源不会自动回收来回开关页面容易把 MySQL 的连接数打满。3.2 insert、update、delete 的返回值语义与 lasterr 的时效性增删改操作在 JSDBC 里分了两组insertData专用于 INSERTupdateData和deleteData顾名思义execDML则用于执行其他数据操纵语句。它们失败时都会在控件内部生成一条错误信息通过getLastError()取。这里有个必须记住的时序问题错误信息是一次性的连续操作时必须每次执行后立即取否则下一次成功调用会覆盖上一次的错误内容。function insertMySQL(sql) { var execFlag mysql.insertData(sql); if (execFlag 1) return 1; lasterr mysql.getLastError(); return 0; } function execDMLMySQL(sql) { // 原版这个封装有问题下面避坑章会细说 var execFlag mysql.execDML(sql); if (execFlag 1) return 1; lasterr mysql.getLastError(); return 0; } function safeDML(sql) { var ret mysql.execDML(sql); if (ret 1) { lasterr ; return 1; } lasterr mysql.getLastError(); return 0; }代码逻辑本身很直观方法返回 1 视为成功0 视为失败失败时把控件侧错误信息存进全局变量lasterr供页面统一提示。safeDML是我在踩过execDML的坑之后加的改良版底层控件真正成功时返回 1失败返回 0但原版封装把成功也映射成了 0会让调用方误判。这个问题的根源不在 JSDBC 本身而在于早期封装者的返回约定不统一使用时一定要先确认自己手里的封装版本对成功返回值是怎么定义的否则“明明插入成功了代码却报告失败”这类诡异问题会浪费你一下午。3.3 selectData 的 cnum 参数列数与解析结果的对应关系cnum是查询函数里最容易写错的参数。它告诉控件“这条 SQL 返回多少列”控件按这个数字生成分隔串JS 再按同一个数字拆分。如果 SQL 是SELECT id, name FROM userscnum2刚好传成 1第二列的数据会跟第一列粘在一起传成 3数组里每个字段的第三个位置会是空字符串。// 错误示范SQL 返回 3 列cnum 却传了 2 var data query(SELECT id, name, age FROM users, 2); // data[0] [1, 张三] age 的 25 被拼到了 name 后面 // 正确做法让 cnum 和 SQL 列数严格一致 var data query(SELECT id, name, age FROM users, 3); // data[0] [1, 张三, 25]要根治这个问题建议不要手写列数而是在 SQL 里先数清楚。或者写一个小工具函数先执行SELECT COUNT(*) FROM (你原来的SQL) t取行数再用固定的列名字段去拼一个带列名查询。这个组件年代太老没有元数据自省能力列数错了它不报错只会在解析结果上恶心你属于最典型的“静默翻车”。3.4 execProduce 存储过程调用in 参数、out 参数与列数怎么填JSDBC 连存储过程的能力在一些老项目里是刚需。execProduce(proname, inparas, outparas, cnum)四个参数分别是要调用的存储过程名、输入参数串、输出参数串和输出列数。// 假设存储过程 // CREATE PROCEDURE p_user(IN uid INT, OUT uname VARCHAR(50), OUT uage INT) // 调用时 inparas 传 1outparas 传 ?,?cnum 传 2 function callProduceMySQL(proname, inparas, outparas, cnum) { var rs mysql.execProduce(proname, inparas, outparas, cnum); if (rs null || rs.length 0) { lasterr mysql.getLastError(); return null; } var rows rs.split(String.fromCharCode(6)); var DataSet []; for (var i 0; i rows.length; i) { DataSet[i] rows[i].split(String.fromCharCode(5)); } return DataSet; }inparas是普通字符串拼接的输入参数多个参数用逗号隔开outparas用占位符对应输出参数的个数cnum对应输出结果集的列数。注意这里有个很多人第一次用都会踩的点如果存储过程只执行更新而不返回结果集rs.length是 0代码会走失败分支。真实场景里这叫“成功但没数据”需要单独对这类过程做返回值约定否则明明 UPDATE 生效了页面却提示失败。这跟前面execDML的坑属于同一类问题——老组件区分“执行成功”和“返回结果”的方式非常朴素必须靠封装层去兜。4. 避坑指南从 ActiveX 注册失败到返回 0 也算成功全是实战记录4.1 浏览器提示 ActiveX 已被阻止页面一片空白现象打开调试页IE 地址栏下方弹出黄色拦截条mysql对象为undefined所有调用直接报脚本错误。 原因IE 对 ActiveX 的默认安全策略是“禁止未授权控件”况且这个控件的codebase指向本地 OCX 文件既没有签名也没有可信来源。 解决把页面加入 IE 的“可信站点”再把可信站点的“对未标记为可安全执行脚本的 ActiveX 控件初始化并执行脚本”设为启用。操作路径是 Internet 选项 → 安全 → 可信站点 → 站点 → 自定义级别一路往下找到 ActiveX 相关选项改成启用。做完这一步记得重启浏览器。如果页面是本地file://打开的还要勾上“允许运行本地文件中的活动内容”否则控件加载不出来。4.2 所有中文查出来全是问号或者乱码现象英文数据正常中文字段在alert里变成???或者乱成一团。 原因三处字符集不一致——页面meta声明的编码、connecte方法传入的 charset、MySQL 表数据和连接用的字符集任何一处不统一都会翻车。 解决排查时先统一到同一种。老数据库常用 GBK那就页面用GBK、connecte传GBK、MySQL 连接也走 GBK新库建议统一用 utf8mb4。如果数据已经在库里存成了 GBK而你页面用 UTF-8 去读即使connecte传了 utf8mb4 也一样是乱码因为数据本身在存储层就是 GBK 字节。这种情况先ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4再改代码。4.3 查询只解析出第一行后面的数据全丢了现象SQL 明明返回 5 行selectMySQL只返回第一行或返回的数组长度是 1。 原因rowsplit和fieldsplit是通过复制粘贴写进代码里的不可见控制字符。这个字符在编辑器里看着像空格复制时大部分编辑器会把它当普通空白处理掉最终代码里拼出来的分隔符根本不是 0x06、0x05rs.split(rowsplit)找不到分隔位置整个字符串就被当成了一行。 解决不要复制原项目里的空白字符用String.fromCharCode(6)和String.fromCharCode(5)在代码里动态生成。写完后可以在 JS 里打印一下分隔符的 charCode 验证rowsplit.charCodeAt(0)必须是 6fieldsplit.charCodeAt(0)必须是 5。这个坑极其隐蔽不打印 charCode 根本发现不了。4.4 execDML 成功返回 0按“等于 1 才成功”判断会一直报失败现象用execDMLMySQL(UPDATE users SET age18 WHERE id1)执行数据库里数据确实变了但页面提示“执行失败”。 原因原版execDMLMySQL的封装逻辑是execFlag 1时return 0失败也return 0两层返回被写反了。成功路径被硬编码成了 0调用方自然永远收到“失败”。 解决别直接用原封装的返回值做判断。要么在封装层修正改成成功返回 1要么调用后直接检查数据库里的数据是否变化用结果反推执行状态。这个坑给了我很深的教训——老组件的文档和代码注释永远值得多看一眼历史上传下来的封装不一定是对的。4.5 新机器、新 MySQL 版本上连接报 SSL 错误现象照抄老配置连不上MySQL 端日志出现 SSL 相关错误客户端报ssl connection error之类的提示。 原因JSDBC 这个 OCX 是 2009 年前后的产物底层驱动只支持当时的旧版 MySQL 认证和协议不支持 SSL 加密通道也认不出新版 MySQL 默认启用的caching_sha2_password认证插件。 解决如果数据库是 MySQL 5.7 以下版本把认证方式改回mysql_native_password就能连如果是 MySQL 8.0 以上只能换思路——要么单独开一个内网实例专门给调试工具用要么放弃 JSDBC 改用 Node 或 Python 起个本地代理服务。JSDBC 的时代局限在这里体现得很彻底指望它适配新协议不现实。5. 顺手把它改成内网工具结果集映射、输入校验与验证方法5.1 二维数组转对象数组给每一列一个名字selectMySQL返回的是二维数组写业务逻辑时data[0][0]、data[0][1]这种下标访问可读性太差列一多就分不清谁是谁。常见做法是封装一层映射把二维数组转成对象数组。function mapRows(rows, columns) { if (!rows || rows.length 0) return []; var result []; for (var i 0; i rows.length; i) { var obj {}; for (var j 0; j columns.length; j) { obj[columns[j]] rows[i][j]; } result[i] obj; } return result; } var data query(SELECT id, name, age FROM users, 3); var list mapRows(data, [id, name, age]); // list[0].name 而不是 data[0][1]这个封装改完之后后续代码基本告别魔法数字下标。columns数组的顺序必须和 SQL 的列顺序一致这又是一个隐形约定建议把 SQL 和列名数组写在一起改 SQL 时强迫自己同步改列名能省掉不少调试时间。5.2 最小限度的 SQL 注入防护参数白名单与类型检查JSDBC 这套方案本身没有任何防注入能力SQL 是纯字符串拼出来的。虽然定位是内网工具但内网也不是绝对安全至少做两件最基础的事对用户输入做类型检查对可枚举的参数做白名单校验。function checkInput(value, type) { if (type int) { return /^\d$/.test(value); } if (type string) { // 拒绝常见注入关键字做一个最基础的风控 var lower String(value).toLowerCase(); var dangerous [;, --, /*, drop , delete , truncate ]; for (var i 0; i dangerous.length; i) { if (lower.indexOf(dangerous[i]) 0) return false; } return true; } return false; }这段代码的逻辑很简单整数用正则硬校验字符串用toLowerCase()转小写后用indexOf检查危险片段。它挡不住精心构造的注入但能拦住手滑输入的裸分号和注释符对内网工具来说已经大幅降低了事故概率。更稳的做法是把所有 SQL 里的动态值都改成参数形式但 JSDBC 的接口只收完整 SQL 串所以要严格限制谁能在这个页面上输入 SQL——把页面使用权限锁在少数维护者手里比任何代码校验都有效。5.3 正确验证方法在可信站点里走完整链路这组件没有单测也没有模拟器验证方法就是打开页面跑真库。我自己的顺序是这样步骤操作预期结果1注册 OCX 并加入可信站点对象创建成功无拦截提示2connecte(127.0.0.1,3306,testdb,...)返回 13执行一条 SELECT打印解析结果行数、列数和数据都对得上4执行 INSERT再查回来新数据可见中文不乱码5执行 UPDATE/DELETE再查回来数据按预期变化6mysql.close()数据库连接数下降每一步失败都直接卡住不要往下走。特别是第 3 步如果发现数组长度不对优先查分隔符和cnum这两个是最常见的问题源。从那以后我每次用这类老控件都强制走一遍“注册 → 连接 → 查询 → 写操作 → 关闭”的完整链路并且把分隔符的 charCode 检查写进页面初始化脚本里再也没被“消失的分隔符”坑过第二次。这套方法放到今天其他遗留系统调试里也一样适用希望帮到你。本文还有配套的精品资源点击获取
返回列表