一、构建达梦数据库连接配置

1、首先在vendor/thinkphp/library/db/builder下添加Dm.php

<?php

namespace think\db\builder;

use think\db\Builder;
use think\db\Query;

/**
 * Dm数据库驱动
 */
class Dm extends Builder
{
    protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT  %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%';

    /**
     * limit分析
     * @access protected
     * @param  Query $query 查询对象
     * @param  mixed $limit
     * @return string
     */
    protected function parseLimit(Query $query, string $limit): string
    {
        $limitStr = '';

        if (!empty($limit)) {
            $limit = explode(',', $limit);

            if (count($limit) > 1) {
                $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")";
            } else {
                $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")";
            }
        }

        return $limitStr ? ' WHERE ' . $limitStr : '';
    }


    /**
     * 设置锁机制
     * @access protected
     * @param  Query      $query 查询对象
     * @param  bool|false $lock
     * @return string
     */
    protected function parseLock(Query $query, $lock = false): string
    {
        if (!$lock) {
            return '';
        }

        return ' FOR UPDATE NOWAIT ';
    }

    /**
     * table分析
     * @access protected
     * @param  Query     $query         查询对象
     * @param  mixed     $tables        表名
     * @return string
     */
    protected function parseTable(Query $query, $tables)
    {
        $item    = [];
        $options = $query->getOptions();

        foreach ((array) $tables as $key => $table) {
            if (!is_numeric($key)) {
                $key    = $this->connection->parseSqlTable($key);
                $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table);
            } else {
                $table = $this->connection->parseSqlTable($table);

                if (isset($options['alias'][$table])) {
                    $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]);
                } else {
                    $item[] = $this->parseKey($query, $table);
                }
            }
        }
        return implode(',', $item);
    }

    /**
     * 字段和表名处理
     * @access public
     * @param  Query  $query  查询对象
     * @param  string $key
     * @param  string $strict
     * @return string
     */
    public function parseKey(Query $query, $key, bool $strict = false): string
    {
        $key = trim($key);

        if (strpos($key, '->') && false === strpos($key, '(')) {
            // JSON字段支持
            list($field, $name) = explode($key, '->');
            $key                = $field . '."' . $name . '"';
        }
        if (in_array($key, ['group','desc'])){
            $key = '"'.strtoupper($key).'"';
        }

        return $key;
    }

    /**
     * 随机排序
     * @access protected
     * @param  Query $query 查询对象
     * @return string
     */
    protected function parseRand(Query $query): string
    {
        return 'DBMS_RANDOM.value';
    }

}

在上面这个文件中,针对原先项目中使用到一些关键词字段导致达梦数据库语句异常情况的处理方法,在parseKey方法中批量处理:

//针对关键字进行加双引号处理,根据达梦数据库配置大小写的情况使用strtoupper
if (in_array($key, ['group','desc'])){
      $key = '"'.strtoupper($key).'"';
}

2、在vendor/thinkphp/library/db/connector下添加Dm.php

<?php

namespace think\db\connector;

use PDO;
use think\db\Connection;

/**
 * Dm数据库驱动
 */
class Dm extends Connection
{
    protected $builder = '\\think\\db\\builder\\Dm';

    /**
     * 解析pdo连接的dsn信息
     * @access protected
     * @param array $config 连接信息
     * @return string
     */
    protected function parseDsn($config)
    {
        $dsn = 'dm:';
        if (!empty($config['hostname'])) {
            $dsn .=  $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/';
        }
        if (!empty($config['charset'])) {
            $dsn .= ';charset=' . $config['charset'];
        }

        return $dsn;
    }

    /**
     * 取得数据表的字段信息
     * @access public
     * @param string $tableName
     * @return array
     */
    public function getFields($tableName)
    {
        list($tableName) = explode(' ', $tableName);
        $sql             = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)";

        $pdo    = $this->query($sql, [], false, true);
        $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
        $info   = [];

        if ($result) {
            foreach ($result as $key => $val) {
                $val                       = array_change_key_case($val);
                $info[$val['column_name']] = [
                    'name'    => $val['column_name'],
                    'type'    => $val['data_type'],
                    'notnull' => $val['notnull'],
                    'default' => $val['data_default'],
                    'primary' => $val['pk'],
                    'autoinc' => $val['pk'],
                ];
            }
        }

