编码问题 char 看起来正确但不是
Encoding Problem char looks right but is not
首先不是常见的utf8问题。我的应用程序的所有部分都设置为 utf8 并且工作正常。
我通过 PHP 通过 IMAP 收到一封邮件并获取标题。在这个标题中,我有一个特殊的字符。来自德语的 ö。现在我在我的数据库中搜索是否有带有此标题的条目。我知道有一个。数据库使用 utf8mb4_general_ci 进行编码,以便能够存储 4 位 utf8 编码的特殊字符。
来自邮件的标题:
Fw: Auflösungsvertrag
数据库中的条目:
Fw: Auflösungsvertrag
我将光标放在 ö 后面并尝试删除它。首先 ö 切换为 o,在第二次按下删除键后它完全消失了。如果我现在键入并在键盘上输入 ö,MySQL 会找到该条目。
如果我把两者都放在记事本里++ 你看
Fw: Auflösungsvertrag
FW:Auflösungsvertrag
如果将编码转换为 ASCII,您会得到
Fw: AufloÌ^sungsvertrag
Fw: Auflösungsvertrag
所以你现在可以看到两个 ö 的编码不同,但它们显示正确。所以我的 mySQL select 没有找到数据库条目。
谁能给我解释一下,并给我一个提示,让我使用 php 命令将第一个编码字符串转换为第二个编码字符串?
我再描述一下这个问题是如何产生的:
我写了一个票务系统。我发出的每封邮件都会在主题中添加门票 ID。如果我发出一封邮件,我会在外发 table 中将其写入数据库。然后一个 cronjob 将这个邮件发送出去 asyncron。我使用 PHP 邮件程序并通过 SMTP 发送。
我通过 IMAP 和 PHP IMAP 类 获取传入邮件。如果一封邮件在主题中带有 TID,我会将这封邮件合并到数据库中的票证中。所有工单条目均按 TID 列分组。
现在的问题是,如果您将邮件从系统发送到同一系统内的另一个邮件地址,您会将邮件合并到现有票证中。
这就是为什么我通过搜索发件人地址、收件人地址和标题来查看每封收到邮件的外发 table。如果我找到邮件,我就知道系统已经发出了。
所以如果我发送邮件,我有第一个编码。如果我再次收到相同的邮件,它会有其他编码。两种编码接缝都是有效的 utf8 编码。在网站上的任何地方我都能得到正确的字符,在数据库中我也能正确显示它。仅当我通过 PDO 进行 SQL 查询时,MySQL 将它们视为两个不同的字符。
这是我解决这个问题的方法,根据我的说法,它必须在数据库端使用一次性指令一劳永逸地修复,而不仅仅是 PHP 上的技巧,你会每次遇到问题时都要重复一遍。
首先我复制了你的 2 个字符串:
Auflösungsvertrag
Auflösungsvertrag
进入 Notepad++,其中我有(非常方便的)HEX 插件。
当我将文本转换为 HEX 时,我有那些值
4175666c6fcc8873756e677376657274726167
4175666cc3b673756e677376657274726167
如果我们拆分它,我们很容易看到导致问题的 2 ö 的十六进制
4175666c 6fcc88 73756e677376657274726167
4175666c c3b6 73756e677376657274726167
现在的诀窍是告诉 MySQL 将所有具有这些十六进制值的字符从一个替换为另一个,即 6fcc88
到 c3b6
你可以用这个使用 UNHEX() 函数的语句来做到这一点
UPDATE your_table
SET your_column=REPLACE(your_column, UNHEX('6fcc88'), UNHEX('c3b6'))
下面的示例和复制
架构 (MySQL v8.0)
/* Creating test data - Row 1 and 2 are identical */
create table test (id int, txt varchar(50), txthex varchar(100));
INSERT INTO test (id,txt,txthex) VALUES (1, 'Auflösungsvertrag', '4175666c6fcc8873756e677376657274726167');
INSERT INTO test (id,txt,txthex) VALUES (2, 'Auflösungsvertrag', '4175666c6fcc8873756e677376657274726167');
INSERT INTO test (id,txt,txthex) VALUES (3, 'Auflösungsvertrag','4175666cc3b673756e677376657274726167');
应用修复
/* Running oneshot fix on row 2 only */
UPDATE test
SET txt=REPLACE(txt, UNHEX('6fcc88'), UNHEX('c3b6'))
WHERE id=2
检查查询
SELECT id, txt, txthex hex_original,
CAST(UNHEX(txthex) AS CHAR(30)) unexed_original ,
HEX(txt) hex_replaced
FROM test;
id
txt
hex_original
unexed_original
hex_replaced
1
Auflösungsvertrag
4175666c6fcc8873756e677376657274726167
Auflösungsvertrag
4175666C6FCC8873756E677376657274726167
2
Auflösungsvertrag
4175666c6fcc8873756e677376657274726167
Auflösungsvertrag
4175666CC3B673756E677376657274726167
3
Auflösungsvertrag
4175666cc3b673756e677376657274726167
Auflösungsvertrag
4175666CC3B673756E677376657274726167
我找到了解决方案。
主题叫做 Unicode 等价性,有规范化的方法。
https://en.wikipedia.org/wiki/Unicode_equivalence
PHP 也有一个 class。
https://www.php.net/manual/de/normalizer.normalize.php
我不得不打电话
normalizer_normalize( $myString, Normalizer::NFKC );
首先不是常见的utf8问题。我的应用程序的所有部分都设置为 utf8 并且工作正常。
我通过 PHP 通过 IMAP 收到一封邮件并获取标题。在这个标题中,我有一个特殊的字符。来自德语的 ö。现在我在我的数据库中搜索是否有带有此标题的条目。我知道有一个。数据库使用 utf8mb4_general_ci 进行编码,以便能够存储 4 位 utf8 编码的特殊字符。
来自邮件的标题:
Fw: Auflösungsvertrag
数据库中的条目:
Fw: Auflösungsvertrag
我将光标放在 ö 后面并尝试删除它。首先 ö 切换为 o,在第二次按下删除键后它完全消失了。如果我现在键入并在键盘上输入 ö,MySQL 会找到该条目。
如果我把两者都放在记事本里++ 你看
Fw: Auflösungsvertrag
FW:Auflösungsvertrag
如果将编码转换为 ASCII,您会得到
Fw: AufloÌ^sungsvertrag
Fw: Auflösungsvertrag
所以你现在可以看到两个 ö 的编码不同,但它们显示正确。所以我的 mySQL select 没有找到数据库条目。
谁能给我解释一下,并给我一个提示,让我使用 php 命令将第一个编码字符串转换为第二个编码字符串?
我再描述一下这个问题是如何产生的:
我写了一个票务系统。我发出的每封邮件都会在主题中添加门票 ID。如果我发出一封邮件,我会在外发 table 中将其写入数据库。然后一个 cronjob 将这个邮件发送出去 asyncron。我使用 PHP 邮件程序并通过 SMTP 发送。
我通过 IMAP 和 PHP IMAP 类 获取传入邮件。如果一封邮件在主题中带有 TID,我会将这封邮件合并到数据库中的票证中。所有工单条目均按 TID 列分组。
现在的问题是,如果您将邮件从系统发送到同一系统内的另一个邮件地址,您会将邮件合并到现有票证中。
这就是为什么我通过搜索发件人地址、收件人地址和标题来查看每封收到邮件的外发 table。如果我找到邮件,我就知道系统已经发出了。
所以如果我发送邮件,我有第一个编码。如果我再次收到相同的邮件,它会有其他编码。两种编码接缝都是有效的 utf8 编码。在网站上的任何地方我都能得到正确的字符,在数据库中我也能正确显示它。仅当我通过 PDO 进行 SQL 查询时,MySQL 将它们视为两个不同的字符。
这是我解决这个问题的方法,根据我的说法,它必须在数据库端使用一次性指令一劳永逸地修复,而不仅仅是 PHP 上的技巧,你会每次遇到问题时都要重复一遍。
首先我复制了你的 2 个字符串:
Auflösungsvertrag
Auflösungsvertrag
进入 Notepad++,其中我有(非常方便的)HEX 插件。
当我将文本转换为 HEX 时,我有那些值
4175666c6fcc8873756e677376657274726167
4175666cc3b673756e677376657274726167
如果我们拆分它,我们很容易看到导致问题的 2 ö 的十六进制
4175666c 6fcc88 73756e677376657274726167
4175666c c3b6 73756e677376657274726167
现在的诀窍是告诉 MySQL 将所有具有这些十六进制值的字符从一个替换为另一个,即 6fcc88
到 c3b6
你可以用这个使用 UNHEX() 函数的语句来做到这一点
UPDATE your_table
SET your_column=REPLACE(your_column, UNHEX('6fcc88'), UNHEX('c3b6'))
下面的示例和复制
架构 (MySQL v8.0)
/* Creating test data - Row 1 and 2 are identical */
create table test (id int, txt varchar(50), txthex varchar(100));
INSERT INTO test (id,txt,txthex) VALUES (1, 'Auflösungsvertrag', '4175666c6fcc8873756e677376657274726167');
INSERT INTO test (id,txt,txthex) VALUES (2, 'Auflösungsvertrag', '4175666c6fcc8873756e677376657274726167');
INSERT INTO test (id,txt,txthex) VALUES (3, 'Auflösungsvertrag','4175666cc3b673756e677376657274726167');
应用修复
/* Running oneshot fix on row 2 only */
UPDATE test
SET txt=REPLACE(txt, UNHEX('6fcc88'), UNHEX('c3b6'))
WHERE id=2
检查查询
SELECT id, txt, txthex hex_original,
CAST(UNHEX(txthex) AS CHAR(30)) unexed_original ,
HEX(txt) hex_replaced
FROM test;
id | txt | hex_original | unexed_original | hex_replaced |
---|---|---|---|---|
1 | Auflösungsvertrag | 4175666c6fcc8873756e677376657274726167 | Auflösungsvertrag | 4175666C6FCC8873756E677376657274726167 |
2 | Auflösungsvertrag | 4175666c6fcc8873756e677376657274726167 | Auflösungsvertrag | 4175666CC3B673756E677376657274726167 |
3 | Auflösungsvertrag | 4175666cc3b673756e677376657274726167 | Auflösungsvertrag | 4175666CC3B673756E677376657274726167 |
我找到了解决方案。
主题叫做 Unicode 等价性,有规范化的方法。 https://en.wikipedia.org/wiki/Unicode_equivalence
PHP 也有一个 class。 https://www.php.net/manual/de/normalizer.normalize.php
我不得不打电话
normalizer_normalize( $myString, Normalizer::NFKC );