大数跨境

数据库轻量RPC网关兼容web跨版本交互-东方仙盟

数据库轻量RPC网关兼容web跨版本交互-东方仙盟 未来之窗软件服务中心
2026-11-01
22
导读:前言维护老旧业务系统的程序员,多半会撞上一类很难取舍的工程困境:业务逻辑本身稳定可用,仅仅因为编程语言版本升级

前言

维护老旧业务系统的程序员,多半会撞上一类很难取舍的工程困境:业务逻辑本身稳定可用,仅仅因为编程语言版本升级,原生配套的老旧数据库驱动被官方移除,整个项目就卡在原地,进退两难。

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 远程调用,实现跨进程的请求传递。

二、架构优势

  
  1. 跨语言 RPC 服务,不限于 PHP通信协议为 TCP+JSON,任意语言只要可以创建 Socket 收发 JSON 报文,都可以作为 RPC 客户端接入。这套网关不止解决 PHP 高低版本兼容,其他语言想要访问这套老旧 SQL Server,都可以复用。
      
  2. 数据库敏感信息隔离,严格收敛攻击面业务客户端不保存数据库 IP、账号、密码,凭证只保存在中介网关。就算业务站点被攻陷,攻击者无法拿到数据库接入凭证。网关默认监听127.0.0.1,不对外开放端口,规避低版本 PHP 本身的安全缺陷。
      
  3. 细粒度权限控制,强于数据库原生权限原生数据库权限最小粒度一般到整个库。本 RPC 网关在应用层增加管控:Token 作为接入凭证、按 Token 限制每日调用次数、精确到单表区分读、写权限,可以约束客户端仅能查询指定数据表,禁止写入或者访问核心表。
      
  4. 调用计数原生支持按量统计每一次 RPC 调用都会统计次数,天然支持调用对账、多租户按量计费,这是原生数据库本身不具备的能力。
      
  5. 完整事务会话支持很多简易转发工具只支持单条 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 校验、频次限流、数据表权限判断、调用计数,是中介网关的管控层:

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  <?php      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。

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       <?php      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 驱动:

                                                                                                                                                                                                                                                                                            <?php      $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 对公网开放。


  1. 人人皆为创造者


    每个人都是使用者,也是创造者;是数字世界的消费者,更是价值的生产者与分享者。在智能时代的浪潮里,单打独斗的发展模式早已落幕,唯有开放连接、创意共创、利益共享,才能让个体价值汇聚成生态合力,让技术与创意双向奔赴,实现平台与伙伴的快速成长、共赢致远。


    原创应该获得永久分成


    原创创意共创、永久收益分成,是东方仙盟始终坚守的核心理念。我们坚信,每一份原创智慧都值得被尊重与回馈,以永久分成锚定共创初心,让创意者长期享有价值红利,携手万千伙伴向着科技星辰大海笃定前行,拥抱硅基 生命与数字智能交融的未来,共筑跨越时代的数字文明共同体。





    • 东方仙盟 

      东方仙盟:拥抱知识开源,共筑数字新生态

      在全球化与数字化浪潮中,东方仙盟始终秉持开放协作、知识共享的理念,积极拥抱开源技术与开放标准。我们相信,唯有打破技术壁垒、汇聚全球智慧,才能真正推动行业的可持续发展。

      开源赋能中小商户:通过将前端异常检测、跨系统数据互联等核心能力开源化,东方仙盟为全球中小商户提供了低成本、高可靠的技术解决方案,让更多商家能够平等享受数字转型的红利。共建行业标准:我们积极参与国际技术社区,与全球开发者、合作伙伴共同制定开放协议与技术规范,推动跨境零售、文旅、餐饮等多业态的系统互联互通,构建更加公平、高效的数字生态。知识普惠,共促发展:通过开源社区、技术文档与培训体系,东方仙盟致力于将前沿技术转化为可落地的行业实践,赋能全球合作伙伴,共同培育创新人才,推动数字经济 的普惠式增长

      阿雪技术观

      在科技发展浪潮中,我们不妨积极投身技术共享。不满足于做受益者,更要主动担当贡献者。无论是分享代码、撰写技术博客,还是参与开源项目维护改进,每一个微小举动都可能蕴含推动技术进步的巨大能量。东方仙盟是汇聚力量的天地,我们携手在此探索硅基生命,为科技进步添砖加瓦。


      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设计应用题题库,计算机软考,软件设计师,自考英语,自考数据库,自考信息安全


            

      图片



【声明】内容源于网络
0
0
未来之窗软件服务中心
在线工单、售后、配送查询、附近商家 业务范围:餐饮、酒店、KTV、洗浴、客房、美容美发、糕点、POS收银系统 ;商城、团购、分销、众筹、医疗、学校、美容 ;OA、CRM、HRM;智能WIFI、产品、商家推广
内容 281
粉丝 0
未来之窗软件服务中心 在线工单、售后、配送查询、附近商家 业务范围:餐饮、酒店、KTV、洗浴、客房、美容美发、糕点、POS收银系统 ;商城、团购、分销、众筹、医疗、学校、美容 ;OA、CRM、HRM;智能WIFI、产品、商家推广
总阅读5.1k
粉丝0
内容281