        return $this->fieldCase($info);
    }

    /**
     * 取得数据库的表信息(暂时实现取得用户表信息)
     * @access   public
     * @param string $dbName
     * @return array
     */
    public function getTables($dbName = '')
    {
        $sql    = 'select table_name from all_tables';
        $pdo    = $this->query($sql, [], false, true);
        $result = $pdo->fetchAll(PDO::FETCH_ASSOC);
        $info   = [];

        foreach ($result as $key => $val) {
            $info[$key] = current($val);
        }
        return $info;
    }

    /**
     * SQL性能分析
     * @access protected
     * @param string $sql
     * @return array
     */
    protected function getExplain($sql)
    {
        return [];
    }

    protected function supportSavepoint()
    {
        return true;
    }
}

3、修改database.php文件配置

'type'            => 'dm', //固定
'hostport'        => '5236', //达梦数据库默认端口

二、达梦数据库的弯路

1、最好是达梦数据库和php服务在同一台服务器(我相信不止我一个人因为分离部署的原因几近崩溃)

2、同一服务器的情况下,使用docker部署也需要注意启动时的参数方法

注意:虽然官方有达梦数据库docker部署方式,但截止2023-12-07号,咨询过达梦技术人员,他们内部总部的工程师有说适配度的话最好不使用docker环境,会有一些问题

所以我就没去使用docker环境部署的数据库,直接宿主机安装的,其他服务(php、redis、nginx)docker的启动方式参数不用默认的网桥模式,配置host模式

#nginx
docker run --net=host -d --name nginx -v /home/wwwroot:/usr/share/nginx/html:cached --privileged=true nginx

#redis
docker run -itd --name redis --net=host redis:5.0

#php
docker run --net=host -d --name php -e LD_LIBRARY_PATH=/dm8/dmdba/dmdbms/bin -v /dm8:/dm8 -v /home/wwwroot:/usr/share/nginx/html:cached --privileged=true php:7.4.33-fpm

3、达梦安装配置

如果是mysql转过来的,达梦安装时簇大小和页大小最好是32,具体有什么问题我也没深究,网上都这么说咱们就听取前辈意见,然后有一点还需要注意,mysql转过来的,最好创建达梦实例的时候配置大小写不敏感,能省掉不少麻烦(我php项目把数据库表名、字段名使用达梦DTS的时候自动转了大写了,然后在上面builder目录下的Dm.php那边有用strtoupper方法批量处理,所以目前是没受大小写敏感配置的影响,但另一个java项目开发到最后发现还是得配置大小写不敏感才行导致数据库重装实例)

关于数据库配置问题,创建dm_svc.conf

TIME_ZONE = (+480)
LANGUAGE = (cn)
#Dwc = (10.57.177.15: 5236,10.57.177.16: 5236) #对方技术给的分布式配置,我这没使用
CHAR_CODE=(PG_UTF8) #这个配置很关键,决定数据库存取数据时是否乱码问题

添加完之后,放在/etc目录下,注意:这里的/etc目录需要跟php同一个环境,如果php安装在宿主机的,那么dm_svc.conf就放在宿主机下的/etc目录下,如果php是docker环境,那么这个文件就需要放在php docker下的/etc目录

 三、银河麒麟V10操作系统问题

虽然甲方给的服务器是银河麒麟V10 Centos7.9版本,在宿主机安装lnmp失败,一开始没注意lnmp官方文档,官方说lnmp1.9版本起才有兼容银河麒麟操作系统,但我不管是哪个版本最后都没安装成功,可能是因为甲方给我的初始化系统里yum安装源不全,使用网上的Centos源也没成功(当然在lnmp论坛里有网友说在银河麒麟里使用centos源没让系统崩了就不错了,我对运维这方面确实不熟,也是因为一直没成功也想着用centos源试试,毕竟底层还是centos的嘛,我这里是错误的方法,大家别学我)

四、整个项目搭建下来碰到过的问题总结一下(仅个人经历的个人观点,未多方验证,仅供参考)

