使用PsySH进行交互式PHP调试

本文介绍了PsySH,一个PHP的Read-Eval-Print Loop (REPL)工具,用于提高调试效率。与PHP的交互式控制台相比,PsySH在变量赋值后立即显示结果。文章通过实例展示了如何在命令行、嵌入式服务器和单元测试中使用PsySH进行调试,揭示了其在Web应用中的强大功能,并强调了与单元测试和phpUnit结合使用的优势。
摘要由CSDN通过智能技术生成

It’s 1:00 a.m., the deadline for your web application’s delivery is in 8 hours… and it’s not working. As you try to figure out what’s going on, you fill your code with var_dump() and die() everywhere to see where the bug is…

现在是凌晨1:00,Web应用程序的交付截止日期为8小时……并且无法正常工作。 当您尝试找出正在发生的情况时,请在各处使用var_dump()die()填充代码,以查看错误的位置…

You are annoyed. Every time you want to try out a return value or variable assignment, you have to change your source code, execute your application, and see the results… In the end, you are unsure of whether or not you’ve removed all those var_dumps from the code. Is this situation familiar to you?

你很生气 每次要尝试返回值或变量赋值时,都必须更改源代码,执行应用程序并查看结果……最后,您不确定是否已从中删除所有这些var_dumps。代码。 您是否熟悉这种情况?

PsySH进行救援 (PsySH to the rescue)

PsySH is a Read-Eval-Print Loop (or REPL). You may have used a REPL before via your browser’s javascript console. If you have, you know that it possesses a lot of power and can be useful while debugging your JS code.

PsySH是一个读评估打印循环(或REPL )。 您可能已经通过浏览器JavaScript控制台使用过REPL。 如果有的话,您会知道它具有强大的功能,并且在调试JS代码时很有用。

Talking about PHP, you might have used PHP’s interactive console (php -a) before. There, you can write some code and the console will execute it as soon as you press enter:

在谈论PHP之前,您可能曾经使用过PHP的交互式控制台( php -a )。 在那里,您可以编写一些代码,控制台将在您按Enter键后立即执行它:

php -a
Interactive shell

php > $a = 'Hello world!';
php > echo $a;
Hello world!
php >

Unfortunately, the interactive shell is not a REPL since it lacks the “P” (print). I had to execute an echo statement to see the contents of $a. In a true REPL, we would have seen it immediately after assigning the value to it.

不幸的是,交互式外壳不是REPL,因为它缺少“ P”(打印)。 我必须执行echo语句才能看到$a的内容。 在真正的REPL中,我们将在为其分配值后立即看到它。

You can install PsySH globally either with a composer g require, or downloading the PsySH executable:

您可以使用composer g require全局安装PsySH,也可以下载PsySH可执行文件:

Composer

作曲家

composer g require psy/psysh:~0.1
psysh

Direct download (Linux/Mac)

直接下载 (Linux / Mac)

wget psysh.org/psysh
chmod +x psysh
./psysh

Additionally, you can have it included per project with composer as you’ll see later in this article.

此外,您可以在composer中将其包含在每个项目中,如本文稍后所见。

Now let’s play with PsySH a little.

现在让我们玩一下PsySH。

./psysh                                                                                                                                             

Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman                                                                                                                                                                              
>>>

The main help will be your best friend. It’s what will give you all sorts of commands and their explanations:

主要帮助将是您最好的朋友。 它将为您提供各种命令及其解释:

>>> help

  help      Show a list of commands. Type `help [foo]` for information about [foo].      Aliases: ?
  
  ls        List local, instance or class variables, methods and constants.              Aliases: list, dir
  
  dump      Dump an object or primitive.
  
  doc       Read the documentation for an object, class, constant, method or property.   Aliases: rtfm, man 
  
  show      Show the code for an object, class, constant, method or property.
  
  wtf       Show the backtrace of the most recent exception.                             Aliases: last-exception, wtf?
  
  trace     Show the current call stack.
  
  buffer    Show (or clear) the contents of the code input buffer.                       Aliases: buf
  
  clear     Clear the Psy Shell screen.
  
  history   Show the Psy Shell history.
  
  exit      End the current session and return to caller.                                Aliases: quit, q
