PHP – 最佳实践

在 PHP 中开发 Web 应用程序时,您应该遵循一些好的做法。其中大多数都非常容易上手,其中一些甚至适用于一般的 Web 开发。

1. POST 请求成功后重定向。

这不是特定于 PHP 的。为避免用户刷新浏览器并两次提交相同的表单数据的情况,您应该始终使用 Post/Redirect/Get (PRG) 模式。

一个基本的例子:

//Process form data here.

//If the form submission was successful.
if($success){
    //Redirect the user.
    header('Location: page.php?msg=success');
    exit;
}

2. 不要使用 mysql_* 函数。

自 PHP 5.5 起,mysql 函数已被正式弃用。根据 PHP 网站的说法,未来旧的 MySQL 扩展将被完全删除。

如果这不能说服您找到替代方案,那么您还应该考虑它缺乏对许多 MySQL 功能的支持这一事实。最为显着地:

  • 准备好的陈述。
  • 交易
  • 存储过程。
  • 异步查询。
  • 多个陈述。

这个古老的扩展是为 MySQL 3.23 版本构建的。

从那时起,几乎没有添加任何功能。综观这一切,当前的 MySQL 版本是 5.6,其中 3.23 早在 1999 年就发布了!

好的,那我用什么代替呢?

两个不错的选择是PDOMySQLi

就个人而言,我更喜欢使用 PDO,因为它提供了数据访问抽象层,这基本上意味着您也可以使用相同的函数来访问其他数据库(PostgreSQL、SQLite 等)。

3. 不要关闭你的 PHP 标签。

许多开发人员会(通常是虔诚地)在他们的文件末尾放置一个结束 PHP 标记,如下所示:

 

<?php

class MyClass{
    public function test(){
        //do something, etc.
    }
}

?>

这样做的问题是,如果开发人员不够小心,它可能会引入空格或换行符。

当标题被中断或空白字符莫名其妙地出现在输出中时,这可能会导致以后头痛(这实际上发生在我上周的一位同事身上)。

好的,那我该怎么做呢?

这是完全可以接受的:

<?php

class MyClass{
    public function test(){
        //do something, etc.
    }
}

老实说,您真正应该关闭 PHP 标记的唯一时间是在使用 PHP 和 HTML 进行模板化时:

<h1><?php echo $title; ?></h1>
<p><?php echo $description; ?></p>

4. 防范XSS!

XSS(跨站脚本)是一个允许攻击者在您的网站上执行 JavaScript 的漏洞。

例如,如果我在评论表单中输入了一些 JavaScript,并且您在未对其进行清理的情况下显示该评论,那么每当用户加载页面时,相关代码就会执行。

为了防御这种类型的漏洞,您应该在将用户提交的数据打印到页面之前对其进行清理。为此,您可以使用函数 htmlspecialchars:

echo htmlspecialchars($userComment, ENT_QUOTES, 'utf-8');

此函数会将特殊字符转换为相关的 HTML 实体,以便安全显示。

5. 不要回显 HTML!

请不要回显 HTML

它看起来很乱,您的 IDE 将无法突出显示相关的 HTML 元素,并且对 PHP 没有信心的设计人员会发现很难编辑或添加新功能。

相反,您应该执行类似于第 3 点中所示示例的操作。

6. 将您的逻辑与输出分开!

没有什么比尝试在逻辑与输出交织在一起的庞大代码库上工作更令人生畏的了。

要将逻辑与演示分开,您可以使用 Laravel 等 MVC 框架或 Twig 等模板引擎。那里有很多不同的选择,所以一定要货比三家。

至少,在构建 Web 应用程序时,您应该始终尝试并执行此原则。

7. 了解什么是 DRY。

DRY 代表不要重复自己。

这意味着您应该避免重复完全相同的代码的情况。这可以通过使用包含、函数和类来完成。

例如,如果我有一段代码可以计算一个人的年龄,我可以创建如下函数:

function calculateAge($dateOfBirth){
    $birthday = new DateTime($dateOfBirth);
    $interval = $birthday->diff(new DateTime);
    return $interval->y;
}