1、为什么说数据库和php服务最好在同一台服务器下,因为我本地(windows)和线上(linux)搭建完php和nginx之后,tp下的database.php里的数据库ip地址填了远程集群的ip,是没有效果的,如果你的php所在服务下没有安装达梦数据库,那么你的系统连接数据库会返回

SQLSTATE[HY000] dpi_login: -70028 创建SOCKET连接失败

这个报错是因为没找到数据库服务,连接失败

如果你本地有安装达梦数据库,那么当你填的是远程ip和用户名密码,你的系统可能会报用户或密码错误,原因还是因为php连接的还是本地的数据库(根据个人经历排查的个人观点,未多方验证,仅供参考),当然网上有看到好像在操作系统配置一个什么配置文件就可以远程请求,我赶着交付就没去研究了,有大佬找到可以的方法也希望告知小弟

2、接口有可能返回编码错误

Malformed UTF-8 characters, possibly incorrectly encodedMalformed UTF-8 char

这个错误很可能是数据库中文乱码了,处理方法就是dm_svc.conf文件中的CHAR_CODE配置,配置前如果有数据库存储操作,需要删除或者手动修改记录之后再次存储操作

3、docker网桥模式的问题

网桥模式启动的php环境,虽然宿主机有安装达梦数据库,但是php docker环境里会找不到达梦的服务,即使把达梦的整个目录挂载进去,而且dmdbms和dmdata还不能分开挂载(会导致php启动不起来),我这边最后是求助了一个大佬才用host模式启动方式解决这个问题。

4、php达梦扩展问题

进入达梦安装目录dmdbms下drivers\php_pdo目录,需要libphp*_dm.so和php*_pdo_dm.so(linux系统),windows系统需要pdo*_dm.dll和php*_dm.dll,拷贝到自己的php扩展目录下,修改php.ini进行引入,*是php版本和nts版本,比如我使用的是php7.4.33,那我linux需要的扩展文件就是libphp74_dm.so(nts版的是libphp74nts_dm.so)和php74_pdo_dm.so(nts版是php74nts_pdo_dm.so),怎么区分ts版本和nts版本呢,查看phpinfo内容,找到ThreadSafety项,如果是enabled,一般来说应该是ts版,否则(disabled)是nts版

修改完php.ini之后用php -m查看是否有dm和pdo_dm的扩展

这时候如果前面有一大串.so文件未找到什么的,就看下文件放的扩展目录是否正确和确认下扩展是不是对应的php/nts版本问题。如果没报so文件问题这时候一般情况还会出现错误

PHP Fatal error: Unable to start DM module in Unknown on line 0

这是因为达梦的环境变量问题

解决方法一:在宿主机/etc/ld.so.conf.d下面创建一个dm.config文件,里面写上达梦数据库bin目录的绝对路径,如下:

然后执行ldconfig刷新,执行结果可能会出现lib*so is not a symbolic link(文件不是符号连接)错误,进入到报错文件目录,使用mv lib*.so lib*修改文件名,然后使用ln -sf lib* lib*.so创建软连接,再执行ldconfig应该就可以了

方法二:网上很多说配置环境变量LD_LIBRARY_PATH=达梦数据库bin的绝对路径,这边要注意:

不能直接在/etc/profile里面直接添加或修改export LD_LIBRARY_PATH=/dm8/dmdba/dmdbms/bin,因为这样你可能会发现yum命令报错

undefined symbol: EVP_md2, version OPENSSL_1_1_0

这是因为你重新定义了LD_LIBRARY_PATH,原本系统里的lib文件找不到了,解决办法是修改LD_LIBRARY_PATH

export LD_LIBRARY_PATH=/usr/lib:/usr/lib64:/dm8/dmdba/dmdbms/bin

保存之后记得source /etc/profile刷新(这个问题搞了我2天都没有解决,一开始以为是宿主机安装lnmp失败导致的系统环境被更新破坏,最后是请教了公司大佬修复的)

5、然后还有个加密模块确实的报错

我记得解决的方法是把达梦数据库bin目录下的libcrypto.so复制到/usr/lib64下(dokcer的貌似是拷贝到docker下的lib64下)

参考文章:

1、tp5.0配置达梦数据库(干货)

2、php和达梦数据库配置完成后,启动php报错,解决思路。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