>>> help ls

Usage:

ls [--vars] [-c|--constants] [-f|--functions] [-k|--classes] [-I|--interfaces] [-t|--traits] [-p|--properties] [-m|--methods] [-G|--grep="..."] [-i|--insensitive] [-v|--invert] [-g|--globals] [-n|--internal] [-u|--user] [-C|--
category="..."] [-a|--all] [-l|--long] [target]

Aliases: list, dir

Arguments:

 target             A target class or object to list.
 
 
Options:

 --vars             Display variables.
 
 --constants (-c)   Display defined constants.
 
 --functions (-f)   Display defined functions.
 
 --classes (-k)     Display declared classes.
 
 --interfaces (-I)  Display declared interfaces.
 
 --traits (-t)      Display declared traits.
 
 --properties (-p)  Display class or object properties (public properties by default).
 
 --methods (-m)     Display class or object methods (public methods by default).
 
 --grep (-G)        Limit to items matching the given pattern (string or regex).
 
 --insensitive (-i) Case-insensitive search (requires --grep).
 
 --invert (-v)      Inverted search (requires --grep).
 
 --globals (-g)     Include global variables.
 
 --internal (-n)    Limit to internal functions and classes.
 
 --user (-u)        Limit to user-defined constants, functions and classes.
 
 --category (-C)    Limit to constants in a specific category (e.g. "date").
 
 --all (-a)         Include private and protected methods and properties.
 
 --long (-l)        List in long format: includes class names and method signatures.
 
 
 Help:
 
 List variables, constants, classes, interfaces, traits, functions, methods, and properties.
 
 Called without options, this will return a list of variables currently in scope.
 
 If a target object is provided, list properties, constants and methods of that target. If a class, interface or trait name is passed instead, list constants and methods on that class.
 
 e.g. 
 
 >>> ls
 >>> ls $foo
 >>> ls -k --grep mongo -i
 >>> ls -al ReflectionClass
 >>> ls --constants --category date
 >>> ls -l --functions --grep /^array_.*/
 >>>

Basically, what a REPL can do is:

基本上,REPL可以做的是:

>>> $a = 'hello';
=> "hello"
>>>

Please note that if we compare PsySH against PHP’s interactive console, PsySH prints the $a value as soon as it is assigned.

请注意,如果我们将PsySH与PHP的交互式控制台进行比较,则PsySH将在分配$a值后立即打印它。

A more complex example can be as follows:

一个更复杂的示例如下:

>>> function say($a) {
...     echo $a;
... }
=> null
>>> say('hello');
hello
=> null
>>>

I defined the function say() and invoked it. Those two null you see are because neither the function definition nor its execution returned a value (the function echoes the value). Additionally, while defining the function, the prompt changed from >>> to ....

我定义了函数say()并调用了它。 您看到的那两个null是因为函数定义及其执行均未返回值(函数回显该值)。 此外,在定义函数时,提示从>>>更改为...

Can we define a class and instantiate it?

我们可以定义一个类并实例化它吗?

>>> class Foo
... {
...     protected $a;
...
...     public function setA($a) {
...         $this->a = $a;
...     }
...
...     public function getA() {
...         return $this->a;
...     }
... }
=> null
>>> $foo = new Foo();
=> <Foo #000000001dce50dd000000002dda326e> {}
>>> $foo->setA('hello');
=> null
>>> $foo->getA();
=> "hello"
>>>

When I instantiated Foo, the constructor returned a reference to the object. This is why PsySh printed <Foo #000000001dce50dd000000002dda326e>. Now let’s see what is interesting about PsySH and objects.