现在,每当我想计算用户的年龄时,我都可以调用上面的函数。这是有利的,因为:

  1. 结果我的代码库变小了。
  2. 如果我需要调整逻辑,我可以简单地编辑 calculateAge 函数。换句话说,我不必搜索我的代码并编辑多个实例。

8.永远不要相信你的用户!

正如我们在第四点中强调的那样,用户可能是恶意的。

这意味着您必须假设您的访问者会积极尝试利用他们在您的代码中发现的任何漏洞来构建您的 Web 应用程序。

需要这种心态,尤其是在您开发对公众开放的网站时。在某些情况下,这些漏洞可以被漏洞扫描程序和网络爬虫发现。

9. 不要在循环内运行查询!

在大循环中运行查询可能会非常昂贵。

在很多情况下,循环会变大,从而导致越来越多的查询。这可能导致网页加载缓慢,并且数据库承受的压力远远超过应有的压力。

大多数时候,使用这些“循环查询”是因为所讨论的开发人员还没有了解 SQL 连接的重要性。

10. 散列用户密码!

用户密码应经过哈希处理,而不是以纯文本或基本编码形式存储。

散列函数是单向街道。一旦明文密码通过它,就无法取回它(因此我们使用术语“散列”而不是“加密”)。

如果你使用 PHP >= 5.5,你应该使用函数password_hash。如果您卡在较早的版本上,那么您可以使用 Github 上的 password_combat 库

需要注意的是,md5 和 sha1 等函数不适用于此目的。一个好的密码散列函数应该非常慢,以至于试图破解它是不切实际的。

阅读: 使用 PHP 保护密码。

11. 使用准备好的语句!

如果您回顾第二点,您会发现我们建议 在mysql_*函数上使用PDO 。

使用 PDO 的主要优点之一是它允许您利用准备好的语句。过去,PHP 开发人员被迫使用诸如mysql_real_escape_string 之类的函数,如下所示:

$username = mysql_real_escape_string($username, $connection);
$result = mysql_query("SELECT name FROM users WHERE username = '$username'");

此函数转义可能会干扰您的 SQL 的特殊字符。

