php面试题2024

基础篇

  • 了解大部分数组处理函数

    strlen():返回字符串的长度

    strpos():查找字符串中某个子串首次出现的位置

    substr():返回字符串的一个子串

    str_replace():替换字符串中的一部分内容

    strtoupper():将字符串转换为大写

    strtolower():将字符串转换为小写

    trim():去除字符串两端的空白字符(或其他指定字符)

  • 字符串处理函数 区别 mb_ 系列函数

    mb_系列函数对适用于中文字符

  • & 引用,结合案例分析

    引用修改变量值的时候会改变原变量的值

  • == 与 === 区别

    == 会进行类型转换来使比较成立,而 === 要求值和类型都必须完全匹配

  • isset 与 empty 区别

    isset()是测试变量是否被赋值;empty()是测试一个已经被赋值的变量是否为空;isset()能传入多个变量;empty()只能传入一个变量

  • 全部魔术函数理解

    1. __construct():构造函数,当一个对象被创建时自动调用。通常用于初始化对象的属性或执行其他设置任务。
    2. __destruct():析构函数,当一个对象不再被引用或脚本执行结束时自动调用。用于执行清理任务,如关闭文件句柄或释放资源。
    3. __get($name):在读取对象中不存在的属性时调用。可以通过该方法实现属性的动态访问。
    4. __set($name, $value):在给对象中不存在的属性赋值时调用。可以通过该方法实现属性的动态设置。
    5. __isset($name):在对不可访问的属性调用 isset() 或 empty() 函数时调用。可以用于自定义属性的 isset 检查行为。
    6. __unset($name):在不可访问的属性上使用 unset() 函数时调用。可以用于自定义属性的 unset 行为。
    7. __call($name, $arguments):在对象中调用一个不存在或不可访问的方法时调用。可以通过该方法实现方法的动态调用。
    8. __callStatic($name, $arguments):在静态上下文中调用一个不存在或不可访问的静态方法时调用。
    9. __toString():在将对象作为字符串使用时调用。通常用于返回对象的字符串表示形式。
    10. __invoke():当尝试以调用函数的方式调用一个对象时调用。可以使对象像函数一样被调用。
    11. __set_state($array):当使用 var_export() 函数导出类的属性时调用。通常用于自定义导出行为。
    12. __clone():当对象被克隆时调用。可以在该方法中实现自定义的克隆行为。
    13. __sleep():在序列化对象时调用,返回一个包含对象中所有应被序列化的属性名称的数组。
    14. __wakeup():在反序列化对象时调用,可以在该方法中执行一些恢复对象的操作
  • static、$this、self 区别

    • static 用于声明静态成员,静态成员可以通过类名直接访问,无需实例化对象。
    • $this 用于访问当前对象的实例成员。在类的非静态方法中,你可以使用 $this 来访问该对象的属性和方法;在静态方法中,不能使用 $this,因为静态方法不依赖于对象实例。
    • self 用于引用当前类的名称,特别是在静态上下文中。通常用于引用当前类的静态成员(属性和方法)。
  • private、protected、public、final 区别

    1. private:表示成员是私有的,只能在当前类中进行访问。
    2. protected:表示受保护权限,体现在继承方面,即子类可以访问父类受保护成员,同时相同包内的其他类也可以访问protected成员。
    3. public:表示成员是公开的,所有其他类都可以访问。
    4. final:用于修饰类、方法、变量。修饰类时,表示该类不能被继承;修饰方法时,表示该方法不能被重写;修饰变量时,表示该变量的值不能被改变。总结就是防止类被继承和防止类的方法被重写。
  • 抽象类、接口使用场景分别是什么

    抽象类的使用场景是作为类的模板,定义一些通用的属性和方法,然后让子类继承并实现抽象方法,达到代码复用的效果;

    接口的使用场景是定义规范,让实现类去实现这些方法,并保证实现类的一致性和可扩展性。

    区别:

    • 抽象类可以有抽象方法和具体方法,而接口中的方法都是抽象的。
    • 一个类只能继承一个抽象类,但可以实现多个接口。
    • 抽象类中可以定义变量,接口中只能定义常量。
    • 抽象类可以有构造方法,接口不能有构造方法。
  • Trait 是什么东西

    Trait(特性)是一种代码复用机制,用于解决单继承语言中的多重继承问题。Trait可以被类使用,以添加额外的功能,而不需要修改类的继承层次结构。Trait可以被多个类使用,并且可以在一个类中使用多个Trait。

  • echo、print、print_r 的区别

    echo:

    • echo 是一个语言结构,而不是一个函数。
    • 它用于输出一个或多个字符串。
    • echo 实际上是一个简写形式,等同于 print
    • 它没有返回值。
    • 可以同时输出多个值,多个值之间用逗号分隔。

    print 是一个函数,和 echo 功能相似。

    • 它也用于输出一个字符串。
    • print 有一个返回值,总是返回 1,表示成功输出。
    • echo 一样,可以输出多个值,但通常只用于输出单个值。

    print_r 是一个函数,用于打印关于变量的详细信息,通常用于调试。

    • 它可以打印出简单变量以及复杂的数据结构,如数组和对象。
    • 对于数组,print_r 会递归地打印出每个元素,包括键和值。
    • 对于对象,print_r 会打印出对象的公共属性。
    • print_r 有返回值,通常返回 true,如果发生错误则返回 false
  • __construct 与 __destruct 区别

    • 参数不同 。construct方法可以接收参数,并在实例化对象时传入;而destruct方法没有参数。
    • 返回值不同 。construct方法没有返回值;而destruct方法也没有返回值。
    • 调用方式不同 。construct方法一般只在实例化对象时调用一次;而destruct方法可能会多次调用,因为一个对象可能会被多次引用。
  • 单引号'与双引号"区别

    双引号字符串会解析其中的变量,而单引号字符串不会;

    双引号字符串会对特定的转义字符进行处理,如换行符(\n)、制表符(\t)等;

    由于双引号字符串需要解析变量和转义字符,因此在处理大量字符串或循环中,使用单引号字符串可能会稍微快一些

  • 依赖注入实现原理

    在需要使用服务的类中,我们只需声明一个与接口或抽象类类型相同的变量,并将其作为类的构造函数参数。然后,在构造函数中,我们可以从服务容器中获取服务实例,并将其赋值给该变量。这样,我们就实现了对该服务的依赖注入。

  • 字符串与数组相互转化的方法

    字符串 –> 数组:explode()

    数组 –>字符串:implode()