当我实例化Foo时,构造函数返回对该对象的引用。 这就是PsySh打印<Foo #000000001dce50dd000000002dda326e> 。 现在,让我们看看有关PsySH和对象的有趣之处。

>>> ls $foo
Class Methods: getA, setA
>>>

If by any chance you forgot which methods the class Foo has defined, you now have the answer. Have you used a Linux OS or Mac command line interface? Then you might be familiar with the ls command. Remember the -la options?

如果您有机会忘记了Foo类定义了哪些方法,那么现在就可以找到答案。 您是否使用过Linux OS或Mac命令行界面? 然后,您可能会熟悉ls命令。 还记得-la选项吗?

>>> ls -la $foo
Class Properties:

  $a   "hello" 
  

Class Methods:

  getA   public function getA()
  setA   public function setA($a)

Sweet, isn’t it?

甜蜜,不是吗?

PsySH’s true power shines when integrated with a web application, so let’s build one.

当PsySH与Web应用程序集成时,其真正的强大之处将散发出光芒,因此让我们构建一个。

演示应用 (Demo app)

Decorator Design Pattern

I’m going to implement a quick application to showcase the decorator design pattern. The UML class diagram of such a pattern is as follows:

我将实现一个快速的应用程序来展示装饰器的设计模式 。 这种模式的UML类图如下:

Do not worry if you do not know much about UML or design patterns, understanding them is not required for this article.

如果您不太了解UML设计模式 ,请不要担心,本文不需要了解它们。

Also for this project, I created a set of test cases. Those test cases can be run by phpUnit. Again, you do not have to be familiar with Unit Testing to understand this article.

同样对于这个项目,我创建了一组测试用例。 这些测试用例可以由phpUnit运行。 同样,您不必熟悉单元测试即可理解本文。

The complete source code for this little application can be found at https://github.com/sitepoint-examples/PsySH

可以在https://github.com/sitepoint-examples/PsySH中找到此小应用程序的完整源代码。

First of all, let’s define our composer.json file in order to declare a dependency on PsySH:

首先,让我们定义composer.json文件,以声明对PsySH的依赖关系:

{
    "name": "example/psysh",
    "authors": [
        {
            "name": "John Doe",
            "email": "john@doe.tst"
        }
    ],
    "require": {
        "psy/psysh": "~0.1"
    },
    "autoload": {
        "psr-4": {"Acme\\": "src/"}
    }
}

After a composer install you should be good to go.

composer install之后,您应该一切顺利。

Please take a look at the following source code from the file public/decorator.php. It will instantiate the SimpleWindow, DecoratedWindow, and TitledWindow objects to showcase the decorator pattern:

请查看以下来自文件public/decorator.php源代码。 它将实例化SimpleWindow,DecoratedWindow和TitledWindow对象以展示装饰器模式:

<?php
chdir(dirname(__DIR__));

require_once('vendor/autoload.php');

use Acme\Patterns\Decorator\SimpleWindow;
use Acme\Patterns\Decorator\DecoratedWindow;
use Acme\Patterns\Decorator\TitledWindow;

echo PHP_EOL . 'Simple Window' . PHP_EOL;

$window = new SimpleWindow();

echo $window->render();

echo PHP_EOL . 'Decorated Simple Window' . PHP_EOL;

$decoratedWindow = new DecoratedWindow($window);

echo $decoratedWindow->render();

echo PHP_EOL . 'Titled Simple Window' . PHP_EOL;

$titledWindow = new TitledWindow($window);

echo $titledWindow->render();

We can execute the code via PHP’s CLI (command line interface) or through a webserver if one is configured. We can use PHP’s internal web server too.

我们可以通过PHP的CLI(命令行界面)或网络服务器(如果已配置)执行代码。 我们也可以使用PHP的内部Web服务器。

在CLI中进行调试 (Debugging in cli)

The execution of the above code through the command line interface will look like this:

通过命令行界面执行上述代码将如下所示:

