耗時5天解決thinkphp連接mysql中文亂碼的問題

坑大,或者不大,它就在那裏,等着你進。


先前修改成熟的一個基於thinkphp3.1.2的後臺框架,裏面有我的autoCode,本來在新項目上不想再用這麼落後的版本,但考慮到後臺項目不對外使用,重點是autoCode是我的進度保證,於是繼續使用了這個版本。


本地開發環境是windows + phpstudy(apache,php5.4),數據庫直接用線上的mariadb10.2,一切在本地就緒,可當我把這個後臺項目放進線上容器(centos7 + apache + php5.4)裏,滿心歡喜的打開在線地址觀看時,竟然發現網頁中的一部分中文變成了英文問號,經過短暫的分析即刻得出:數據庫裏讀出來的中文亂碼了,而模板輸出的中文是正常的。


這首先排除了頁面編碼的問題,而且代碼在本地運行時一切正常,與線上用的是同一個數據庫,因此數據庫也不存在問題。

需要強調的是:

1、我給所有人要求過代碼文件必須使用utf8無bom

2、每個html文件都有<meta http-equiv="content-type" content="text/html; charset=utf-8">這段

3、數據庫,表及字段全是utf8,且數據庫裏的中文內容是正常的

4、thinkphp的config.php裏面已經加入了'DB_CHARSET' => 'utf8'


我檢查了許許多多的配置文件,php的和apache的,連無辜的mariadb也沒有放過。顯式的配置了各種與編碼相關的配置項,但問題並沒有解決。

深深的疑惑使我嘗試過許許多多的做法,我懷疑了很多不該懷疑的東西,除了人生。

我在php入口文件里加了header("Content-type: text/html; charset=utf-8"); 

我還把thinkphp3.1.2的/Lib/Driver/Db/DbMysql.class.php中的

mysql_query("SET NAMES '".C('DB_CHARSET')."'", $this->linkID[$linkNum]);

改爲了

mysql_query("SET NAMES 'utf8'", $this->linkID[$linkNum]);

然而這都是無用功。


其實非常明顯,這就是php連接mariadb時的編碼有問題,我一次又一次的把目光投向了apache、php和mariadb的配置文件,特別是mariadb的/etc/my.cnf.d/server.cnf,[mysqld]加入了:

character_set_server=utf8

init_connect = 'SET collation_connection = utf8_general_ci'

init_connect = 'SET NAMES utf8'

其他幾個配置文件該加default-character-set = utf8的也都加了。


亂碼依舊。


痛定思痛,我冷靜下來,經驗告訴我,也許問題就在某個被自己認爲沒有問題而忽視掉的地方。

於是我把以上每項又檢查了一遍。


是該做點別的嘗試了,於是我檢查了centos7的語言環境,並安裝了中文語言包。

我的期待並沒有得到滿足,亂碼還在。


對了我剛開始還讓另一個php工程師在相同環境下的另一個項目裏去讀同一個數據庫裏的中文,結果並沒有亂碼。

而且我直覺一定是哪個地方使得這個後臺項目中php使用了latin1或者別的編碼去連接mysql。

第N次的痛定思痛,我一直都很確定這就是php使用了非utf8編碼去連接mysql,但我在config.php裏配置了呀,甚至還在DbMysql.class.php裏寫死了。

於是我用php做了一個驗證:

           dump(M()->query("SHOW VARIABLES LIKE '%char%'"));
           dump(M()->query("SET NAMES 'utf8'"));
           dump(M()->query("SHOW VARIABLES LIKE '%char%'"));

           die;

這段代碼在我本地毫無意外的,與mysql連接的編碼都是utf8,而在線上的結果正如我所想,第一行打出來的結果裏,character_set_connection、character_set_client、character_set_results這三個都是latin1,而在執行第二行後,第三行打印的結果就變成utf8了。

這表明我最後的結論是正確的,可是在哪裏產生的這個問題呢?


於是我再一次打開thinkphp3.1.2框架的/Lib/Driver/Db/DbMysql.class.php文件,

把其中數據庫版本大於4.1的if段註釋掉,改成了判斷mysql_set_charset函數是否存在的if段,如下:


            $dbVersion = mysql_get_server_info($this->linkID[$linkNum]);
            /*
            if ($dbVersion >= '4.1') {
                //使用UTF8存取數據庫 需要mysql 4.1.0以上支持
                mysql_query("SET NAMES '".C('DB_CHARSET')."'", $this->linkID[$linkNum]);
                mysql_query("SET character_set_client = ".C('DB_CHARSET'), $this->linkID[$linkNum]);
                mysql_query("SET character_set_results = ".C('DB_CHARSET'), $this->linkID[$linkNum]);
            }*/
            
            if (function_exists('mysql_set_charset') === false) {
                mysql_query("SET NAMES '".C('DB_CHARSET')."'", $this->linkID[$linkNum]);
            }else{
                mysql_set_charset(C('DB_CHARSET'), $this->linkID[$linkNum]);

            }


問題到此解決。

我先是震驚,問題居然這麼簡單,就是$dbVersion >= '4.1'這個判斷沒進去,於是我想起了我用的是mariadb10.2,確實按照字符串來對比版本的話,這盤算我輸。

線上把$dbVersion這個變量打出來看了下,確實是 string(19) "10.2.12-MariaDB-log"

我又疑惑了,那我本地也是用的這同一個庫啊,難道?

我在本地也打印了下$dbVersion,結果竟然是 string(25) "5.5.5-10.2.12-MariaDB-log"

這個疑問先留着,還有就是前面我明明在mariadb的server.cnf裏配置了默認的連接編碼,誰知道告訴我一聲,我還是先把這個坑記錄一下。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章