简介:这份源码面向需要在ThinkPHP5.0与FastAdmin环境下接入达梦数据库(DM)的PHP开发者,尤其适合政务、企业信息化等对国产数据库有自主可控要求的项目场景。资源围绕数据库连接、查询构造与模型适配展开,帮助开发者绕开框架默认仅支持主流数据库的限制,快速完成DM的深度整合。压缩包共21个文件,以19个PHP文件为主,另含1个SQL脚本与1个说明文档,整体约214KB,体量轻便,便于直接嵌入现有工程。其中PHP文件覆盖连接器、查询构造器、表达式与异常处理等核心模块,SQL脚本可用于初始化库表结构,说明文档则交代使用方式与注意事项。目前已有606人学习下载,可作为二次开发与集成调试的参考起点,帮助读者理解框架数据库抽象层的扩展思路,缩短从环境搭建到业务落地的周期。
1. 从 MySQL 到 DM:为什么要在 FastAdmin 上做一次数据库迁移
很多团队用 FastAdmin 起步时默认绑死 MySQL,业务跑通、后台成型、权限体系稳定之后,突然接到一个要求:整套系统要部署到达梦数据库 DM8 上。这时候真正的痛点不是「换驱动」,而是 ThinkPHP5.0 的数据库抽象层、FastAdmin 自带的建表 SQL、以及后台大量依赖information_schema的查询,全都要重新对齐。标题里的「基于 ThinkPHP5.0 的 FastAdmin 达梦数据库 DM 集成设计源码」,说的就是这件事:在 FastAdmin 这套后台框架上,把底层数据存储从 MySQL 切到达梦,并且让后台的增删改查、权限菜单、附件管理继续可用。
适合谁看:手上有一套 FastAdmin 项目、需要做信创适配或国产化替换的后端;正在评估达梦迁移成本、想知道坑在哪的技术负责人;以及想搞清楚 ThinkPHP5.0 数据库层到底怎么扩展的开发者。这篇不讲概念,讲的是我实际迁移时踩过的路径、参数和排错方式,新手能照着走,熟手能直接看边界条件。
2. 达梦 DM 与 ThinkPHP5.0 的适配层怎么搭
2.1 先搞清楚 DM 的驱动形态和连接方式
达梦 DM8 提供两种 PHP 侧接入方式:一种是 DM 官方提供的php_dm扩展(对应dm_connect、dm_query这类函数),另一种是走 PDO 的pdo_dm驱动。ThinkPHP5.0 的数据库层默认基于 PDO,所以最省事的路径是让 DM 提供 PDO 驱动,然后在database.php里把type指向一个自定义的连接器类。
我一般会先确认三件事:PHP 版本(DM 的 PDO 驱动对 PHP 7.x 支持较稳,PHP 8 要确认驱动版本)、DM 服务端字符集(建议 UTF-8,否则中文菜单会乱码)、以及dm.ini里的兼容模式。兼容模式很关键,DM 可以开 MySQL 兼容或 Oracle 兼容,开 MySQL 兼容能让一部分LIMIT、SHOW语句少改很多。
# 确认 PHP 已加载 DM 的 PDO 驱动 php -m | grep -i dm # 期望看到 pdo_dm 或 dm 相关扩展 php -i | grep -i "pdo_dm"上面两条命令用来确认扩展是否真的加载。如果php -m里没有,先解决扩展安装,不要急着改框架代码,否则后面报「could not find driver」会浪费很多时间。参数上,dm.ini的COMPATIBLE_MODE建议先设成 MySQL 兼容做第一轮跑通,稳定后再评估是否收紧。
2.2 在 ThinkPHP5.0 里注册 DM 连接器
ThinkPHP5.0 的数据库连接器放在thinkphp/library/think/db/connector/下,每个文件对应一种数据库。要接 DM,最干净的做法是新增一个Dm.php,继承Connector,实现connect、parseDsn、getFields等必须方法。核心是parseDsn拼出 PDO 的 DSN,以及getFields用 DM 的系统表替换 MySQL 的SHOW COLUMNS。
<?php namespace think\db\connector; use PDO; use think\db\Connection; class Dm extends Connection { // 拼装 PDO DSN,DM 的 PDO 驱动一般形如 dm:host=...;port=...;dbname=... protected function parseDsn($config) { $dsn = 'dm:host=' . $config['hostname'] . ';port=' . $config['hostport'] . ';dbname=' . $config['database']; return $dsn; } // 获取字段信息,DM 用 ALL_TAB_COLUMNS 替代 MySQL 的 SHOW COLUMNS public function getFields($tableName) { $sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, NULLABLE FROM ALL_TAB_COLUMNS WHERE TABLE_NAME = '" . strtoupper($tableName) . "'"; $result = $this->query($sql); $info = []; foreach ($result as $val) { $info[$val['COLUMN_NAME']] = [ 'name' => $val['COLUMN_NAME'], 'type' => $val['DATA_TYPE'], 'notnull' => $val['NULLABLE'] === 'N', ]; } return $info; } }这段代码的关键点有三个。第一,parseDsn里的 DSN 前缀要和实际安装的 PDO 驱动名一致,不同 DM 驱动版本前缀可能是dm或odbc,装完先用一个最小 PHP 脚本单独测连接,别在框架里试。第二,getFields里表名要转大写,DM 默认把未加引号的标识符折叠成大写,FastAdmin 建表时如果用小写,这里查不到字段。第三,ALL_TAB_COLUMNS是当前用户可见的列,如果表在别的 schema 下,要换成DBA_TAB_COLUMNS并补OWNER条件。
2.3 配置 database.php 并跑通第一条查询
连接器写好后,在application/database.php里把type改成dm,其余 host、port、database、username、password 按 DM 实际填。DM 默认端口是 5236,不是 MySQL 的 3306,这个别抄错。
return [ 'type' => 'dm', 'hostname' => '127.0.0.1', 'database' => 'FASTADMIN', 'username' => 'SYSDBA', 'password' => 'your_password', 'hostport' => '5236', 'charset' => 'utf8', 'prefix' => 'fa_', ];配置完成后,先别跑后台,写一个最小验证脚本,只做一次Db::query('SELECT 1')和一次Db::name('admin')->limit(1)->select()。第一条验证连接,第二条验证查询构造器和前缀。如果第一条过、第二条报错,问题基本在getFields或 SQL 方言上,而不是连接。参数上,prefix要和实际表前缀一致,FastAdmin 默认是fa_,迁移时如果表名大小写敏感,建议统一成大写建表,减少后续麻烦。
3. FastAdmin 后台功能在 DM 上的落地改造
3.1 建表 SQL 的方言转换
FastAdmin 安装时靠一堆.sql文件建表,这些 SQL 是 MySQL 方言,直接拿到 DM 上跑会挂在AUTO_INCREMENT、ENGINE=InnoDB、UNSIGNED这些关键字上。我的做法是先把 SQL 过一遍转换规则,再手工核对。
| MySQL 写法 | DM 对应写法 | 说明 |
|---|---|---|
INT AUTO_INCREMENT | INT IDENTITY(1,1) | DM 用 IDENTITY 自增 |
ENGINE=InnoDB | 删除 | DM 无存储引擎概念 |
UNSIGNED | 删除 | DM 无符号修饰 |
TINYINT(1) | TINYINT | 括号宽度在 DM 中多被忽略 |
DATETIME | TIMESTAMP | 建议统一用 TIMESTAMP |
| 反引号 `col` | 双引号或直接裸写 | DM 默认大写折叠 |
转换时最容易翻车的是反引号。MySQL 里到处是反引号,DM 不认,要么删掉,要么换成双引号。我一般直接删掉,然后靠统一大写来避免大小写问题。另外COMMENT语法 DM 支持,但位置和 MySQL 略有差异,建议建完表再单独补注释,别混在一条 CREATE 里。
3.2 分页与 LIMIT 的兼容处理
ThinkPHP5.0 的paginate最终会生成LIMIT offset, count。DM 在 MySQL 兼容模式下能认一部分,但更稳的写法是LIMIT count OFFSET offset。如果兼容模式没开或版本差异导致报错,可以在连接器里重写parseLimit。
// 在 Dm 连接器中重写 limit 解析,输出 DM 更稳的 OFFSET 写法 protected function parseLimit($limit) { if (empty($limit)) { return ''; } $limit = explode(',', $limit); if (count($limit) > 1) { // 原写法 offset,count 转成 count OFFSET offset return ' LIMIT ' . (int)$limit[1] . ' OFFSET ' . (int)$limit[0]; } return ' LIMIT ' . (int)$limit[0]; }这段逻辑的作用是把 TP5 默认的LIMIT 0,10转成LIMIT 10 OFFSET 0。参数上注意$limit传进来是字符串,先explode再转 int,避免 SQL 注入。改完后要重点测后台列表页的翻页,尤其是最后一页和空结果,这两种情况最容易暴露 offset 计算问题。
3.3 权限菜单与附件模块的字段适配
FastAdmin 的权限体系依赖fa_auth_rule、fa_auth_group这几张表,字段里有ismenu、status这类 tinyint。DM 里 tinyint 的布尔语义和 MySQL 不完全一样,查询时如果框架用where('status', 1),一般没问题,但用where('status', true)可能匹配不上。我一般统一改成整型比较。
附件模块fa_attachment里存的是相对路径,和数据库无关,但上传后写库时如果字段类型是VARCHAR而 DM 按字节算长度,中文文件名可能超长。建议把url、filename这类字段长度放大到 MySQL 的两倍,或者建库时确认 DM 的字符长度语义是按字符还是按字节。这个点不提前确认,上线后会出现「文件名短但插入报超长」的玄学问题。
4. 迁移过程中最容易翻车的几个点
4.1 现象:连接测试通过,但后台一登录就报「表不存在」
原因:DM 默认把未加引号的标识符折叠成大写,而 FastAdmin 的 SQL 里表名是小写,实际查询时变成FA_ADMIN找不到。解决:建表时统一用大写,或者在连接器里对表名做strtoupper处理,同时确认database.php里的prefix大小写和实际表一致。
4.2 现象:列表页能显示,但搜索框一用就报语法错误
原因:FastAdmin 的搜索构造器会拼出LIKE '%kw%',DM 对LIKE的转义字符和 MySQL 不同,某些版本对%前后的空格敏感。解决:在连接器里检查parseLike相关逻辑,必要时显式指定ESCAPE,并确认搜索字段类型是字符型而不是被误判成数值型。
4.3 现象:新增数据成功,但自增 ID 返回 0
原因:DM 的 IDENTITY 列在 PDO 下lastInsertId行为与 MySQL 不同,部分驱动版本拿不到刚插入的值。解决:在连接器的insert后改用SELECT IDENTITY_VAL_LOCAL()或 DM 提供的序列函数取 ID,别直接依赖lastInsertId。
4.4 现象:中文内容写入后显示成问号
原因:DM 服务端字符集或客户端连接字符集不是 UTF-8,PDO DSN 里没带字符集参数。解决:在 DSN 里补charset=utf8,同时确认 DM 实例建库时的字符集,两边必须一致,只改一边没用。
4.5 现象:后台某些统计查询在 MySQL 上正常,DM 上报「无效的列」
原因:MySQL 允许SELECT里出现不在GROUP BY中的列,DM 严格模式下不允许。解决:把统计 SQL 补全GROUP BY,或者关掉 DM 的严格模式做过渡,但长期还是建议改 SQL,别依赖宽松模式。
5. 验证迁移是否真的成功:一套可复用的检查清单
迁移做完不等于能用,我一般会跑一套固定检查,确认不是「表面能登录」。这套检查分三层:连接层、功能层、数据层。
连接层看三件事:SELECT 1能过、getFields能正确返回字段、事务能正常提交回滚。功能层重点测后台登录、菜单加载、列表分页、搜索、新增编辑删除、附件上传。数据层抽查几条中文记录、时间字段、自增 ID,确认没有截断和错位。
// 一个最小的事务与自增验证脚本 Db::startTrans(); try { $id = Db::name('admin')->insertGetId([ 'username' => 'dm_test', 'nickname' => '达梦测试', 'status' => 1, ]); // 确认自增 ID 有效,中文未乱码 $row = Db::name('admin')->where('id', $id)->find(); if (!$row || $row['nickname'] !== '达梦测试') { throw new \Exception('数据校验失败'); } Db::commit(); echo 'OK id=' . $id; } catch (\Exception $e) { Db::rollback(); echo 'FAIL: ' . $e->getMessage(); }这段脚本的价值在于一次性覆盖自增、中文、事务三个最容易出问题的点。参数上注意insertGetId在 DM 下可能返回 0,如果返回 0 但数据实际插入成功,说明要换取值方式,而不是插入失败。跑通这套之后,再去做全量数据迁移,心里才有底。
我自己的习惯是:迁移前先把 MySQL 里的表结构和数据各导一份做对照,迁移后用行数和关键字段做抽样比对,别只看「能打开后台」。达梦这套适配做完,最大的体会是——别指望框架自动兼容,连接器和 SQL 方言这两层必须自己盯死,剩下的就是耐心逐条排错。希望帮到你。
本文还有配套的精品资源,点击获取