php public/decorator.php 

Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Decorated Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Titled Simple Window
+-------------+
|Title        |
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

How can we interact with PsySH? Just add \Psy\Shell::debug(get_defined_vars()); anywhere on the code where you want to debug your application, typically where you would insert a var_dump() statement:

我们如何与PsySH互动? 只需添加\Psy\Shell::debug(get_defined_vars()); 您想要调试应用程序的代码上的任何位置,通常是在其中插入var_dump()语句的位置:

<?php
chdir(dirname(__DIR__));

require_once('vendor/autoload.php');

//... a lot of code here

$titledWindow = new TitledWindow($window);

echo $titledWindow->render();

\Psy\Shell::debug(get_defined_vars()); //we want to debug our application here!

After saving the file, we will get the following output:

保存文件后,我们将得到以下输出:

php public/decorator.php 

Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Decorated Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Titled Simple Window
+-------------+
|Title        |
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman
>>>

The script’s execution will be suspended, and we now have PsySH’s prompt to play with. I am passing get_defined_vars() as a parameter to Psy\Shell::debug() so I have access to all defined variables inside the shell:

该脚本的执行将被暂停,现在我们可以使用PsySH进行提示。 我将get_defined_vars()作为参数传递给Psy\Shell::debug()因此我可以访问Psy\Shell::debug()中的所有已定义变量:

>>> ls
Variables: $_COOKIE, $_FILES, $_GET, $_POST, $_SERVER, $argc, $argv, $decoratedWindow, $titledWindow, $window
>>>

Let’s examine the $window variable:

让我们检查$window变量:

>>> ls -al $window

Class Methods:
  render   public function render()  
>>>

Something nice about having PsySH inside an application is that we can examine the source code of an instantiated object.

在应用程序内部包含PsySH的好处是,我们可以检查实例化对象的源代码。

>>> show $window
class Acme\Patterns\Decorator\SimpleWindow implements Acme\Patterns\Decorator\Window
class SimpleWindow implements Window
{
    public function render()
    {
        $returnString = <<<EOL
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+
EOL;
        return $returnString . PHP_EOL;
    }
}
>>>

So, $window is an instance of SimpleWindow, which implements the Window interface… I wonder what the source code for the Window interface looks like…

因此, $window是SimpleWindow的实例,该实例实现了Window接口……我想知道Window接口的源代码是什么样的……

>>> show Acme\Patterns\Decorator\Window
interface Acme\Patterns\Decorator\Window
interface Window
{
    public function render();
}
>>>

Why do SimpleWindow and DecoratedWindow have the same output? Let’s examine the $decoratedWindow object.

为什么SimpleWindow和DecoratedWindow具有相同的输出? 让我们检查$decoratedWindow对象。

>>> ls -al $decoratedWindow

Class Properties:
  $windowReference   <Digitec\Patterns\Decorator\SimpleWindow #000000003643d67700000000731101b7>  

Class Methods:
  __construct          public function __construct(Digitec\Patterns\Decorator\Window $windowReference)         
  getWindowReference   public function getWindowReference()                                                    
  render               public function render()                                                                
  setWindowReference   public function setWindowReference(Digitec\Patterns\Decorator\Window $windowReference)  
>>>

This object is “heavier” than the SimpleWindow one, so the source code might be long… Let’s see the source code for the render() method only:

该对象比SimpleWindow对象“重”,因此源代码可能很长……让我们仅查看render()方法的源代码:

>>> show $decoratedWindow->render
public function render()
    public function render()
    {
        return $this->getWindowReference()->render();
    }

The getWindowReference() method is invoked, and then it returns the result from the render() method. Let’s check the getWindowReference() source:

调用getWindowReference()方法,然后从render()方法返回结果。 让我们检查一下getWindowReference()源:

>>> show $decoratedWindow->getWindowReference
public function getWindowReference()
    public function getWindowReference()
    {
        return $this->windowReference;
    }
