前言
维护老旧业务系统的程序员,多半会撞上一类很难取舍的工程困境:业务逻辑本身稳定可用,仅仅因为编程语言版本升级,原生配套的老旧数据库驱动被官方移除,整个项目就卡在原地,进退两难。
PHP 版本迭代向来激进。PHP8 等高版本拥有完善的安全补丁,更适合对外承载业务,但官方已经彻底移除了 mssql 系列旧驱动;而不少企业现存的 SQL Server 老库,仅能依靠 PHP5.6 环境下的 sqlsrv 扩展稳定访问。
这里有一个非常关键的工程红线必须放在最前面说明:低版本 PHP 本身存在大量已公开的安全漏洞,如果业务环境逼不得已要保留旧版本、老旧驱动,承载驱动的服务端口绝对不能对外开放,仅允许内网或者本机回环访问。旧版本没有后续安全补丁,一旦端口暴露在公网,很容易被探测、利用,进而造成数据库泄露、服务器沦陷。本方案默认监听 127.0.0.1,正是出于这个安全考量。
常规方案只有两条路:投入不小成本重构业务代码适配新驱动,或者停留在低版本 PHP 直接对外提供服务,持续暴露安全风险。两种方案都有明显短板。
本文基于适配器模式(Adapter)与中介者模式(Mediator),搭配一套自研 TCP-JSON 轻量 RPC 远程调用机制,实现这个数据库中介网关。它不需要大规模改造上层业务,业务侧可以继续使用高版本 PHP,并且协议本身不绑定 PHP,Java、Go、Python 等其他语言都可以作为客户端接入调用。
它不只是一个临时的兼容脚本,在 RPC 网关这一层,我们内置了 Token 授信、表级读写权限隔离、调用频次限流、访问计数统计、事务会话管理等管控能力。
一、两种设计模式的定位(分开阐述,不强行混为一谈)
适配器模式(Adapter)
适配器模式的核心作用:将一个类的接口转换成客户希望的另外一个接口,使得原本因为接口不兼容而不能一起工作的那些类可以一起工作。
放到本项目里:上层业务(PHP8)想要执行 SQL 访问 SQL Server,但是新版环境缺少对应的驱动接口,无法直接发起数据库调用。低版本 PHP5.6 进程携带可用的 sqlsrv 驱动,充当适配器,对外输出一套统一的调用接口。上层客户端不用感知底层驱动差异,统一走 RPC 接口发起请求,由适配器层完成底层数据库驱动的适配。
简单来说:适配器负责抹平接口、驱动、版本之间的不兼容。
中介者模式(Mediator)
中介者模式定义:用一个中介对象来封装一系列对象的交互,使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
传统直连架构是网状依赖:每一个业务客户端都直接连接数据库,客户端持有数据库账号、地址,多接入方同时访问的时候,依赖关系杂乱,权限、审计逻辑散落在各个业务代码里,很难统一管控。我们增加网关作为中介者:所有客户端不再直接和数据库通信,全部 RPC 请求统一发给中介网关。鉴权、限流、表权限校验、会话维护、结果回传全部收拢在中介内部处理。客户端和数据库彻底解耦,新增接入方的时候,只需要在网关配置授信信息,不需要改动数据库和其他客户端。
简单来说:中介者收拢多方通信,解决网状依赖混乱的问题。
两者分工清晰:适配器解决 “能不能调用” 的兼容问题;中介者解决 “怎么管控通信” 的解耦问题。再依托 RPC 远程调用,实现跨进程的请求传递。
二、架构优势
-
跨语言 RPC 服务,不限于 PHP通信协议为 TCP+JSON,任意语言只要可以创建 Socket 收发 JSON 报文,都可以作为 RPC 客户端接入。这套网关不止解决 PHP 高低版本兼容,其他语言想要访问这套老旧 SQL Server,都可以复用。 -
数据库敏感信息隔离,严格收敛攻击面业务客户端不保存数据库 IP、账号、密码,凭证只保存在中介网关。就算业务站点被攻陷,攻击者无法拿到数据库接入凭证。网关默认监听 127.0.0.1,不对外开放端口,规避低版本 PHP 本身的安全缺陷。 -
细粒度权限控制,强于数据库原生权限原生数据库权限最小粒度一般到整个库。本 RPC 网关在应用层增加管控:Token 作为接入凭证、按 Token 限制每日调用次数、精确到单表区分读、写权限,可以约束客户端仅能查询指定数据表,禁止写入或者访问核心表。 -
调用计数原生支持按量统计每一次 RPC 调用都会统计次数,天然支持调用对账、多租户按量计费,这是原生数据库本身不具备的能力。 -
完整事务会话支持很多简易转发工具只支持单条 SQL,无法跑事务。网关维护独立会话 ID,同一个事务的多次 RPC 请求复用同一个数据库连接,完整支持 begin /exec/commit /rollback。
三、整体架构链路
任意语言RPC客户端(PHP8 / Java / Go / Python)↓ 内网TCP JSON RPC请求PHP5.6 中介网关(中介者角色:鉴权、限流、权限、会话管理)↓ sqlsrv驱动适配(适配器角色,抹平版本驱动差异)SQL Server数据库
安全说明:PHP5.6 网关进程仅开放本机回环地址监听,不监听 0.0.0.0,禁止公网直接访问。
四、源码实现
4.1 权限内核 ApiAuthor.php
负责 Token 校验、频次限流、数据表权限判断、调用计数,是中介网关的管控层:
class ApiAuthor{// 授信token白名单private static $tokenWhiteList = [”DbAgent_2026_Secret_001”];// 表级权限配置,区分读、写private static $tableRule = [”DbAgent_2026_Secret_001” => [”read” => [”user”, ”log”, ”goods”],”write” => [”temp_data”]]];// 单日最大调用上限private static $dayMaxLimit = 999999;// 调用计数缓存public static $statics = [];public staticfunctioncheckToken($token){return in_array($token, self::$tokenWhiteList);}public staticfunctioncheckFlowLimit($token){$key = $token . ”_” . date(”Ymd”);!isset(self::$statics[$key]) && self::$statics[$key] = 0;self::$statics[$key]++;return self::$statics[$key] <= self::$dayMaxLimit;}public staticfunctioncheckTableAuth($token, $sql){$sql = strtolower(trim($sql));$rule = self::$tableRule[$token] ?? [”read” => [], ”write” => []];$isRead = str_starts_with($sql, ”select”);$isWrite = in_array(true, [str_starts_with($sql, ”insert”),str_starts_with($sql, ”update”),str_starts_with($sql, ”delete”)]);preg_match(”/(from|into|update)\s+([a-z0-9_]+)/”, $sql, $match);$table = $match[2] ?? ””;if ($isRead) {return $rule[”read”] === [”*”] || in_array($table, $rule[”read”]);}if ($isWrite) {return in_array($table, $rule[”write”]);}return true;}public staticfunctiongetTodayCount($token){return self::$statics[$token . ”_” . date(”Ymd”)] ?? 0;}}
4.2 RPC 中介网关服务端(php5.6 常驻运行)
同时承载适配器、中介者双重角色:接收 RPC 请求、统一校验权限、通过 sqlsrv 适配驱动访问数据库、维护事务会话池,超时自动回滚防止锁表。
重点:代码里写死监听 127.0.0.1,不要改成 0.0.0.0。
require_once ”ApiAuthor.php”;$server = stream_socket_server(”tcp://127.0.0.1:9006”, $errno, $errstr);if (!$server) exit(”网关启动失败:{$errstr}”);echo ”数据库RPC中介网关启动成功 | 监听 127.0.0.1:9006\n”;$sessionPool = [];$sessionTimeout = 30;while ($conn = stream_socket_accept($server)) {$rawData = fgets($conn, 65536);$req = json_decode(trim($rawData), true);$res = [”ok” => 0, ”msg” => ”请求异常”];if (empty($req[”token”]) || empty($req[”cmd”])) {fwrite($conn, json_encode($res) . ”\n”);fclose($conn);continue;}$token = $req[”token”];if (!ApiAuthor::checkToken($token)) {$res[”msg”] = ”客户端Token未授信”;fwrite($conn, json_encode($res) . ”\n”);fclose($conn);continue;}if (!ApiAuthor::checkFlowLimit($token)) {$res[”msg”] = ”当日RPC调用频次已超限”;fwrite($conn, json_encode($res) . ”\n”);fclose($conn);continue;}if (!empty($req[”sql”]) && !ApiAuthor::checkTableAuth($token, $req[”sql”])) {$res[”msg”] = ”没有该数据表的操作权限”;fwrite($conn, json_encode($res) . ”\n”);fclose($conn);continue;}$cmd = $req[”cmd”];$sid = $req[”sessionId”] ?? ””;if ($cmd === ”begin”) {$dbcfg = [”UID” => $req[”dbuser”],”PWD” => $req[”dbpwd”],”Database” => $req[”dbname”],”ConnectionPooling” => true];$dbh = sqlsrv_connect($req[”dbhost”], $dbcfg);if (!$dbh) {$res[”err”] = sqlsrv_errors();} else {$sid = uniqid(”sess_”, true);$sessionPool[$sid] = [”handle” => $dbh,”lasttime” => time()];sqlsrv_begin_transaction($dbh);$res = [”ok” => 1, ”sessionId” => $sid];}} elseif ($cmd === ”exec” && !empty($sid) && isset($sessionPool[$sid])) {$sessionPool[$sid][”lasttime”] = time();$stmt = sqlsrv_query($sessionPool[$sid][”handle”], $req[”sql”], $req[”param”] ?? []);if (!$stmt) {$res[”err”] = sqlsrv_errors();} else {$rows = [];while ($r = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {$rows[] = $r;}$res = [”ok” => 1, ”data” => $rows];sqlsrv_free_stmt($stmt);}} elseif ($cmd === ”commit” && !empty($sid) && isset($sessionPool[$sid])) {sqlsrv_commit($sessionPool[$sid][”handle”]);sqlsrv_close($sessionPool[$sid][”handle”]);unset($sessionPool[$sid]);$res = [”ok” => 1];} elseif ($cmd === ”rollback” && !empty($sid) && isset($sessionPool[$sid])) {sqlsrv_rollback($sessionPool[$sid][”handle”]);sqlsrv_close($sessionPool[$sid][”handle”]);unset($sessionPool[$sid]);$res = [”ok” => 1];} elseif ($cmd === ”single”) {$dbcfg = [”UID” => $req[”dbuser”],”PWD” => $req[”dbpwd”],”Database” => $req[”dbname”],”ConnectionPooling” => true];$dbh = sqlsrv_connect($req[”dbhost”], $dbcfg);if (!$dbh) {$res[”err”] = sqlsrv_errors();} else {$stmt = sqlsrv_query($dbh, $req[”sql”], $req[”param”] ?? []);if (!$stmt) {$res[”err”] = sqlsrv_errors();} else {$rows = [];while ($r = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {$rows[] = $r;}$res = [”ok” => 1, ”data” => $rows];sqlsrv_free_stmt($stmt);}sqlsrv_close($dbh);}} else {$res[”msg”] = ”未知RPC指令”;}$res[”todayCount”] = ApiAuthor::getTodayCount($token);fwrite($conn, json_encode($res) . ”\n”);fclose($conn);// 清理超时未提交的事务,自动回滚防锁表foreach ($sessionPoolas $k => $v) {if (time() - $v[”lasttime”] > $sessionTimeout) {sqlsrv_rollback($v[”handle”]);sqlsrv_close($v[”handle”]);unset($sessionPool[$k]);}}}fclose($server);
4.3 RPC 客户端示例(PHP8 版本)
客户端调用如同本地函数,底层封装 Socket RPC 请求,不需要任何 sqlsrv 驱动:
$addr = ”127.0.0.1:9006”;$clientToken = ”DbAgent_2026_Secret_001”;function rpcSend($pkg){global $addr, $clientToken;$pkg[”token”] = $clientToken;$fp = stream_socket_client(”tcp://” . $addr, $ec, $em, 15);if (!$fp) return false;fwrite($fp, json_encode($pkg) . ”\n”);$resp = fgets($fp, 65536);fclose($fp);return json_decode($resp, true);}function dbQuery($dbcfg, $sql, $param = []){return rpcSend([”cmd” => ”single”,”dbhost” => $dbcfg[”host”],”dbuser” => $dbcfg[”user”],”dbpwd” => $dbcfg[”pass”],”dbname” => $dbcfg[”dbname”],”sql” => $sql,”param” => $param]);}// 使用示例$cfg = [”host” => ”127.0.0.1”,”user” => ”sa”,”pass” => ”xxx”,”dbname” => ”testdb”];$ret = dbQuery($cfg, ”select top 10 * from user”, []);var_dump($ret);
五、结语(1024 收尾)
适配器和中介者,在工程实践里经常一起配合使用,但二者职责完全不同,不能混淆。适配器解决接口兼容,中介者解决通信耦合,再搭配轻量 RPC,刚好落地这个老旧数据库兼容场景。
很多开发者提到 RPC 第一反应就是 gRPC、Thrift 这类成熟重型框架。但在内网闭环、需求稳定的场景,自研一套极简 RPC 足够好用,没有额外代码生成、序列化组件的依赖。
这套方案代码量精简、逻辑可控,适合老旧系统平滑升级、多语言共享一套存量数据库。它既是一次驱动兼容的工程实践,也是适配器、中介者两种设计模式从理论走向线上生产的落地案例。再次重申安全底线:承载老旧 PHP 版本的网关,只允许内网 / 本机回环访问,严禁监听 0.0.0.0 对公网开放。
-
人人皆为创造者 每个人都是使用者,也是创造者;是数字世界的消费者,更是价值的生产者与分享者。在智能时代的浪潮里,单打独斗的发展模式早已落幕,唯有开放连接、创意共创、利益共享,才能让个体价值汇聚成生态合力,让技术与创意双向奔赴,实现平台与伙伴的快速成长、共赢致远。
原创应该获得永久分成 原创创意共创、永久收益分成,是东方仙盟始终坚守的核心理念。我们坚信,每一份原创智慧都值得被尊重与回馈,以永久分成锚定共创初心,让创意者长期享有价值红利,携手万千伙伴向着科技星辰大海笃定前行,拥抱硅基 生命与数字智能交融的未来,共筑跨越时代的数字文明共同体。
-
东方仙盟
东方仙盟:拥抱知识开源,共筑数字新生态
在全球化与数字化浪潮中,东方仙盟始终秉持开放协作、知识共享的理念,积极拥抱开源技术与开放标准。我们相信,唯有打破技术壁垒、汇聚全球智慧,才能真正推动行业的可持续发展。
开源赋能中小商户:通过将前端异常检测、跨系统数据互联等核心能力开源化,东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案,让更多商家能够平等享受数字转型的红利。共建行业标准:我们积极参与国际技术社区,与全球开发者、合作伙伴共同制定开放协议与技术规范,推动跨境零售、文旅、餐饮等多业态的系统互联互通,构建更加公平、高效的数字生态。知识普惠,共促发展:通过开源社区、技术文档与培训体系,东方仙盟致力于将前沿技术转化为可落地的行业实践,赋能全球合作伙伴,共同培育创新人才,推动数字经济 的普惠式增长
阿雪技术观
在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。
Hey folks, in this wild tech - driven world, why not dive headfirst into the whole tech - sharing scene? Don't just be the one reaping all the benefits; step up and be a contributor too. Whether you're tossing out your code snippets, hammering out some tech blogs, or getting your hands dirty with maintaining and sprucing up open - source projects, every little thing you do might just end up being a massive force that pushes tech forward. And guess what? The Eastern FairyAlliance is this awesome place where we all come together. We're gonna team up and explore the whole silicon - based life thing, and in the process, we'll be fueling the growth of technology.
开通方法
关注我们
营销资讯:抖音运营,微信公众号运营,小红书运营
网站运营:安全、备案、申请网站,漏洞扫描,数字证书
开发:开发技巧、热点技术,人工智能,数据分析,数据优化,docker
数据服务:数据恢复,数据安全,数据融合,数据异地容灾,数据优化,数据自动备份、数据清洗
人工智能:智能物联网,智慧大屏幕,OCR,智慧刷脸,语音交互,智能机器人,数字生命,数字人,大模型,本地化,边缘化智能(手机模型),faclaw,未来之窗神识系统,语音控制软件
支付:微信支付服务商,支付支付服务商,刷脸支付
安全服务:WAF安全,网安扫描,漏洞扫描,安全补丁,防火墙定制
智慧大屏:物资耗材大屏幕,单位用餐大数据,智慧场馆大屏,销售大屏幕,智慧社区大屏幕,大厅查询机,景区自助机
智慧酒店:酒店系统、酒店押金、酒店房价牌、酒店门锁、布草系统
行业软件:酒店、餐饮、便利店,美发、超市,批发,景区门票,道闸,堂食,配送系统,烘焙系统,健身,美容系统,月子中心系统,健康证系统,专家管理系统
物联网:智能衣柜,足浴店衣柜,售货柜,酒店自助入住机
国产化:uos系统答疑,国产软件开发,国产服务器配置,docker
一体化:酒店一体化(闸机,酒店系统,餐饮系统,售票,布草,无人酒店,在线订房),景区一体化(门票、餐饮、住宿、押金、超市、药店、商铺租赁,通车系统,售票大厅,大屏幕,无人景区,景区自助机) 电脑软件:资源管理,压缩软件,录屏软件,远程桌面,截图软件。 专业系统:软件考试系统,软件试炼系统,软件学习系统。学习系统,打字练习,英语单词系统 学习工具:AI网页阅读器,计算机等级考试软件,web设计应用题题库,计算机软考,软件设计师,自考英语,自考数据库,自考信息安全

