将UTF-8支持添加到JS/PHP脚本

11

我正在处理一个页面,该页面使用JavaScipt通过AJAX POST向PHP脚本发送数据。问题是,如果输入的语言不是拉丁语系,我最终会在MySQL表中存储无意义的字符。拉丁字母表运作正常。

页面本身能够呈现UTF-8字符,如果它们是在页面加载时提供的数据,那么就没有问题,但是对于提交我遇到了困难。

اختبار

并保存。查看浏览器开发工具中的网络POST请求。

该POST是通过以下JS函数进行的。

function createEmptyStack(stackTitle) {
    return $.ajax({
        type:'POST',
        url:'ajax.php',
        data: {
            "do": 'createEmptyStack',
            newTitle: stackTitle
        },
        dataType: "json"
    });
}

这是我的PHP代码。

header('Content-Type: text/html; charset=utf-8');

$newTitle = trim($_POST['newTitle']);

$db->query("
INSERT INTO t1(project_id, label) 
VALUES (".$_SESSION['project_id'].", '".$newTitle."')");

当我像这样检查页面的编码时:

mb_detect_encoding($_POST['newTitle'], "auto");

我得到的结果是:UTF-8

我还尝试了以下头部:

header("Content-type: application/json; charset=utf-8");

MySQL数据表的排序规则被设置为utf8_general_ci,以确保数据存储在正确的位置。

我有另一个页面,其中包含一个表单,用户可以使用任何语言填写相同的表格,并且可以完美地工作。当我查看其他页面时,发现它能够成功地将类似的数据插入数据库,在插入查询之前会添加以下内容:

mysql_query("SET NAMES utf8");

我尝试在查询上方放置相同的行,但数据仍然看起来像无意义的字符。我还尝试了以下几个替代方法:

 mysql_query("SET CHARACTER SET utf8 ");

mysql_set_charset('utf8', $db);

...但是没有成功。我被难住了,需要帮助找出问题所在。

环境:

PHP 5.6.40 (cgi-fcgi)

MySQL 5.6.45


更新:

我进行了更多测试。

我用阿拉伯语短语“这是一个测试”- هذا اختبار。

看起来ajax.php代码正常工作。在插入数据库后,它返回UTF-8编码的值,看起来像:“\u0647\u0630\u0627 \u0627\u062e\u062a\u0628\u0627\u0631”,并且编码设置为:“UTF-8”,但是在我的数据库表中插入的数据显示为:هذا اختبار。

那么为什么我不立即将我的数据库表转换为不同的排序方式?有几个原因:它有近50万条记录,并且当我转到执行非常类似的INSERT的另一页时,它实际上可以正常工作。

结果发现,我在其他页面上插入数据时使用ASCII编码。因此,我尝试在ajax.php中转换为ASCII是很自然的选择。然而,我最终得到了空白数据的问题。我现在非常困惑...

谢谢


已解决:根据一些线索,我最终重写了此页面的所有函数为PDO,它奏效了!


您的服务器是否启用了MBString? - Prabhjot Singh Kainth
问题可能在于您的数据库排序规则。 - MontrealDevOne
3
请勿使用PHP的mysql_*接口。请转换为PDO或mysqli_*。 - Rick James
请展示给我们这些无意义的字符;这将有助于调试问题。 - Rick James
1
警告:您的应用程序容易受到SQL注入攻击,因此应该使用参数化的预处理语句而不是手动构建查询语句。它们由PDOMySQLi提供。永远不要相信任何输入!即使您的查询仅由可信用户执行,数据仍然可能被损坏转义并不足够安全! - Dharman
显示剩余3条评论
5个回答

5

المراكز 是 Mojibake,或可能是“双重编码”,表示为 المراكز。请执行 SELECT col, hex(col) ... 以查看哪个类似于以下内容:

Mojibake:D8A7D984D985D8B1D8A7D983D8B2
双重编码:C398C2A7C399E2809EC399E280A6C398C2B1C398C2A7C399C692C398C2B2

如果是 Mojibake:

  • 要存储的字节需要使用 UTF-8 编码。修复此问题。
  • 在插入和选择文本时,连接需要指定 utf8 或 utf8mb4。修复此问题。
  • 必须声明该列为 CHARACTER SET utf8(或 utf8mb4)。修复此问题。
  • HTML 应以 <meta charset=UTF-8> 开始。

如果是双重编码:这是由于将 Latin1(或其他编码)转换为 UTF-8,然后将这些字节视为 Latin1 并重复转换而导致的。

更多讨论:

UTF-8 字符出现问题;我看到的与我存储的不同

不要使用 PHP 中的 mysql_* 接口;切换到 mysqli_* 或 PDO 接口。 mysql_* 已在 PHP 5.7 中被删除。


哇,这太棒了。肯定有些线路交叉了。我刚刚再试了一次。使用存储值为 INSERT 时,它看起来像:هذا اختبار,如果我用 utf8_encode() 包装它,则存储为:هذا اختبار,如果我尝试 utf8_decode() 则得到 ??? ??????。 - santa
搞定了!我最终将整个例程重写为PDO,它起作用了。 - santa
<meta charset=UTF-8> 在你的回答中被隐藏了。 - Paul Spiegel
1
请不要在mysql中使用utf8,因为它只会使用3个字节的utf8,而utf8实际上支持多达4个字节。这意味着您只能正确处理长达3个字节的字符。Unicode大量使用第4个字节,例如表情符号。一定要始终使用utf8_mb4(多字节4)。此外,请确保在每个表、数据库、连接、源文件和html上始终正确设置编码。 - Daidon
我同意utf8mb4是首选,但在5.5和5.6版本中(OP正在使用),存在一些问题,例如最大索引大小。同时,阿拉伯语在任何字符集中都能很好地工作。 - Rick James
@santa - 那个带有波浪线A的字符串可能是阿拉伯语的“双重编码”。也就是说,它重新搞乱了文本! - Rick James

3
如果您的数据库是latin1,则会将Unicode字符存储为多字节字符。如果它基于utf-8,则仍会存储多个字符,但以更“明智”的方式显示。
假设您的ر字符表示为XYZ(3个字节),那么当您检索XYZ时,浏览器将重新组合它们成为可见的ر。
但是,如果您的数据库是utf-8,则它将进一步对每个组件进行编码,以便您最终“可靠地”看到XYZ。假设X表示为x1,x2,Y只是y,而Z是z1,z2,z3,因此,您现在看到的是x1x2yz1z2z3,而不是存储为XYZ的ر,后者被显示为XYZ。
尝试将数据库转换为latin1以至少确认我的理论。谢谢。
编辑:
无需使用utf8 js库。确保页面的字符编码为utf8。
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

当您使用XHR请求发送数据时,可以在发送之前使用encodeURIComponent对其进行编码。我不确定jQuery中的$.ajax是否已经进行了编码。

我根据最新的测试结果更新了原始帖子,并添加了更多信息。 - santa
Latin 1 可以表示 256 个代码点。UTF-8 可以表示整个 Unicode 定义的代码点集,其数量达到 2^21 个代码点。 - Dragonthoughts
当阿拉伯语被塞入latin1时,会变得混乱不堪。 - Rick James

0

这是我用来让你的代码工作的:

<?php

$db = mysqli_connect("localhost", "root", "", "demo");
$db->set_charset("utf8");

// Check connection
if ($db === false) {
    die("ERROR: Could not connect. " . mysqli_connect_error());
}

$newTitle = trim($_POST['newTitle']);

$db->query("
        INSERT INTO t1(project_id, label) 
        VALUES ('5', '" . $newTitle . "')");

将此标签添加到您的HTML头部:

<meta charset="utf-8">

我已经测试了 latin1_binutf8_bin,在两种情况下都可以正常工作。

PHP 版本 7.3.9

MySQLi 5.0.12-dev


警告:您的程序容易受到SQL注入攻击,应该使用参数化的预处理语句来代替手动构建查询。它们可以通过PDOMySQLi提供。永远不要相信任何输入!即使您的查询只由可信用户执行,您仍然有破坏数据的风险转义是不够的! - Dharman
不建议使用 utf8,请使用 utf8mb4 字符集! - Dharman
显然,我没有提供连接数据库的完整示例。我的主要目的是展示如何插入数据,并在查看数据库时正确显示它。我了解SQL注入、PDO和MySQLi。这段代码仅用于演示目的。 - Kalimah
如果您要推荐一个您知道并不是最佳做法的技术,请不要将其添加到您的脚本中,并且非常清楚地说明您的脚本的漏洞,以便不知情的研究人员不盲目信任您的代码。 - mickmackusa
显示剩余3条评论

0

大约一年前,我在使用运行MySQL 5.7的系统时遇到了一个非常类似的问题。听起来你的一些数据库设置是utf8,但应该设置为utf8mb4。这样可以使数据库正确处理多字节字符。

注意:utf8mb4编码是在MySQL 5.5版本中添加的。

以下查询可用于更改您的编码。请确保根据您的情况更改数据库、表和列名称以及列数据类型:

# For each database:
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
# For each table:
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# For each column:
ALTER TABLE table_name CHANGE column_name column_name DATATYPE CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

可以在这里找到更详细的解释。

你的 MySQL 版本(5.6)的文档。

当前的 MySQL 8 文档。


请参见 https://dev59.com/WFoT5IYBdhLWcg3wnQn9 。注意 ALTER -- 如果使用错误的语句,可能会使情况变得更糟。请参见 http://mysql.rjweb.org/doc.php/charcoll#fixes_for_various_cases。 - Rick James

0

UTF8的话题有点复杂。

在使用MySql时,重要的是要理解,MySql的UTF8仅支持3个字节的数据,即使标准规范允许最多4个字节。在unicode中,实际上有很多字符需要使用这第4个字节,比如像这样的表情符号。 使用utf8mb4,您可以完全支持并保存这些数据到数据库中,没有任何问题。但单独使用UTF8会让您失望。

只要遵循这些规则,就应该没问题:

  • 确保所有源文件都是UTF8编码。
  • 确保在php.ini中将utf8设置为默认字符集:

    default_charset = "utf-8"
    
  • 确保在html头部使用utf-8字符集:

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    
  • 确保将header字符集设置为UTF8:

    header("Content-type: application/json; charset=utf-8");
    
  • 确保在PDO中将MySql连接设置为utf8mb4

    $dsn='mysql:host=example.com;dbname=testdb;port=3306;charset=utf8mb4';
    
  • 确保在utf8mb4中创建数据库或转换数据库(如果必须):

    CREATE DATABASE mydatabase CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    
  • 确保在utf8mb4中创建表或转换表(如果必须):

    CREATE TABLE my_table ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
    
  • 非常重要:确保在PHP中使用mb_字符串函数,因为普通的字符串函数只会假定单字节数据。这意味着,你应该使用mb_strlen来代替strlen,因为它会计算每个字符的长度。此外,简单的错误,如将字符串作为数组访问,会破坏你的代码,因为$string[0]只会访问你的字符串的第一个字节,即使你的第一个字符可能有4个字节。在这种情况下,请使用mb_substr

对于最后一个,您需要php的mbstring扩展。此外,请注意,某些扩展需要先加载mbstring,因此您加载扩展的顺序可能很重要,以防需要安装它。

另外作为附注:请务必使用PDO和预处理语句。您会在网上找到大量教程。SQL注入仍然是网络上最大的漏洞,而预处理语句是防止SQL注入最有效的方法!

如果您遵循上面的列表,就不会再有问题了。

祝您玩得愉快。

一些参考资料: https://mathiasbynens.be/notes/mysql-utf8mb4


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接