>>>

This method is returning the object’s windowReference property, and as we saw from the ls -al command above, it is an instance of Acme\Patterns\Decorator\SimpleWindow. Of course, we could have just looked at how DecoratedWindow::__construct() works, but this is another way we can check.

此方法返回对象的windowReference属性,正如我们从上面的ls -al命令看到的那样,它是Acme\Patterns\Decorator\SimpleWindow一个实例。 当然,我们可以看看DecoratedWindow::__construct()工作方式,但这是我们可以检查的另一种方法。

使用嵌入式服务器进行调试 (Debugging with embedded server)

Unfortunately, debugging through a webserver like Apache is not supported. However, we can debug our application using PHP’s embedded server:

不幸的是,不支持通过Web服务器(如Apache)进行调试。 但是,我们可以使用PHP的嵌入式服务器调试应用程序:

$ cd public
$ php -S localhost:8080
PHP 5.5.8 Development Server started at Wed Jul 23 17:40:30 2014
Listening on https://localhost:8080
Document root is /home/action/workspace/lab/PsySH/public
Press Ctrl-C to quit.

The Development Server is now listening for connections on port 8080, so as soon as we request the decorator.php file through this web server (https://localhost:8080/decorator.php), we should see the following:

开发服务器现在正在侦听端口8080上的连接,因此,只要我们通过此Web服务器( https:// localhost:8080 / decorator.php )请求decorator.php文件,就应该看到以下内容:

Psy Shell v0.1.11 (PHP 5.5.8 — cli-server) by Justin Hileman
>>>

We can begin playing with PsySH just as we did with the CLI

我们可以像使用CLI一样开始使用PsySH。

>>> ls -al

Variables:
  $_COOKIE           []                                                                              
  $_FILES            []                                                                              
  $_GET              []                                                                              
  $_POST             []                                                                              
  $_SERVER           Array(19)                                                                       
  $decoratedWindow   <Acme\Patterns\Decorator\DecoratedWindow #0000000031ef2e3e000000003c2d3a90>  
  $titledWindow      <Acme\Patterns\Decorator\TitledWindow #0000000031ef2e39000000003c2d3a90>     
  $window            <Acme\Patterns\Decorator\SimpleWindow #0000000031ef2e3f000000003c2d3a90>     
  $_                 null                                                                            
>>> exit
Exit:  Goodbye.

使用单元测试进行调试 (Debugging with unit tests)

As a good developer, you should write unit tests for your code as proof that it is working as expected. In the project’s files you will find the tests folder, and if you have phpUnit installed, you can run the tests inside it.

作为一名优秀的开发人员,您应该为代码编写单元测试,以证明其按预期工作。 在项目的文件中,您将找到tests文件夹,并且如果安装了phpUnit,则可以在其中运行测试。

cd tests/
phpunit
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml

...F+-------------+
|Title        |


Time: 66 ms, Memory: 4.50Mb

There was 1 failure:

1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle
Failed asserting that true is false.

/home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46
                                     
FAILURES!                            
Tests: 4, Assertions: 7, Failures: 1.

Even though the code appears to run flawlessly, a test is failing. We can examine further by running just the failing test:

即使代码看起来可以完美运行,但测试失败。 我们可以通过仅运行失败的测试来进一步检查:

$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml


Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'.
F+-------------+
|Title        |


Time: 60 ms, Memory: 4.25Mb

There was 1 failure:

1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle
Failed asserting that true is false.

/home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46
                                     
FAILURES!                            
Tests: 1, Assertions: 1, Failures: 1.

We have the test, file and line where the error is being generated. Let’s take a look at TitledWindowTest.php

我们具有生成错误的测试,文件和行。 让我们看一下TitledWindowTest.php

<?php
namespace AcmeTest\Patterns\Decorator;

use PHPUnit_Framework_TestCase;
use Acme\Patterns\Decorator\TitledWindow;
use ReflectionMethod;


class TitledWindowTest extends PHPUnit_Framework_TestCase 
{
    public function testRender()
    {
        /* some test code here */
        
    }
    
    public function testAddTitle()
    {
        $renderString = 'bar';
        
        $window = $this->getMock('Acme\Patterns\Decorator\SimpleWindow');
        $window->expects($this->any())->method('render')->will($this->returnValue($renderString));
        
        $titledWindow = new TitledWindow($window);
        
        $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle');
        $reflectionMethod->setAccessible(true);
        
        $rs = $reflectionMethod->invoke($titledWindow);
        
        $this->assertFalse(empty($rs));
        
    }
}

If you are unfamiliar with phpUnit, do not pay too much attention to the code. In a nutshell, I’m setting up everything to test the TitledWindow::addTitle() method, and expecting to receive a non empty value.

如果您不熟悉phpUnit,请不要过多地关注代码。 简而言之,我将设置所有内容以测试TitledWindow::addTitle()方法,并期望接收一个非空值。

So, how do we use PsySh to check what is going on? Just add the Shell::debug() method as we did previously.

那么,我们如何使用PsySh检查发生了什么? 只需像以前一样添加Shell::debug()方法即可。

<?php
namespace DigitecTest\Patterns\Decorator;

use PHPUnit_Framework_TestCase;
use Digitec\Patterns\Decorator\TitledWindow;
use ReflectionMethod;

class TitledWindowTest extends PHPUnit_Framework_TestCase 
{
    public function testRender()
    {
        /* Some test code here */
    }
    
    public function testAddTitle()
    {
        $renderString = 'bar';
        
        $window = $this->getMock('Digitec\Patterns\Decorator\SimpleWindow');
        $window->expects($this->any())->method('render')->will($this->returnValue($renderString));
        
        $titledWindow = new TitledWindow($window);
        
        $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle');
        $reflectionMethod->setAccessible(true);
        
        $rs = $reflectionMethod->invoke($titledWindow);
        
        \Psy\Shell::debug(get_defined_vars());
        
        $this->assertFalse(empty($rs));
        
    }
}

We are ready to rock!

我们准备摇滚!

$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml


Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'.
Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman
>>>

So in $rs we should have a string; let’s see what we really have.

因此,在$rs我们应该有一个字符串; 让我们看看我们真正拥有的。

>>> $rs
=> null

Null value, no wonder the test failed… Let’s check the source code of TitledWindow::addTitle(). If we perform an ls command, we can see that we have that object’s method available through the $titledWindow object.

空值,难怪测试失败了……让我们检查TitledWindow::addTitle()的源代码。 如果执行ls命令,可以看到可以通过$titledWindow对象使用该对象的方法。

>>> show $titledWindow->addTitle
protected function addTitle()
    protected function addTitle()
    {
        $returnString = <<<EOL
+-------------+
|Title        |
EOL;
        echo $returnString . PHP_EOL;
    }
>>>

There is the bug. The method is echoing the value instead of returning it. Even though the application seems to work right, through unit testing and PsySH we discovered a defect and we can now fix it.

有错误。 该方法回显值而不是返回值。 即使该应用程序似乎正常运行,但通过单元测试和PsySH,我们发现了一个缺陷,现在可以对其进行修复。

结论 (Conclusion)

This article was not meant to be exhaustive in showcasing all the potential PsySH has. There are other cool features (like ‘doc’) that you should try out. PsySH alone may not be very useful, but if combined with other tools and your clever debugging powers, it can prove to be a valuable asset.

本文并非旨在详尽地展示PsySH的所有潜在功能。 您还应该尝试其他一些很酷的功能(例如“ doc”)。 单独使用PsySH可能不是很有用,但是如果与其他工具结合使用以及您精巧的调试功能,则可以证明PsySH是有价值的资产。

翻译自: https://www.sitepoint.com/interactive-php-debugging-psysh/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值