ARTICLE DETAIL

资讯详情

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

FastAdmin适配达梦DM8:ThinkPHP5.0数据库驱动层改造实践

FastAdmin适配达梦DM8:ThinkPHP5.0数据库驱动层改造实践 简介数据库从MySQL切换到达梦DM8往往不是改个连接配置就能完成的任务。尤其是基于ThinkPHP5.0生态构建的FastAdmin后台系统其内置数据库驱动只识别MySQL、PostgreSQL等主流数据库直接切换会遇到类未找到、语法不兼容等连锁问题。核心解决思路是在框架的数据库抽象层进行扩展通过自定义连接器与查询构建器实现方言翻译、字段映射、分页改写、自增主键和保留字处理让上层业务代码无需大量改动即可平滑运行。这种驱动层适配方案具有高复用性和低侵入性适用于企业级数据库国产化迁移场景。本文基于FastAdmin与达梦DM8的集成实践梳理了从环境准备、驱动定制到常见报错排查的完整路径为PHP项目访问达梦数据库提供可落地的工程参考。 这个项目标题有点长拆开来看其实就三样东西FastAdmin 后台框架、ThinkPHP5.0 底层、达梦数据库 DM8 的集成。做过数据库改造的朋友看到这个组合应该不陌生——后台系统跑得好好的结果数据库要从 MySQL 换到达梦第一关就是框架根本不认识这个数据库。FastAdmin 自带的数据库驱动只认 MySQL、PostgreSQL 那几个主流货直接改个配置是不可能切过去的。我这次做的事情是把 FastAdmin 从数据库驱动层开始做了一次完整的达梦适配自定义连接器、自定义查询构建器、处理字段类型映射、分页语法、自增主键、保留字这些问题最后做到上层业务代码几乎不动就能把整套前后台跑在达梦上。整个过程踩了不少坑这次把核心思路、源码改动和排查经验都整理出来给有同样需求的团队做个参考。1. 项目概述与整体集成思路1.1 FastAdmin和达梦数据库是什么先交代一下背景。FastAdmin 是基于 ThinkPHP5.0 生态开发的后台管理系统框架自带权限管理、插件机制、通用 CRUD、日志、配置管理这些能力在 PHP 后台项目里使用频率相当高。它和 ThinkPHP5.0 的绑定关系很深虽然官方后来也做过兼容新版框架的方向但这个项目既然标注了 ThinkPHP5.0就说明底层始终是 TP5.0 这一代。达梦数据库也就是 DM 数据库是国内自主研发的关系型数据库管理系统目前市面上最常见的版本是 DM8。从使用体验来说它大部分语法遵循国际 SQL 标准同时也兼容了不少 MySQL 和 Oracle 的语法习惯。默认端口是 5236超级管理员账号是 SYSDBA这个概念和 MySQL 的 root 差不多。但达梦有个特别容易踩坑的设计用户和模式Schema是绑定的。创建用户的同时会创建一个同名模式如果你不写模式名前缀表就建在当前登录用户的模式下面。对做后台开发的人来说达梦真正让人头疼的其实是三层问题叠加PHP 层面没有像 pdo_mysql 那样开箱即用的官方扩展ThinkPHP 框架层面没有内置达梦驱动以前业务代码里写的很多 MySQL 特有写法达梦要么不认要么表现不一致。这三层每一层都能卡住人。1.2 为什么“改个配置”不行很多人拿到达梦之后的第一反应是把 database.php 里的 type 改成 dm然后直接连一下试试。实际会发现在 ThinkPHP5.0 里根本走不通。TP5 在实例化数据库连接器的时候会拿着 type 参数去找 think\db\connector 命名空间下对应的类这个命名空间下根本没有 Dm直接就会报 class not found。就算你想办法把 type 指向一个相对接近的驱动比如先改成 pgsql 或者 oracle后面也是一坑接一坑。因为 TP5.0 的查询构建器会把表名和字段名用反引号包起来而达梦的标识符标准写法用的是双引号limit 分页在不同数据库里写法也不一样还有 auto_increment、ON DUPLICATE KEY UPDATE、INSERT IGNORE 这类 MySQL 特色语法达梦只能说部分兼容很多情况直接报错。所以这个项目真正的核心工作量不在业务 SQL 语句本身而在框架的数据库抽象层。这套源码标题叫“集成设计源码”其实本质就是往 FastAdmin 里加了一层“达梦适配器”让上层代码继续用 ThinkPHP 的模型、查询器写法底层由适配器翻译成达梦能理解的 SQL业务代码不需要大动。1.3 驱动层改造 vs 业务SQL改造这个项目在方案选型上我其实认真对比过两条路最后确定了一条主线在驱动层做兼容而不是在每个业务代码里做兼容。第一种是“业务 SQL 改造”方案把所有模型查询、字符串条件、分页代码全部按达梦的语法手工改写。听起来直接实际工程量巨大。FastAdmin 后台随便都有几十个表、几十个控制器涉及的字段查询和列表页搜索到处都是改起来不仅工作量爆炸而且以后如果数据库要从达梦再切回 MySQL又得全部反改一遍维护成本完全不可控。第二种是“驱动层翻译”方案也就是扩展 ThinkPHP5.0 的数据库连接器和查询构建器让框架自己生成达梦方言的 SQL。业务代码里还是正常写 where、order、limit、join底层由我们重写后的 Dm 连接器和 Dm Builder 负责把语法翻译过去。这样做对上层完全无感而且这套驱动是完全可以复用的——以后就算换一个别的 ThinkPHP5.0 项目只要把这两个类复制过去数据库配置文件一改就搞定。这个项目选的是第二种这也是我给同样做数据库适配的人最直接的建议能改框架底层解决的事就不要去改业务层业务层改得越多风险越大。2. 环境准备与达梦数据库连接基础2.1 达梦数据库安装与初始化准备达梦数据库的部署方式比较多生产环境一般用官方安装包在 Linux 上装本地开发也可以直接用 Docker 镜像跑能省不少事。不管哪种方式装完之后的默认端口都是 5236需要确认防火墙放通了 TCP 5236。初始化阶段最关键的一步是建业务用户。我不建议直接用 SYSDBA 去跑 FastAdmin 业务一是权限过大会带来安全隐患二是达梦里用户和模式绑定用 SYSDBA 会把表都建在 SYSDBA 模式下后面业务库配置会非常别扭。建议单独建一个账号比如和 FastAdmin 前缀一致CREATE USER FASTADMIN IDENTIFIED BY YourPassword123; GRANT DBA TO FASTADMIN;开发阶段直接给 DBA 角色比较省心因为 FastAdmin 安装和升级过程会涉及建表、改表、创建索引等一堆操作权限不够会到处报错。生产环境当然还是按最小权限原则来只授业务模式需要的系统权限和对象权限。这里要特别强调达梦和 MySQL 的一个核心差异达梦里的“模式”就是用户。你用 FASTADMIN 这个账号连上去之后默认模式就是 FASTADMIN创建表不写模式名表就建在 FASTADMIN 模式下。而 FastAdmin 的表结构设计时通常不带库名前缀统一用 fa_ 作为表前缀这两者在适配时要对齐避免 Dm 连接器把表名拼成“库名.表名”导致找不到对象。2.2 PHP连接达梦的三种方式对比PHP 连达梦实际有几种可选方式我这里整理了一个对比连接方式驱动优点缺点适用场景达梦官方 PHP 扩展php_dm / pdo_dm性能好官方长期维护需要匹配 PHP 版本和 CPU 架构编译安装有门槛生产环境首选ODBC 桥接pdo_odbc 达梦 ODBC 驱动免编译快速验证性能一般字符集配置容易出乱码本地调试、应急验证JDBC 中间层通过 HTTP/代理转换不用装 PHP 扩展多一层转发延迟高故障点多不推荐以 PHP 7.x 为例官方扩展包里对应 php_dm.so放到 PHP 的 extension 目录php.ini 里加 extensionphp_dm.so重启 php-fpm 之后用 php -m 能看到 DM 就说明扩展加载成功。这里有个硬性要求PHP 版本、扩展版本、CPU 架构三者必须严格匹配。x86_64 和 aarch64 是两套不同的包PHP 7.2 和 PHP 7.4 也不能混用搞错了扩展是加载不起来的而且报错信息往往很不直观。ODBC 方式相对通用一些系统先装好 unixODBC再装达梦安装包自带的 ODBC 驱动然后 PHP 的 pdo_odbc 就能连接。缺点是需要在 odbc.ini、odbcinst.ini 里做一圈配置客户端字符集如果没配对中文分分钟乱码。我在调试阶段用 ODBC 比较多因为本地环境没有对应 PHP 扩展包时可以快速验证 SQL 和数据结构验证完了生产环境再切官方扩展。2.3 快速验证达梦连接能否成功装好扩展之后第一步是写一个最简单的脚本验证。不经过任何框架直接测 PDO 能不能拿到连接?php $dsn dm:127.0.0.1:5236; $user FASTADMIN; $pass YourPassword123; try { $pdo new PDO($dsn, $user, $pass); $stmt $pdo-query(SELECT 1 AS ok FROM DUAL); var_dump($stmt-fetch(PDO::FETCH_ASSOC)); } catch (Exception $e) { echo $e-getMessage(); }这里有两个点需要留心。第一达梦的 DSN 格式和 MySQL 完全不同官方 PHP 扩展拼的是 dm:host:port不是 MySQL 那种 host... 的键值对方式。如果这一步报 could not find driver绝大多数情况是扩展没正确加载回到 php -m 检查。第二SELECT 1 FROM DUAL 这条语句达梦支持 Oracle 风格的 DUAL 表这个没问题。但如果你想验证后续建表权限建议再执行一条 CREATE TABLE 再 DROP TABLE确认当前用户有建表和删表的权限这一步能提前暴露权限问题比后面进了框架再排查要快得多。3. FastAdmin数据库层改造与ThinkPHP5.0驱动适配3.1 自定义数据库连接器 Dm 类在 ThinkPHP5.0 的架构里数据库连接是通过 think\db\Connection 这个抽象类展开的每一种数据库对应 think\db\connector 命名空间下的一个子类。TP5 自带 MySQL、PostgreSQL、SQLite、SQLServer、Oracle 等驱动唯独没有达梦所以我们要做的第一件事就是在 extend/think/db/connector/Dm.php 里建一个连接器。连接器的核心是提供 DSN 和 PDO 连接实例我贴一下核心结构?php namespace think\db\connector; use PDO; use think\db\Connection; class Dm extends Connection { protected $builder \\think\\db\\builder\\Dm; protected function parseDsn(array $config) { // 达梦官方 PHP 扩展的 PDO DSN 格式dm:host:port $dsn dm: . $config[hostname] . : . $config[hostport]; return $dsn; } protected function initConnect($master true) { // 把业务库名称作为默认 schema 传入避免每次查询都写全限定名 $this-options[schema] $this-config[database]; parent::initConnect($master); } }实际项目里这个类还要重写 getFields、getTables 这些元数据方法。FastAdmin 后台的“一键生成菜单”“在线建表”“表结构查看”这些功能都会调用框架的字段信息接口。达梦的系统表是 all_tab_columns、all_tables 这一套和 MySQL 的 information_schema 完全不同如果不重写后台很多跟表结构相关的页面会直接白屏或者报列不存在。3.2 自定义查询构建器 Dm Builder连接器负责“连上”查询构建器负责“说话”。ThinkPHP5.0 的 Builder 类负责把查询数组翻译成最终 SQL包括字段名包装、order by、limit、join、insert 等各个环节。我们还需要在 extend/think/db/builder/Dm.php 里继承 think\db\Builder重写三块核心内容第一是 parseKey把 TP5 默认的反引号改成双引号。第二是 parseLimit把 LIMIT offset,size 改写成达梦稳定支持的 ROWNUM 分页写法。第三是 parseInsert 和 parseUpdate 里的特殊函数比如 MySQL 的 ON DUPLICATE KEY UPDATE 这种语法在达梦下要么降级成“先查重再更新”要么直接忽略掉。parseKey 的核心逻辑大致是这样protected function parseKey($key, $options []) { // 已经处理过或者是聚合函数直接返回 if (!is_string($key) || 0 strpos($key, )) { return $key; } // 处理带表名字段的写法fa_user.id fa_user.id if (strpos($key, .)) { list($table, $field) explode(., $key, 2); return $this-parseKey($table) . . . $field . ; } return . $key . ; }这段代码看着简单解决的却是最要命的问题。TP5 默认把关键字包成反引号MySQL 能识别达梦见到反引号直接报语法错误。所有业务代码里只要写了字段名最终都会走到 parseKey 这一步。不改这一行FastAdmin 后台登录都进不去。3.3 修改database.php配置连接器和查询构建器都建好了剩下就是把 FastAdmin 的数据库配置切到达梦。配置文件放在 application/database.phpreturn [ type \\think\\db\\connector\\Dm, hostname 127.0.0.1, database FASTADMIN, username FASTADMIN, password YourPassword123, hostport 5236, params [], charset utf8, prefix fa_, debug true, ];注意 type 这里填的是完整命名空间如果你写 short name DmTP5 会按 think\db\connector\Dm 去解析。因为我们是把自定义类放在 extend 目录下composer 的 psr-4 自动加载规则也能匹配到所以两种写法都行。我倾向写完整命名空间防止以后有第三方主题或者插件重名覆盖。到这里FastAdmin 连达梦的基本问题已经解决。但真正把整个后台跑起来后面还有四五个大坑要填下面单独拿一章来说兼容性改造的细节。4. 核心兼容性改造细节与源码实现4.1 建表语句与字段类型映射FastAdmin 的在线命令、数据库备份还原、插件安装这些功能都会往数据库里执行 DDL而这些 SQL 是从 MySQL 习惯里来的。典型问题有这么几类字段类型写的 int(11)、tinyint(4)、varchar(255)达梦对 int 后面的显示宽度参数不认。整型用了 AUTO_INCREMENT达梦自增列的写法是 IDENTITY 或者使用序列两者不一样。COMMENT 备注 这种列注释在达梦里要改成 COMMENT ON COLUMN 表名.字段名 IS 备注。ENGINEInnoDB DEFAULT CHARSETutf8 这种建表选项达梦直接不支持必须去掉。解决方案是把 DDL 统一改成标准 SQL字段类型用不带显示宽度的 int、bigint、varchar自增列用 IDENTITY注释用达梦的 COMMENT ON 语法。如果手头有大量 MySQL 建表语句建议写一个自动转换脚本不要手工复制粘贴容易漏。转换示例对照MySQL 写法达梦 DM8 写法id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEYid INT IDENTITY(1,1) NOT NULL PRIMARY KEYtitle VARCHAR(255) COMMENT 标题title VARCHAR(255)后续单独执行 COMMENT ON COLUMN 表名.title IS 标题statusTINYINT(4) DEFAULT 1status TINYINT DEFAULT 1ENGINEInnoDB 等表选项去掉不写4.2 自增主键、时间字段和保留字处理FastAdmin 几乎每张表的主键都是 id INT AUTO_INCREMENT在达梦里建表时尽量直接定义成 IDENTITY(1,1)这样 TP 的 insert 操作不传主键值就能自增生成。这里有一个比较头疼的问题如果你的表已经建好了想在达梦里给已有表加自增列是非常麻烦的。达梦默认不支持 ALTER TABLE 直接加 IDENTITY 列很多情况下只能重建表再迁移数据。所以我的建议是建表之前一定把字段类型定义清楚别等项目上线了再回头补代价非常大。时间字段也需要单独注意。FastAdmin 默认的 create_time、update_time 用 int 类型存 unix 时间戳这在达梦里没有兼容性问题。但如果你某些表用了 datetime 类型达梦的日期函数和格式化符号与 MySQL 有细微差异查询日期区间时尽量传标准格式字符串少用 DATE_FORMAT、NOW() 这类 MySQL 函数统一改成 CURRENT_TIMESTAMP 或者参数化传值。保留字是另一个隐蔽的坑。FastAdmin 自带表字段里有 type、status、comment、order 这类词MySQL 里用反引号包住没事达梦里用双引号包住大部分情况下也能跑但有些保留字比较顽固连双引号都救不了只能改字段名或者在 SQL 里加模式名前缀。我之前就遇到过 order 字段在排序场景下 SQL 直接被达梦解析吞掉的情况排查了很久最后把字段改名为 sort_order 才彻底解决。4.3 分页、事务与锁机制的差异处理FastAdmin 列表页的分页走的是 ThinkPHP 的 paginate 方法默认生成的 SQL 在 MySQL 里是 LIMIT 0,10在达梦里我们通过 Dm Builder 的 parseLimit 统一改写。达梦最常见也最稳定的分页写法是三层嵌套配合 ROWNUM 控制范围SELECT * FROM ( SELECT t.*, ROWNUM AS rn FROM ( SELECT * FROM fa_admin ORDER BY id DESC ) t WHERE ROWNUM 20 ) WHERE rn 10;这个三层结构保证了排序在前、分页在后、范围区间正确。如果你的达梦版本开了完整的 MySQL 兼容模式底层也认 LIMIT 语法但生产环境不建议依赖这个开关统一走 ROWNUM 写法更稳切换环境不会出问题。事务这块比较省心。TP5 的 Db::transaction、startTrans 最终都是调用 PDO 的 beginTransaction、commit、rollback达梦官方 PHP 扩展支持标准 PDO 事务接口所以这块基本不用改。真正要注意的是锁。FastAdmin 里有些模型用了 lock(true) 做行锁MySQL 的 SELECT ... FOR UPDATE 在达梦里也能用但前提是当前事务隔离级别下锁语义一致。建议业务层加锁之前先确认达梦的事务隔离级别生产环境一般用 READ COMMITTED避免没走索引时行锁退化成表锁性能差别很大。5. 常见报错与问题排查实录5.1 典型报错速查表这次适配过程中遇到的报错类型算是很全了我整理成一个速查表后续遇到类似问题可以直接对号入座报错信息根因解决方案Class think\db\connector\Dm not found自定义连接器类没放对命名空间确认 extend/think/db/connector/Dm.php 存在且文件名、类名、命名空间三者一致SQLSTATE[HY000]: [unixODBC] ... 乱码ODBC 字符集没配对在 odbc.ini 里设置 charset 为 utf8或者换官方 PHP 扩展达梦数据库 model附近出现错误SQL 中存在达梦不认识的 MySQL 语法打开 SQL 日志定位具体语句把反引号、limit、insert ignore 等语法改写ORA-00942: table or view does not existschema 用错了或者表建在别的模式确认 database 配置是业务用户模式名或者 SQL 里显式写模式名无法将值 NULL 插入列 ID建表用了 IDENTITY但 insert 数据里带了 idNULL检查 insert 数据里是否手动传了主键字段去掉即可关键字 order 附近出现错误字段名是保留字字段改名或者加模式名前缀规避5.2 三个排查心法第一先开 SQL 日志不要靠猜。FastAdmin 在 application/config.php 里把 app_debug 打开数据库层把 debug 设为 trueSQL 就会记录到日志里。遇到语法错误就把最后一条 SQL 拉出来和 MySQL 版对照一眼就能看出是哪个方言点没翻译到。第二达梦的错误信息经常是“xxx附近出现错误”这个 xxx 往往不是关键关键在它前面的那个部分。比如常见的“model附近出现错误”多半是 model 前面有 TP5 拼出来的反引号或者错误的分隔符。这种报错别死盯着错误提示词本身往 SQL 的上一处片段看。第三字符集问题优先统一成 UTF-8。达梦服务端安装时可以选字符集如果选的是 GBK而 PHP 端连接用 utf8插入中文就会出现乱码。最省事的办法是创建数据库实例时直接选 UTF本文还有配套的精品资源点击获取
返回列表