实践篇

  • 给定二维数组,根据某个字段排序

    在 PHP 中,你可以使用 usort() 函数来对二维数组进行排序。usort() 函数允许你自定义排序算法。以下是一个示例,展示了如何根据二维数组中的某个字段进行排序:

    <?php
    
    // 示例二维数组
    $students = [
        ['name' => 'John', 'age' => 20],
        ['name' => 'Jane', 'age' => 22],
        ['name' => 'Bob', 'age' => 19],
        ['name' => 'Alice', 'age' => 21],
    ];
    
    // 根据 'age' 字段进行排序
    usort($students, function($a, $b) {
        return $a['age'] - $b['age'];
    });
    
    // 打印排序后的结果
    foreach ($students as $student) {
        echo $student['name'] . ' - ' . $student['age'] . "\n";
    }
    
    ?>
    

    在这个示例中,我们有一个包含学生信息的二维数组。每个学生都有 nameage 两个字段。我们使用 usort() 函数和匿名函数来根据 age 字段进行排序。排序算法是一个简单的比较函数,它返回两个元素之间的差值。如果 $a['age'] 小于 $b['age'],则返回一个负数,这会导致 $a 在排序后的数组中出现在 $b 之前。如果 $a['age'] 大于 $b['age'],则返回一个正数,这会导致 $a 在排序后的数组中出现在 $b 之后。如果两者相等,则返回 0,保持原始顺序。

  • 如何判断上传文件类型,如:仅允许 jpg 上传

    1. 使用pathinfo()函数:
      pathinfo()函数返回一个关联数组,包含有关文件路径的信息,如dirname(目录名)、basename(文件名)、extension(扩展名)等。你可以使用extension键来获取文件的扩展名,然后根据扩展名来判断文件类型。
    $file = '/path/to/file.txt';
    $info = pathinfo($file);
    $extension = $info['extension'];
    
    if ($extension === 'txt') {
        // 是文本文件
    } elseif ($extension === 'jpg') {
        // 是图片文件
    } else {
        // 其他文件类型
    }
    
    1. 使用mime_content_type()函数:
      mime_content_type()函数可以返回文件的MIME类型,这通常比仅仅根据扩展名更可靠,因为它会检查文件的内容。
    $file = '/path/to/file.txt';
    $mime_type = mime_content_type($file);
    
    if ($mime_type === 'text/plain') {
        // 是文本文件
    } elseif (strpos($mime_type, 'image/') === 0) {
        // 是图片文件
    } else {
        // 其他文件类型
    }
    
    1. 使用exif_imagetype()函数:
      对于图片文件,你可以使用exif_imagetype()函数来检查图像的类型。这个函数返回图像的MIME类型对应的常量值。
    $file = '/path/to/image.jpg';
    $image_type = exif_imagetype($file);
    
    if ($image_type === IMAGETYPE_JPEG) {
        // 是JPEG图片
    } elseif ($image_type === IMAGETYPE_PNG) {
        // 是PNG图片
    } else {
        // 其他图片类型或不是图片
    }
    
  • 不使用临时变量交换两个变量的值 $a=1; $b=2; => $a=2; $b=1;

    方法一:使用算术运算

    $a = 5;
    $b = 10;
    
    $a = $a + $b;
    $b = $a - $b;
    $a = $a - $b;
    
    echo "a: $a\n"; // 输出 a: 10
    echo "b: $b\n"; // 输出b: 5
    

    方法二:使用位运算

    $a = 5;
    $b = 10;
    
    $a = $a ^ $b;
    $b = $a ^ $b;
    $a = $a ^ $b;
    
    echo "a: $a\n"; // 输出a: 10
    echo "b: $b\n"; // 输出b: 5
    
  • PHP中删除字符串最后一位字符的方法

    方法一:substr( a r r s t r , 0 , s t r l e n ( arr_str,0,strlen( arrstr,0,strlen(arr_str)-1);

    方法二:substr($arr_str, 0, -1);

    方法三:rtrim($arr_str, “,”);

算法篇

  • 快速排序(手写)

    function quickSort($arr) {
        $length = count($arr);
        
        // 如果数组为空或只有一个元素,则无需排序
        if ($length <= 1) {
            return $arr;
        }
        
        // 选择一个基准值(这里选择第一个元素作为基准值)
        $pivot = $arr[0];
        
        // 初始化两个数组,分别用于存储小于和大于基准值的元素
        $less = [];
        $greater = [];
        
        // 遍历数组,将元素分配到小于和大于基准值的数组中
        for ($i = 1; $i < $length; $i++) {
            if ($arr[$i] < $pivot) {
                $less[] = $arr[$i];
            } else {
                $greater[] = $arr[$i];
            }
        }
        
        // 递归对小于和大于基准值的数组进行排序,并将结果与基准值合并
        return array_merge(quickSort($less), [$pivot], quickSort($greater));
    }
    
    // 示例用法
    $arr = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5];
    $sortedArr = quickSort($arr);
    print_r($sortedArr);
    
  • 冒泡排序(手写)

    function bubbleSort($array){
        $count = count($array);
        for ($i=0;$i<$count-1;$i++){
            for ($j=0;$j<$count-$i-1;$j++) {
                if($array[$j] > $array[$j+1]) {
                    $t = $array[$j];
                    $array[$j] = $array[$j+1];
                    $array[$j+1] = $t;
                }
            }
        }
        return $array;
    }
    
  • 打印乘法表

    for($i=1;$i<=9;$i++)  //控制行数
    {
        for ($k=1; $k <=$i; $k++) { //控制列数
            echo $k."*".$i."=".$k*$i.'\t';
        }
        echo '\n';
    }
    

对比篇

  • Cookie 与 Session 区别

    1. 存储位置不同:Cookie将数据存储在客户端的浏览器中;Session则是将数据存储在服务器端。
    2. 隐私策略和安全性不同:Cookie有被第三方截获的风险,安全性较低;Session则相对安全一些,因为存储在服务器上的数据不易被攻击者获取。
    3. 存储内容不同:Cookie只能存储字符串类型的数据;Session可以存储任意类型的数据。
    4. 生命周期不同:Cookie存在时间可以设置,可以长期保存在客户端;Session存在时间则是由服务器决定,并且通常存活时间要比Cookie短。当用户关闭浏览器或者超过Session存活时间,Session就会被清除掉。
  • includerequire、 `include_oncerequire_once` 的区别

    1. 错误处理
      • include 语句在包含文件失败时,会发出一个警告(warning),并且脚本会继续执行。
      • require 语句在包含文件失败时,会发出一个致命错误(fatal error),并停止脚本的执行。
    2. 性能考虑
      • 在某些情况下,include 可能比 require 稍微快一些,因为它不会导致脚本停止执行。
      • 然而,在大多数情况下,这种性能差异可以忽略不计,更重要的是根据错误处理的需求来选择使用哪一个。
    3. 使用场景
      • 如果你希望包含的文件是可选的,或者你可以处理文件缺失的情况,那么使用 include
      • 如果你希望包含的文件是必需的,并且缺少它会导致脚本无法继续执行,那么使用 require

    include_oncerequire_once,这两个语句与 includerequire 类似,但它们会检查文件是否已经被包含过,如果是,则不会再次包含。这在处理循环依赖或防止重复包含同一文件时非常有用。

  • MySQL 存储引擎MyISAM 与 Innodb 区别

    1. 数据存放结构不同:MyISAM采用非聚簇索引,数据文件和索引文件是分离的;InnoDB采用聚簇索引,数据文件本身就是索引文件。
    2. 是否支持事务:MyISAM不支持事务;InnoDB支持事务。
    3. 是否支持外键:MyISAM不支持外键;InnoDB支持外键。
    4. 锁级别不同:MyISAM仅支持表级锁;InnoDB支持表级锁和行级锁。
    5. 是否保存表的具体行数:MyISAM保存表的具体行数;InnoDB不保存表的具体行数。
  • Apache 与 Nginx 区别

    nginx轻量级,比apache占用更少的内存及资源,抗并发
    nginx处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能。
    apache 相对于nginx 的优点:rewrite比nginx 的rewrite 强大,少bug,稳定。(需要性能用nginx,求稳定就apache)

  • define() 与 const 区别

    1. 定义方式
      • define() 是一个函数,使用它可以定义常量。它的第一个参数是常量的名称,第二个参数是常量的值。
      • const 是一个语言构造符,用于在类、接口或全局范围内定义常量。它的语法是在关键字 const 后面跟着常量名称和值。
    2. 作用域
      • 使用 define() 定义的常量具有全局作用域,可以在任何地方访问。
      • 使用 const 定义的常量具有块级作用域(block scope),只能在定义它的文件、类、接口或方法内部访问。
    3. 类常量与全局常量
      • const 可以在类中定义类常量,这些常量只能在类的内部访问,除非使用 self::static:: 访问符。
      • define() 只能定义全局常量,不能在类内部定义。
    4. 常量名称大小写
      • 使用 define() 定义的常量默认是大小写敏感的。
      • 使用 const 定义的常量默认是大小写不敏感的(类常量除外,它们在类内部是大小写敏感的)。
    5. 常量值类型
      • 使用 define() 定义的常量可以是任何有效的 PHP 表达式。
      • 使用 const 定义的常量必须是常量表达式,这意味着它必须在编译时就能确定值,并且不能包含任何变量、属性访问或函数调用。
    6. 性能
      • 在某些情况下,const 常量可能比使用 define() 定义的常量具有更好的性能,因为它们在编译时就已经确定值,并且可以直接嵌入到生成的代码中
  • traits 与 interfaces 区别 及 traits 解决了什么痛点?

    Interfaces(接口)

    定义

    • 接口是一种定义方法的规范,它声明了一组方法,但没有实现这些方法。
    • 实现接口的类必须实现接口中声明的所有方法。

    特点

    • 接口是隐式抽象的,不能被实例化。
    • 接口不能被继承,但可以被类实现(implement)和多个接口可以被一个类实现(通过逗号分隔)。
    • 接口可以继承其他接口,使用 extends 关键字。

    用途

    • 接口主要用于定义一组行为或契约,确保实现它的类具有某种功能。
    • 它们允许开发者定义一套方法签名,但不关心具体实现。

    Traits(特性)

    定义

    • Traits 是一种代码复用机制,允许开发者创建可复用的代码块,并在多个类中重用这些代码块。
    • Traits 不能被实例化,它们只是包含方法的集合。

    特点

    • Traits 可以被类使用(use),并且可以包含抽象方法和具体方法。
    • 一个类可以使用多个 Traits,并且如果 Traits 之间有方法冲突,可以通过而不是操作符(insteadof)或别名操作符(as)来解决。
    • Traits 不能被继承,但可以被其他 Traits 使用。

    用途

    • Traits 主要用于解决单继承语言(如 PHP)中的多重继承问题。
    • 它们允许开发者将一组方法封装到一个 Traits 中,并在需要时将这些方法添加到类中。
    • Traits 提供了更灵活的代码复用方式,允许开发者在不同的类之间共享代码,而不必通过继承来实现。

    Traits 解决的痛点

    Traits 解决了 PHP 中单继承限制的问题。在 PHP 中,一个类只能继承一个父类,这限制了代码复用的灵活性。通过使用 Traits,开发者可以将一组相关的方法封装到一个 Traits 中,并在多个类中使用这个 Traits,而不需要通过继承来实现代码复用。这使得代码更加模块化、可维护和可扩展。

    此外,Traits 还提供了一种避免方法名冲突的方式。当多个 Traits 被同一个类使用时,如果它们之间存在方法名冲突,开发者可以使用 insteadofas 操作符来解决冲突,确保代码能够正确执行。

    总之,Interfaces 和 Traits 都是 PHP 中用于代码复用的机制,但它们各有特点,适用于不同的场景。Interfaces 主要用于定义行为契约,而 Traits 则提供了一种更灵活的方式来实现代码复用。

  • swoole和workman的区别

    workerman纯php写的,swoole是php的c扩展,性能肯定更高。
    功能上swoole提供的高级特性很多,例如SSL/TLS隧道加密、http2.0、异步mysql驱动、异步redis驱动、异步的http/websocket客户端等。
    外部依赖上workerman需要依赖很多额外的第三方PHP扩展来实现,局限性比较大,这些扩展并非是PHP官方维护的,维护性方面良莠不齐,有些扩展连PHP7都不支持,数年没人维护。而Swoole基本上无依赖,底层的代码全部可控。
    当然workerman的优势是它完全使用PHP代码实现,开发者可以直接看它的源码。有特殊需求也可以直接改源码来实现。

  • redis 和 memache 缓存的区别

    1、存储方式:
    memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
    redis有部份存在硬盘上,这样能保证数据的持久性。
    2、数据支持类型:
    Memcache对数据类型支持相对简单;Redis有复杂的数据类型,支持list、set、sorted set、hash等众多数据结构。
    3、使用底层模型不同:
    新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。

    4、Memcache的速度比Redis更快

数据库篇

  • 说说索引、联合索引

    1.索引的分类

    • 单列索引:一个索引只包含单个列,一个表可以有多个单列索引。
    • 复合索引(联合索引):一个索引包含多个列。复合索引是基于多个列创建的,查询时只有当使用了复合索引的第一个字段时,索引才会被使用。
    1. 联合索引

    联合索引是在多个列上创建的索引,这些列在索引中按照从左到右的顺序存储。当查询语句的 WHERE 子句中使用了联合索引中的第一个字段时,索引才会被使用。如果查询条件中包含了联合索引中的多个字段,那么只有最左边的字段被用来确定索引的使用。

    例如,假设有一个表 employees,其中有一个联合索引 (first_name, last_name)。以下查询会利用这个联合索引:

    SELECT * FROM employees WHERE first_name = 'John' AND last_name = 'Doe';
    

    但是,以下查询则不会利用联合索引:

    SELECT * FROM employees WHERE last_name = 'Doe';
    

    这是因为联合索引是按照 (first_name, last_name) 的顺序创建的,只有当查询条件包含 first_name 时,索引才会被使用。

    1. 索引的优缺点
    • 优点
      • 提高查询速度。
      • 保证数据的唯一性(唯一索引)。
      • 加速表与表之间的连接。
    • 缺点
      • 插入、删除和更新操作可能会变慢,因为需要同时更新索引。
      • 索引会占用额外的磁盘空间。
      • 如果过多地使用索引,可能会导致优化器无法选择最优的查询计划。
  • 分库分表(水平分表垂直分表

    • 水平分表:按照某个字段的值将表拆分成多个表,每个表存储某个范围内的数据
    • 垂直分表:将一个表的列按照业务逻辑分拆到多个表中,每个表只存储某些列
  • Slow Log(怎么打开、有什么用)

    如何启用慢查询日志

    1. 设置配置文件
      在MySQL的配置文件my.cnfmy.ini中,添加或修改以下设置:
    inislow_query_log = 1slow_query_log_file = /path/to/your/logfile.loglong_query_time = 2  # 这里设置阈值,单位是秒,这里设置为2秒
    
    1. 重启MySQL服务
      为了使这些更改生效,你需要重启MySQL服务。

    如何查询慢查询日志

    你可以使用任何文本编辑器或命令行工具(如catlessgrep等)来查看慢查询日志的内容。

    例如,使用grep查找包含特定SQL语句的慢查询:

    bashgrep "SELECT *" /path/to/your/logfile.log
    

    慢查询日志的作用

    1. 识别性能瓶颈
      通过慢查询日志,你可以找到那些执行时间长的查询,这些查询可能是性能瓶颈的原因。
    2. 优化查询
      通过分析慢查询日志中的查询,你可以重写或优化这些查询,以提高它们的执行效率。
    3. 监控数据库活动
      慢查询日志还可以帮助你监控数据库的活动,了解哪些查询最频繁地执行,哪些查询最耗时。
    4. 调整数据库配置
      通过分析慢查询日志,你可以了解数据库的使用情况,并据此调整数据库的配置,如增加缓冲区大小、优化索引等。
    5. 发现潜在问题
      有时候,某些查询可能由于数据表结构不合理、索引缺失或查询逻辑问题而导致执行效率低下。慢查询日志可以帮助你发现这些问题。
  • JOIN、LEFT JOIN 、RIGHT JOIN、INNER JOIN的区别

    • JOIN:就是INNER JOIN,表示以两个表的交集为主,查出来是两个表有交集的部分。
    • LEFT JOIN:就是“左连接”,表示以左边的表为主,关联右边表的数据,查出来的结果显示左边的所有数据,然后右边显示的是和左边有交集部分的数据。
    • RIGHT JOIN:“右连接”,表示以右边的表为主,关联查询左边的表的数据,查出右边表所有数据以及两个表有交集的数据。
    • INNER JOIN:只返回两个表中匹配的行,即只返回连接条件为真的行。如果一个表中的行没有匹配,那么这些行不会出现在结果中。
  • 常用 MySQL 函数

    1. 字符串函数
      • CONCAT:连接两个或多个字符串。
      • LENGTH:返回字符串的长度。
      • LOWER:将字符串转换为小写。
      • UPPER:将字符串转换为大写。
      • TRIM:删除字符串开头和结尾的空格。
      • SUBSTRING:返回字符串的子串。
    2. 数学函数
      • ROUND:返回某个数字四舍五入后的结果。
      • CEIL:返回大于或等于某个数字的最小整数。
      • FLOOR:返回小于或等于某个数字的最大整数。
      • MOD:返回两个数字相除的余数。
      • POW:返回某个数字的指定次幂。
    3. 日期和时间函数
      • NOW:返回当前日期和时间。
      • YEAR:返回日期中的年份。
      • MONTH:返回日期中的月份。
      • DAY:返回日期中的天数。
      • WEEKOFYEAR:返回日期是一年中的第几周
  • 触发器是什么,说个使用场景

    MySQL触发器(MySQL triggers)是一种数据库对象,它与表相关联,并在表中的某个事件发生时自动执行一系列操作.

    MySQL触发器的使用场景有很多:

    1.数据完整性和约束:触发器可以用于实施各种数据完整性约束。
    2.日志记录和审计:触发器可以用于记录对表的数据进行的更改操作,以实现审计和日志记录的功能。
    3.数据同步和复制:在数据库复制和数据同步场景中,触发器可以用于在源数据库上的特定操作触发时,自动在目标数据库上执行相应操作。

  • 数据库优化手段

    1. 使用索引:创建合适的索引可以大大加快查询速度。
    2. 优化查询:尽可能使用简单的查询语句,并确保使用索引来过滤数据,以减少查询时间。
    3. 优化表结构:避免使用大型、不必要的列和表,以及尽可能使用小型数据类型。
    4. 使用存储过程和触发器:可以优化重复性操作的性能,并提高数据完整性。
    5. 使用分区表:将表分为较小的分区可以提高查询性能,并减少查询时间。
    6. 使用缓存:对于经常访问的查询结果,可以考虑使用缓存技术。
    7. 选择合适的存储引擎:根据应用程序的需求,选择适当的存储引擎,如MyISAM或InnoDB。
    8. 优化MySQL配置文件:调整MySQL配置文件中的参数,如缓冲区大小、最大连接数等,以适应应用程序的需要。
  • mysql备份和恢复语句

    备份指定数据库:
    mysqldump -h hostname -u username -p databasename > db.sql

    恢复:
    mysql -h hostname -u username -p databasename < backupfile.sql

  • Redis的数据类型及应用?

    Redis支持五种常用数据类型:
    string(字符串):缓存或计数器,
    hash(哈希):它的结构类似于一个对象,用于存储哈希表或数据表,
    list(列表):内部是一个双向链表,元素可重复,用做队列和栈,
    set(集合):类似数组结构,用作一组数字或字符串的集合,
    zset(sorted set:有序集合):带权值排序的集合,用于排行等

  • 如何解决mysql和Redis数据不一致问题

    • 更新数据库的时候,同步或异步更新redis
    • 设置缓存失效时间,如果有缓存就从缓存中取数据,如果没缓存就从数据库中取数据,并且重新设置缓存
    • 定时任务,按照一定时间间隔更新缓存
  • redis常用命令

    添加键值:set key value
    获取键值:get key
    删除键值:del key
    是否存在:exists key
    设置过期时间:expire key seconds
    获取剩余生存时间:ttl key
    添加hash:HMSET key name “redis tutorial” description “redis basic” (name和description 是属性)
    获取hash:HGETALL key
    添加列表:LPUSH list_name list_item
    取出列表:BLPOP list_name
    遍历列表:LRANGE KEY_NAME START END
    添加集合:SADD set_name set_item
    获取集合:SMEMBERS set_name
    添加有序集合:ZADD KEY_NAME SCORE1 VALUE1
    遍历有序集合:ZRANGE KEY_NAME 0 -1 WITHSCORES

服务器篇

  • cgi、fast-cgi、php-fpm的区别

    • cgi:语言解释器与webwerver的通信协议
    • fast-cgi:是cgi的改良版本,webserver每收到一个请求,都会去fork一个cgi进程,请求结束再kill掉这个进程,发现很浪费资源。fast-cgi每次处理完请求后,不会kill掉这个进程,而是保留这个进程,使这个进程可以一次处理多个请求。这样每次就不用重新fork一个进程了,大大提高了效率
    • php-fpm:即php-Fastcgi Process Manage,是 FastCGI 的实现,并提供了进程管理的功能,包含 master 进程和 worker 进程两种进程,master 进程只有一个,负责监听端口,接收来自 Web Server 的请求,而 worker 进程则一般有多个(具体数量根据实际需要配置),每个进程内部都嵌入了一个 PHP 解释器,是 PHP 代码真正执行的地方
  • php如何安装扩展

    1. 获取扩展源代码:从PECL(PHP Extension Community Library)或扩展的官方网站获取扩展的源代码。PECL是一个流行的PHP扩展仓库。

      使用PECL安装扩展:

      sudo pecl install <extension_name>

      或者,如果你从扩展的官方网站下载了源代码压缩包,你需要解压它。

    2. 编译和安装扩展:进入扩展的源代码目录,并使用PHP的configure脚本和make命令来编译和安装扩展。

      进入扩展源代码目录:

      cd /path/to/extension_source

      配置扩展:

      phpize./configure --with-php-config=/usr/bin/php-config

      编译和安装扩展:

      make

      sudo make install

  • linux常用命令

    • CPU使用率:top

    • 内存使用率:free -h

    • 磁盘使用率:df -h

    • 指定程序的内存占用:先用ps -ef | grep 进程名 获取pid,然后用pmap -x pid

    • 添加可执行权限:chmod u+x xxx.sh ;u代表所有者,x代表执行权限, + 表示增加权限

    • 移动/重命名:mv 目录/文件名 目标目录/文件名

    • 删除目录:rm -rf 目录路径

    • 查看文件:

      (1)tail -f 文件名,查看文件最后几行 ,-n指定显示多少行

      (2)more 文件名,按空格翻页

      (3)less 文件名

      (4)cat -n 文件名,从1开始对所有输出的行数(包括空行)进行编号

    • 压缩: tar -czvf xxx.tar.gz source_file

    • 解压:tar -xzvf xxx.tar.gz

    • 查看端口占用:netstat -anp | grep 80

设计模式

php常用设计模式应用场景及示例 | PHP 技术论坛 (learnku.com)

安全篇

  • SQL 注入

    SQL 注入(SQL Injection)是一种常见的网络攻击手段,攻击者通过在应用程序的输入字段中插入恶意的 SQL 代码,来欺骗数据库服务器执行非授权的任意查询;假设有一个登录表单,其用户名和密码的输入直接用于构建 SQL 查询语句,攻击者可以尝试在用户名或密码字段中输入类似于 ' OR '1'='1 的内容

    防止 SQL 注入的方法主要有以下几种

    1. 参数化查询:使用参数化查询或预编译的语句来构建 SQL 查询,而不是直接将用户输入拼接到 SQL 语句中。这样可以确保用户输入被当作数据来处理,而不是 SQL 代码的一部分。
    2. 使用存储过程:通过存储过程来执行数据库操作,因为存储过程通常更安全,并且参数会被正确地处理。
    3. 输入验证和清理:对用户输入进行验证,只允许符合特定格式和要求的输入通过。此外,清理或转义用户输入中的特殊字符,以防止它们被解释为 SQL 代码的一部分。
    4. 最小权限原则:确保应用程序连接数据库的用户账户只拥有执行必要操作的最小权限。这样即使发生 SQL 注入,攻击者也只能执行有限的操作。
    5. 错误处理:不要向用户显示详细的数据库错误信息,因为这可能会泄露数据库的结构和其他敏感信息,帮助攻击者更容易地进行 SQL 注入攻击。
    6. 使用 Web 应用防火墙(WAF):WAF 可以检测和过滤恶意输入,提供额外的安全层。
    7. 保持软件更新:定期更新数据库管理系统和应用程序框架,以获取最新的安全补丁和修复。
  • XSS 与 CSRF

    XSS(跨站脚本攻击)和CSRF(跨站请求伪造)是两种常见的Web应用程序安全漏洞。为了防止这些攻击,可以采取以下措施:

    防止XSS攻击:

    1. 输入验证和过滤:对所有用户输入进行严格的验证和过滤,确保输入不包含恶意脚本或代码。使用白名单验证方法,只允许已知的、安全的输入通过。
    2. 输出编码:在将用户输入的数据输出到HTML页面时,确保对其进行适当的编码,以防止浏览器将其解释为可执行代码。例如,使用HTML实体编码来转义特殊字符。
    3. 限制使用JavaScript:尽量避免在页面中直接插入用户提供的JavaScript代码。如果必须使用,确保代码在安全的沙箱环境中执行。
    4. 设置HTTP响应头:使用Content-Security-Policy(CSP)响应头来限制页面加载的资源,防止XSS攻击中的脚本注入。
    5. 使用最新的安全技术和框架:保持对Web安全技术的关注,采用最新的安全框架和库,以减少潜在的XSS漏洞。

    防止CSRF攻击:

    1. 使用CSRF令牌:在表单中包含一个随机生成的令牌(token),并在服务器端进行验证。这个令牌应该与用户的会话相关联,并且每次提交表单时都需要包含这个令牌。攻击者无法获取到有效的令牌,因此无法伪造合法的请求。
    2. 验证HTTP请求头:可以检查HTTP请求头中的某些字段,如RefererOrigin,以确保请求来自预期的来源。然而,这种方法并不是完全可靠的,因为攻击者可以伪造这些头信息。
    3. 使用POST方法:对于重要的操作,尽量使用POST方法而不是GET方法。GET方法会将参数暴露在URL中,而POST方法将参数包含在请求体中,相对更安全。
    4. 限制Cookie的使用:对于敏感操作,可以考虑使用HTTP-only和Secure标志设置Cookie,以防止攻击者通过JavaScript访问Cookie信息。
    5. 使用最新的安全技术和框架:保持对Web安全技术的关注,采用最新的安全框架和库,以减少潜在的CSRF漏洞。
  • php输入过滤函数有哪些

    1. htmlspecialchars(): 此函数将特殊字符转换为HTML实体,以防止跨站脚本攻击(XSS)。它将 <, >, &, ", 和 ' 转换为相应的HTML实体,这样浏览器就会将其解释为文本而不是HTML代码。
    2. htmlentities(): 此函数将适用的字符转换为HTML实体。它不仅转换上述的基本字符,还转换其他适用的字符。
    3. strip_tags(): 此函数从字符串中去除HTML和PHP标签。它允许你指定允许的标签,并保留它们。
    4. mysqli_real_escape_string(): 当与MySQL数据库交互时,此函数用于转义SQL查询中的特殊字符,从而防止SQL注入攻击。
    5. trim(): 这个函数用于去除字符串两端的空格或其他预定义字符,这对于清理用户输入很有用
  • 安全的 Session ID (让即使拦截后,也无法模拟使用)

    1. 随机性和唯一性
      • 确保 Session ID 是随机生成的,并且具有足够的长度和复杂性,以抵抗暴力破解和猜测攻击。
      • 使用加密安全的随机数生成器来创建 Session ID。
      • 确保每个 Session ID 都是唯一的,并且不会与其他用户的 Session ID 冲突。
    2. 传输安全
      • 始终通过 HTTPS 协议传输 Session ID,以防止中间人攻击(Man-in-the-Middle, MITM)和其他传输层的安全威胁。
      • 不要通过不安全的 HTTP 协议传输 Session ID,因为它容易受到窃听和篡改。
    3. 存储安全
      • 将 Session ID 存储在安全的服务器上,确保它们不能被未经授权的用户访问。
      • 使用安全的存储机制,如数据库加密或内存缓存,以防止数据泄露。
    4. 会话超时
      • 设置合理的会话超时时间,以确保用户会话不会长时间保持活动状态。
      • 在用户不活跃一段时间后,自动销毁会话并生成新的 Session ID。
    5. 会话固定
      • 防止会话固定攻击,其中攻击者预测或猜测一个有效的 Session ID 并尝试使用它来劫持用户的会话。
      • 不要在客户端代码中硬编码 Session ID,而是应该由服务器生成并通过安全的传输方式发送给客户端。
    6. 会话绑定
      • 将会话绑定到特定的用户身份和/或 IP 地址。这可以通过将会话数据与用户身份验证信息相关联来实现。
      • 如果可能,使用双因素身份验证来增强用户身份验证的安全性。
    7. HTTPOnly 和 Secure 标志
      • 设置 Session ID 的 HTTPOnly 和 Secure 标志,以防止跨站脚本攻击(XSS)和中间人攻击。
      • HTTPOnly 标志确保 Session ID 无法通过客户端脚本(如 JavaScript)进行访问,从而减少了 XSS 攻击的风险。
      • Secure 标志确保 Session ID 仅通过 HTTPS 协议传输,而不是不安全的 HTTP 协议。
    8. 会话失效和刷新机制
      • 定期检查会话是否仍然有效,并在需要时重新生成 Session ID。
      • 可以通过定期检查用户的活动状态、验证用户输入或使用心跳机制来实现。
    9. 日志和监控
      • 记录会话创建、销毁和访问的日志,以便进行安全审计和监控。
      • 监控任何可疑的会话行为,并及时采取行动以防止潜在的安全漏洞。
  • eval 函数执行脚本安全

    1. 避免使用 eval()
      尽量避免使用 eval() 函数,因为它允许执行任意的 PHP 代码,这增加了安全风险。如果可能的话,寻找替代方案,如使用预定义的函数、类方法或闭包。
    2. 用户输入验证和过滤
      在将用户输入传递给 eval() 之前,始终验证和过滤输入。使用 PHP 的过滤函数,如 filter_var(),以及正则表达式来清理和验证数据。确保输入不包含任何潜在的恶意代码。
    3. 限制 eval() 的作用域
      如果必须使用 eval(),尝试限制其执行环境的作用域。例如,你可以使用 sandboxing 技术来创建一个受限的环境,其中 eval() 只能访问特定的函数和资源。
  • disable_functions 常见高危函数

    1. eval(): 由于 eval() 函数可以执行任意 PHP 代码,它通常被视为非常危险的函数,并经常被禁用。
    2. exec(): 该函数用于执行外部程序,并返回最后一行输出。由于它可以执行任意命令,因此通常被禁用。
    3. system(): 与 exec() 类似,system() 函数也用于执行外部程序,但它会将输出直接打印到屏幕上。
    4. passthru(): 该函数将执行外部程序并传递原始输出。由于它可以执行任意命令并返回完整输出,因此也可能被禁用。
    5. shell_exec(): 此函数通过 shell 环境执行命令,并返回完整的输出。由于它提供了对 shell 的访问权限,因此也可能被禁用。
    6. pcntl_exec(): 该函数用于在当前进程空间执行一个程序,并替换当前 PHP 进程。由于它可以执行任意程序,因此可能被禁用。
    7. pcntl_fork(): 此函数用于创建子进程。由于它允许创建新的进程,可能带来安全风险,因此可能被禁用。
    8. pcntl_waitpid(): 该函数用于等待或返回子进程的状态信息。由于它涉及进程管理,可能被禁用。
    9. pcntl_wait(): 与 pcntl_waitpid() 类似,此函数也用于等待子进程结束。
    10. pcntl_wifexited(): 检查子进程是否以正常状态退出。
    11. pcntl_wifstopped(): 检查子进程是否已停止。
    12. pcntl_wifsignaled(): 检查子进程是否因为信号而终止。
    13. pcntl_wexitstatus(): 获取子进程的退出状态。
    14. pcntl_wtermsig(): 获取导致子进程终止的信号。
    15. pcntl_wstopsig(): 获取导致子进程停止的信号。

网络编程篇

  • TCP 三次握手流程

    1. 第一次握手:客户端发送连接请求报文段,将SYN位置为1,SEQ为x,客户端进入SYN_SEND状态,等待服务器确认。
    2. 第二次握手:服务器收到SYN报文段,需要确认客户的SYN,同时自己也发送一个SYN报文段,将SYN位置为1,ACK位置为1,SEQ为y,ACK为x+1,服务器进入SYN_RECV状态。
    3. 第三次握手:客户端收到服务器的SYN+ACK报文,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
  • TCP、UDP 区别,分别适用场景

    • 可靠性 。TCP提供可靠的传输,保证数据的完整性和顺序性;UDP不保证数据的完整性和顺序性1。
    • 速度 。TCP在传输数据之前,需要三次握手来建立连接,并且通过数据校验、拥塞控制、重传控制、滑动窗口和确认应答等机制来实现可靠交付,数据传输过程中,数据无丢失,无重复,无乱序,但大幅度降低了数据传输的实时性;UDP传输控制简单,因此工作效率相对高,对系统资源的要求偏低,实时性较高12。
    • 适用场景 。TCP对数据传输的质量有较高要求,但对实时性要求不高,比如HTTP、HTTPS、FTP等传输文件的协议以及POP、SMTP等邮件传输的协议;UDP对实时性要求高,对数据传输的质量要求不高,比如视频传输、实时通信等
  • 有什么办法能保证 UDP 高可用性(了解)

    • 冗余设计 。在系统中增加备份和冗余组件,以便在主要组件出现故障时,备份组件可以接管工作,保证系统的持续运行2。
    • 错误处理 。在UDP通信过程中,实现适当的错误处理机制,如重传机制、超时机制等,以处理可能出现的丢包、乱序等问题2。
    • 流量控制 。通过限制发送速率、接收窗口大小等方式,避免网络拥塞,保证UDP通信的稳定性
  • TCP 粘包如何解决?

    • 使用消息定界符 。在每个消息的结尾添加一个特定的字符或字符序列作为消息的定界符,接收方可以根据定界符判断消息的结束位置,从而正确地解析消息。
    • 使用消息长度 。在每个消息的开头添加一个指定长度的字段,用于表示消息的总长度,接收方先读取该字段,然后根据长度读取对应的消息,从而正确地解析消息。
    • 使用固定长度的消息 。如果每个消息的长度都是固定的,接收方可以按照固定长度读取数据,从而正确地解析消息1。
    • 使用分隔符 。可以在每个消息之间添加一个分隔符,例如换行符、制表符等,接收方可以根据分隔符判断消息的结束位置,从而正确地解析消息。
    • 应用层协议设计 。在设计应用层协议时,可以将每个消息分为消息头和消息体两部分,消息头中包含消息的长度等信息,从而解决粘包问题
  • 什么是长连接?

    长连接是指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。

    长连接的优点有以下几点:

    1. 减少连接和断开的开销:长连接可以复用已经建立的连接,避免了每次连接时的握手和认证过程,减少了连接和断开的开销,提高了通信的效率。
    2. 实时性和低延迟:通过保持连接,实时数据可以更快地传输到目标端,降低了响应时间和延迟,提供了更好的用户体验。
    3. 节省带宽和资源:长连接可以复用已经建立的连接,避免了每次连接时的握手和认证过程,减少了带宽和服务器资源的消耗。
    4. 更好的稳定性和可靠性:长连接保持持久的通信状态,当网络出现中断或异常时,连接可以自动恢复,确保数据的可靠传输。
  • HTTPS 是怎么保证安全的?

    • 使用对称加密和非对称加密 。HTTPS通信先是使用非对称加密进行密钥的协商,协商出一个对称加密的密钥,之后的通信则采用这个对称密钥进行对称加密密文传输。因为非对称加密其算法极其复杂,导致解密效率低下,而对称加密效率则明显高出百倍。
    • 使用签名算法 。签名算法不是用来做加密的,而是用来确认消息是否被篡改。
    • 使用证书机制 。证书机制是用来确认服务器端的身份。
  • 进程间通信几种方式

    1. 管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
    2. 有名管道:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
    3. 消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
    4. 信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
    5. 信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    6. 套接字:套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
  • 常见 HTTP 状态码及其含义

    • 200:服务器响应正常。
    • 304:该资源在上次请求之后没有任何修改(这通常用于浏览器的缓存机制,使用GET请求时尤其需要注意)。
    • 400:无法找到请求的资源。
    • 401:访问资源的权限不够。
    • 403:没有权限访问资源。
    • 404:需要访问的资源不存在。
    • 405:需要访问的资源被禁止。
    • 407:访问的资源需要代理身份验证。
    • 414:请求的URL太长。
    • 500:服务器内部错误。

API 篇

  • RESTful API是什么

    RESTful API是利用HTTP请求访问或使用数据的应用程序接口(API)的体系结构样式

    RESTful API基于表示性状态转移(REST),它是Web服务开发中经常使用的一种体系结构样式和通信方式

  • API 请求如何保证数据不被篡改?

    1. 使用HTTPS协议:HTTPS协议通过在HTTP协议上添加SSL/TLS加密层,可以对传输的数据进行加密和完整性校验,从而防止数据在传输过程中被篡改。
    2. 消息认证码(MAC):在请求中包含一个由发送方计算并附加的消息认证码。接收方使用相同的密钥和算法重新计算MAC,并与接收到的MAC进行比较。如果两者不匹配,数据可能在传输过程中被篡改。
    3. 数字签名:使用公钥加密技术,发送方使用私钥对请求数据进行签名,接收方使用相应的公钥验证签名。只有拥有私钥的发送方才能生成有效的签名,因此如果签名有效,则可以证明数据未被篡改。
    4. 时间戳和nonce(随机数):在请求中包含一个时间戳和一个nonce,以确保每个请求都是唯一的。接收方可以检查时间戳和nonce的合理性,以防止重放攻击。
    5. 请求参数的校验:在接收请求时,对请求参数进行校验,确保参数符合预期的数据类型和格式。如果参数不符合预期,可以拒绝处理该请求。
    6. 限制请求频率:对来自同一客户端的请求频率进行限制,以防止恶意用户通过发送大量请求来尝试篡改数据。
    7. 使用API密钥或OAuth认证:为每个API请求提供身份验证机制,如API密钥或OAuth令牌。只有经过验证的请求才能访问API,这可以防止未经授权的访问和篡改。
  • JSON 和 JSONP 的区别

    1. 性质不同:JSON是一种基于文本的数据交换方式;JSONP是一种非官方跨域数据交互协议。
    2. 安全性不同:JSON只支持同源数据请求;JSONP支持跨域数据请求,安全性较低。
    3. 格式不同:JSON返回的是一串数据;JSONP返回的是脚本代码(包含一个函数调用)。
    4. 用途不同:JSON是理想的数据交换格式;JSONP是获取数据的方法。
  • 常见的对称加密算法和非对称加密算法

    常见的对称加密算法有DES(Data Encryption Standard)、3DES(Triple DES)、AES(Advanced Encryption Standard)和RC4(Rivest Cipher 4)等。在对称加密中,发送者和接收者需要事先共享同一个密钥,并且使用该密钥进行加密和解密操作。

    常见的非对称加密算法有RSA(Rivest, Shamir, Adleman)、DSA(Digital Signature Algorithm)和ECC(Elliptic Curve Cryptography)等。在非对称加密中,公钥用于加密数据,而私钥用于解密数据或者进行数字签名

  • OAuth 2 主要用在哪些场景下

    • 第三方登录 。当自己开发的系统需要引入微信、QQ、支付宝、钉钉等第三方登录时,可以使用OAuth2.0协议。
    • 开放平台 。当自己开发的系统需要开放一些接口,用来给第三方应用去查询使用相关的信息时,也可以使用OAuth2.0协议。
    • SSO单点登录 。当自己开发的系统是在微服务环境下使用SSO相关的场景时,可以使用OAuth2.0协议实现单点登录。
  • JWT是什么

    JWT是JSON Web Token的简称,是一种用于身份验证和授权的开放标准(RFC 7519),它是一种安全的、轻量级的身份验证方式

  • 15
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一些常见的 PHP 面试题: 1. 什么是 PHP?它有哪些特性? PHP 是一种开源的服务器端脚本语言,用于开发 Web 应用程序。它具有易于学习,适用于大多数 Web 服务器和操作系统,支持多种数据库等特点。 2. PHP 有哪些数据类型? PHP 支持八种数据类型:整数,浮点数,字符串,布尔型,数组,对象,NULL,资源。 3. 如何在 PHP 中创建一个函数? 函数可以使用 function 关键字来创建。例如: ``` function myFunction() { // 函数体 } ``` 4. 如何使用 PHP 连接数据库? 可以使用内置的 mysqli 或 PDO 扩展来连接数据库。例如,使用 mysqli: ``` $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // 创建连接 $conn = new mysqli($servername, $username, $password, $dbname); // 检查连接 if ($conn->connect_error) { die("连接失败: " . $conn->connect_error); } ``` 5. 如何在 PHP 中处理表单数据? 可以使用 $_POST 或 $_GET 数组来获取表单数据。例如: ``` if($_SERVER["REQUEST_METHOD"] == "POST") { // 获取表单数据 $name = $_POST["name"]; $email = $_POST["email"]; } ``` 6. 如何在 PHP 中使用会话? 会话用于在不同页面之间存储用户信息。可以使用内置的 session 函数来创建和管理会话。例如,创建一个会话: ``` // 开始会话 session_start(); // 存储数据到会话中 $_SESSION["username"] = "John"; // 获取会话中的数据 echo "用户名是:" . $_SESSION["username"]; ``` 这些是一些常见的 PHP 面试题,希望对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值