但是,它不会保护您免受不涉及特殊字符(例如 x00、n、r、 、'、" 和 x1a )的攻击。还有一个问题是,未设置正确的字符集会使它对某些攻击无用。

幸运的是,SQL 注入无法匹配准备好的语句。使用prepared statements,在发送数据之前将SQL语句发送到服务器。这使它们彼此独立。

这意味着数据库在发送任何潜在危险字符之前就知道它需要执行什么语句。使用 PDO 对象选择行的示例:

//We prepare the SQL statement. At this stage it is sent off to the database server.
$stmt = $db->prepare("SELECT name FROM users WHERE username = :username");

//We bind our parameters / data.
$stmt->bindParam(':username', $username);

//We execute the statement.
$stmt->execute();

重要提示: 使用 PDO 扩展,您将需要手动启用“自然”预准备语句的使用。出于可移植性的目的,此扩展默认使用模拟的准备好的语句。要关闭模拟的预处理语句,您可以使用以下代码:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

12. “or die()” 需要死掉……

如果您决定完全忽略有关切换到 PDO 的所有要点,那么我认为可以公平地说您可能正在处理失败的查询,如下所示:

mysql_query($sql, $conn) or die(mysql_error($conn));

上述方法的问题是您无法捕获错误或记录错误。您也无法控制它是否打印到屏幕上。

在 die 函数眼里,开发服务器和直播服务器完全是一回事!它无法通过 ini 设置或站点范围的配置文件进行控制。

更好的方法是使用异常,因为它们可以被捕获处理

$res = mysql_query($sql, $conn);
if($res === false){
    throw new Exception(mysql_error($conn));
}

可以使用 TRY CATCH 块捕获上述异常,也可以使用自定义异常处理程序进行处理。这使您可以更好地控制错误的处理方式。

当然,如果您使用的是 PDO 扩展,那么您可以使用以下属性默认抛出异常:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

13. 电子邮件验证。

避免使用正则表达式来验证电子邮件地址。Internet 上流传的大多数正则表达式示例的问题在于它们不符合 RFC 822 语法。

换句话说,他们实际上会拒绝有效的电子邮件地址。最好的方法是像这样使用 filter_var 函数:

if(!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
    //Email is invalid. Show the user an error message.
}

请注意,许多网站 通过向相关地址发送基于令牌的电子邮件来验证电子邮件地址。

14.避免短标签。

您应该避免使用短标签,除非您将代码部署到您可以配置的服务器上。

在某些共享主机上,您会发现默认情况下禁用短标签,并且您无法更改任何 PHP 的配置设置。

因此,为了确保某种程度的可移植性,您应该坚持使用<?php,而不是<? . 它可能会在将来为您省去很多麻烦。

15. 避免微优化。

在一些不同的帮助网站上,我看到 PHP 开发人员强调微优化。

例如,您经常会遇到诸如“复制变量会减慢我的应用程序吗?”之类的问题。

答案是:这可能无关紧要。

如果你已经到了复制变量成为性能问题的地步,那么你手头就会遇到更大的问题!相反,您应该遵循最佳实践并专注于编写干净、可读的代码。

这些事情中的大多数都是如此渺小和微不足道,以至于它们永远不会 成为问题。

16. 了解数据库规范化。

数据库规范化是所有开发人员都应该知道的,无论他们使用什么语言。

设计不佳的数据库可能会导致错误的代码。这是因为程序员将被迫弥补缺乏适当的结构。

如果您是 Web 开发人员,那么您很可能必须在某个时候创建​​一个关系数据库。到那时,您将被迫熟悉实体化和建立关系的过程。

17. 保持一致。

一致性是程序员可以拥有的最重要的属性之一。

不得不翻阅由不一致的编码人员编写的代码库类似于在雷区中奔跑。混乱的文件夹结构。具有不合适的功能的类。驼峰式大小写和下划线在整个项目中可互换使用。

这可能是一团糟。

关于一致性的一个小注释。

18. 版本控制。

无论您的应用程序是单页网站还是庞大的复杂怪物,都无关紧要。在这个时代,使用Git等版本控制软件 是必须的。优点?

  • 您可以轻松恢复到应用程序的旧版本。
  • 它使维护多个不同版本变得更加容易。这很好,因为它允许您尝试某些功能。
  • 它有助于防止您意外覆盖其他人的工作的情况。
  • 它使其他人能够提交对您的代码库的更改。
  • 它可以帮助您找出导致引入新错误的更改。

19. 字节码缓存。

默认情况下,PHP 将始终像新代码一样执行您的代码(它不是编译语言)。在每个请求上,PHP 都会解析您的代码并将其转换为字节码以便执行。

无论您的代码是否几个月未动,都会发生这种情况。显然,这会浪费服务器资源。

幸运的是,如果您可以完全控制托管应用程序的服务器,则可以轻松实现字节码缓存。如果您运行的是 PHP 5.5 或更高版本,则可以使用当前内置于 PHP中的Opcache 。

如果您不幸卡在旧版本上,您可以使用另一个称为APC的字节码缓存。

这些工作通过将生成的字节码存储在内存中以便可以重复使用。

20. 了解常见的设计模式。

设计模式是常见问题的可重用解决方案。了解经过验证 的设计模式的来龙去脉可以帮助您加快开发过程。

它还可以使其他开发人员更容易阅读和理解您的代码。

对我们来说幸运的是,Github 上有大量 特定于 PHP 的示例。在那里,您将看到模式的 PHP 实现,例如工厂模式和依赖注入。

21. 如有疑问,请使用 UTF-8!

不确定在您的网页上使用什么字符集?使用UTF-8

<meta charset="utf-8">

不确定要为您的数据库表使用什么编码?使用utf8_unicode_ci!连接到您的数据库?嗯,你明白了:

$db = new PDO('mysql:host=localhost;dbname=database;charset=utf8', 'root', '');

基本上,您需要在整个应用程序中使用 UTF-8!请记住:链条的强度取决于其最薄弱的环节!未能在您的应用程序中的某个地方使用 UTF-8,您最终可能会得到乱码!

22. 了解使用 MVC 框架的优势。

您可能不需要使用MVC 框架,但您应该 了解使用它的优点。

  • 执行关注点分离设计原则。
  • 可以更好地重用代码。
  • 许多 MVC 框架都预先打包了许多有用的实用程序类和库,可帮助您快速解决常见问题,例如用户身份验证和发送电子邮件。这可以帮助您加快开发速度。
  • 正如第 20 点所述:了解 MVC 框架的开发人员会发现您的代码更易于理解(与您自己想象的自定义设计相反)。

有很多很棒的 PHP MVC 框架。仅举几例:

如果您想了解更多信息,请阅读Jeff Atwood 的这篇文章

23. 掌握 Web 应用程序安全的一些基础知识。

XSS 和 SQL 注入并不是您需要注意的唯一漏洞。CSRF 和Session Fixation带来的安全风险 也应该是您最关心的问题。如果您是 PHP 开发新手,您可能应该查看官方网站的 Security 页面。在那里,您会找到很多有用的信息,这些信息与您在开发应用程序时需要考虑的不同类型的安全漏洞有关。以与空字节有关的问题为例,其中 $_GET['file'] 是“../../etc/passwd”:

<?php
$file = $_GET['file'];
if (file_exists('/home/wwwrun/'.$file.'.php')) {
    // file_exists will return true as the file /home/wwwrun/../../etc/passwd exists
    include '/home/wwwrun/'.$file.'.php';
    // the file /etc/passwd will be included
}

在这里,您可以看到攻击者可能会强制您的 Web 应用程序包含 /etc/passwd 文件。

如需进一步了解 Web 应用程序安全性,请务必查看OWASP

24. 知道使用什么数据库列类型。

过去,我遇到过多个使用不正确数据类型的开发人员实例。有时,看到一个伟大的项目被糟糕的数据库相关决策所拖累,这有点令人沮丧。有些人会将他们的价格数据存储在 VARCHAR 列中。其他人不会知道 SMALLINT、INT 和 BIGINT 之间的区别。示例:您知道TINYINT (3) 中的 3 与列的存储大小完全无关吗?您是否知道在 VARCHAR 列中存储日期是完全愚蠢的,因此您将无法使用日期函数?您是否知道 BIGINT 可能会造成浪费,仅仅是因为您不太可能需要存储与 18446744073709551615 ? 您的文本列会要求您使用TEXT 或 MEDIUMTEXT吗?这些都是可以通过快速阅读手册来回答的问题。基本上,RTFM!

我最近写了一篇文章,名为:MySQL:使用哪些列? 这将有望引导初学者开发人员朝着正确的方向前进。

25. 不要用正则表达式解析 HTML。

当您可以使用诸如Document Object Model之类的 DOM 解析器时,为什么还要使用正则表达式来解析 HTML ?用正则表达式解析 HTML 充其量是很棘手的,它最终会导致代码庞大且不可维护。如果新添加的元素属性破坏了您的代码怎么办?只需像理智的人一样使用 XML 解析器/库。

示例:从 HTML 中提取链接。

26. var_dump,不要回显。

我经常遇到开发人员使用 echo 来“调试”他们的变量,尽管 echo 会遗漏很多重要信息。另一方面,var_dump会告诉您正在处理的变量类型。它还将帮助您定位空白和换行问题。运行以下示例:

$str = 'Hello '; //Example whitespace issue.
echo $str;
var_dump($str);

哪个更有用?

27. 测试您的应用程序。

单元测试是最流行的测试方法之一。基本上,它涉及将您的代码库分解为更小的部分(通常是函数和对象方法),以便您可以以孤立的方式测试它们。就目前而言,PHPUnit是最流行的 PHP 测试框架。一定要通读他们的启动指南。做几个例子并弄脏你的手将帮助你更好地理解它。

如需进一步阅读,请务必查看有关测试驱动开发的 Wiki 文章。

28. 存储上传的图片。

上传的图像应存储在文件系统中,然后通过数据库进行引用。即上传文件到您的网络服务器,然后将图像的文件路径存储在表格列中。除非 完全有必要,否则不应将图像存储在数据库中!为什么?

  • 在许多情况下,您会发现数据库存储比文件系统存储更昂贵/有限。许多托管解决方案就是这种情况。
  • 它可能会给您的数据库增加(不必要的)压力。
  • 访问数据库中的图像可能比访问文件系统上的图像要慢得多。
  • 数据库变得更大。即备份需要更长的时间并且维护数据库的复杂性通常会增加。
  • Facebook等高流量网站更喜欢文件系统存储。
  • 您将无法利用任何云存储解决方案。
  • 访问网络服务器上的图像不需要额外的编码/处理。
  • 如果您将图像存储在数据库中,您可能会失去基于操作系统的优化,例如 sendfile。

29. 在上传时重新调整图片大小。

使用 PHP 即时调整图像大小可能会占用大量资源。在大多数情况下,与处理典型的 PHP 网页相比,您在调整图像大小时会使用更多的 CPU 和 RAM。更糟糕的是,动态调整图像大小的影响会随着每个缩略图的显示而恶化。一个更有效的解决方案是在图像上传后立即重新调整大小。即重新调整图像大小并创建一两个不同大小的副本。磁盘空间很便宜。CPU功率和RAM?没那么多。

30. 文件。

选择一种评论“风格”,然后坚持下去(参见第 17 点关于保持一致)。例如:  phpDocumentor 是一个允许您自动为代码生成文档的工具,只要 您坚持它们的样式/语法:

/**
 * Description of the function goes here.
 * 
 * @param int $num Small description about this parameter.
 * @return boolean Small description about the return value.
 */
public function isOne($num){
    if($num === 1){
        return true;
    }
    return false;
}

31.了解==和===的区别

作为 PHP 开发人员,您绝对应该花时间阅读有关比较运算符的官方文档页面。了解 == 和 === 之间的区别至关重要。考虑以下:

<?php
$a = 1;
$b = "01";
if($a == $b){
    echo 'True!';
}

上面的 IF 语句将等同于 true,尽管 $b 是一个字符串而 $a 是一个整数。这是因为类型杂耍。基本上,$a 和 $b 被认为是相等的,因为 PHP 在进行比较之前会将 $a 和 $b 转换为整数(在这种情况下,我们认为它是“松散”比较)。另一方面,=== 仅当两个变量相等且它们属于同一类型时才等于 true。运行下面这段代码,你会发现输出是“False!” 这是因为 $a 和 $b 不是同一类型:

<?php
$a = 1; //integer
$b = "01"; //string
//This will equate to FALSE because $a is an int and $b is a string.
if($a === $b){
    echo 'True!';
} else{
    echo 'False!';
}

公平地说,不知道 == 和 === 之间的区别将不可避免地导致代码错误。以以下示例/陷阱为例:

<?php
$a = false;
$b = "";
if($a == $b){
    echo 'Both $a and $b are considered to be false!';
}

不久前,我遇到了一个问题,我们现有的一个 cron 脚本抛出了错误的错误。基本上,有问题的脚本是向特定 URL 发出 HTTP 请求。请求完成后,自定义错误处理程序会报告它已失败,尽管我们都知道它已成功完成。深入研究代码后,我发现编写错误处理程序的人使用了如下松散比较:

<?php
$res = file_get_contents($url);
if($res == false){
    //request failed
}

上面代码的问题是,如果 $url 的输出为空(在本例中是),则请求将被视为失败。file_get_contents文档页面上的一条消息警告我们:

此函数可能返回布尔值 FALSE,但也可能返回计算结果为 FALSE 的非布尔值。请阅读有关布尔值的部分以获取更多信息。使用 === 运算符测试此函数的返回值。

32. 对象缓存。

在许多情况下,通过将昂贵的数据库查询的结果存储在内存中来缓存它们可能是有益的(从内存中检索数据比从磁盘检索数据要快得多)。考虑以下场景:

  • 您拥有一个社交网站。
  • 在您的主页上,您会显示新注册用户的列表。
  • 您的主页每分钟被访问 500 次。
  • 因此,选择这些用户的数据库查询每小时执行 30,000 次。

公平地说,所有这些都有些浪费,仅仅是因为:

  1. 这不是一个重要的功能。如果您考虑一下:它的唯一真正目的是让其他用户知道该网站处于活动状态。
  2. 如果有问题的列表是五分钟还是十分钟,访问者不会在意。

要将此查询的结果缓存在内存中,我们可以使用对象缓存系统,例如MemcachedRedis。举个例子,它使用 PHP 的Memcached Extension

//Attempt to get the newly-registered list from Memcached.
$userList = $memcached->get('newly_registered');

//If $userList is FALSE, it means that our list doesn't exist in Memcached.
if($userList === false){

    //Select the last 10 users from our database.
    $userList = $this->User->getNewlyRegistered(10);

    //Store the result in Memcached for 5 minutes so that any preceding
    //visitors in the next 5 minutes will be able to access it via
    //the cache.
    $memcached->set('newly_registered', $userList, time() + 300);
}

var_dump($userList);

上面的代码将尝试从 Memcached 中检索用户列表。如果列表不存在,它将运行查询,然后将结果存储在 Memcached 中 5 分钟(一旦 5 分钟结束,Memcached 将从缓存中逐出数据并且 Memcached::get 将再次返回 FALSE)。这意味着我们的数据库查询只有在密钥过期并且数据被自动“驱逐”时才会执行。即我们现在每小时运行 12 个查询而不是 30,000 个。

33. HTML 不提供验证!

这与我关于不信任您的用户的观点有关!在很多情况下,我遇到过似乎认为 SELECT 元素的值不需要由服务器验证的开发人员。

<select name="gender">
    <option value="1">Male</option>
    <option value="2">Female</option>
</select>

“值只能是1或2,对吧?!” 错误的!

许多初学者似乎认为 SELECT 元素的值是可以信任的,因为它“限制”了用户并迫使他们从预定义的值列表中进行选择。

不幸的是,情况并非如此,因为攻击者可以通过打开 Firebug 或 Chrome 开发工具轻松编辑 SELECT 元素的值。这也适用于隐藏的表单字段!如果我打开 Firebug 并将“1”更改为“Hello”会怎样?如果我用“3”代替“2”怎么办?如果我修改了隐藏输入字段的值会发生什么?您的应用程序将如何反应?

结论:任何人都可以编辑 HTML 。表单值可以被篡改,字段可以轻松删除。

34. JavaScript 验证不能代替服务器端验证!

依赖客户端验证的 Web 表单数量绝对令人恐惧!如果用户决定禁用 JavaScript,会发生什么?如果攻击者决定打开 Chrome 开发者工具并修改您的代码会怎样?说得直白点:JavaScript 应该与 HTML 一样被对待。两者都发送到浏览器。两者都可以由最终用户修改。他们谁都不能信任。

35. 了解 PHP 中的错误报告。

PHP 中有许多不同类型的错误。一些最常见的是:

  • E_ERROR:这是一个致命的运行时错误。您的应用程序无法从 E_ERROR 中恢复。因此,脚本被停止。示例原因:尝试调用不存在的函数。
  • E_PARSE:每当 PHP 无法解析/编译您的代码时,就会发生这种情况。因此,您的脚本将不会运行。示例原因:未能正确关闭括号。
  • E_WARNING: 这是一个运行时警告,不会阻止应用程序的其余部分运行。示例:尝试访问不存在的文件或 URL。
  • E_NOTICE: 每当 PHP 遇到可能指示错误的事情时,就会发生 E_NOTICE。示例:尝试访问不存在的数组索引。
  • E_STRICT: 每当 PHP 警告您代码的未来兼容性时发生。原因示例:使用已弃用的函数或语言特性。

开发环境

在开发环境中,您应该 显示所有错误。在开发环境中隐藏警告和通知是不好的做法,仅仅是因为您应该修复根本原因;不要试图掩盖它们!像这样在地毯下扫除“污垢”可能会导致出现难以识别的恼人错误!要显示所有可能的 PHP 错误,您可以在PHP.ini文件中插入以下指令:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

如果您无权访问 PHP.ini 文件,可以将以下代码放在脚本顶部

error_reporting(-1);
ini_set("display_errors", 1);

制作/现场环境

在生产/实时环境中,应记录错误,但不向最终用户显示。不建议在实时环境中显示 PHP 错误。这是因为:

  1. 它们对用户不友好。
  2. 警告和通知可能会破坏您网站的显示/布局。
  3. 它们可以为攻击者提供有关应用程序内部工作的关键信息。

要隐藏 PHP 错误,您可以在 PHP.ini 文件中插入以下指令:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

如果您无权访问 PHP.ini 文件,可以将以下代码放在脚本顶部:

error_reporting(0);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值