杰里6916_杰里特很棒

杰里6916

In the past week I’ve started using Gerrit for all development on SQLAlchemy, including for pull requests, feature branch development, and bug fixes across maintenance branches. I was first introduced to Gerrit through my involvement in the Openstack project, where initially I was completely bewildered by it. Eventually, I figured out roughly enough what was going on to be productive with it and to ultimately prefer it for adding code changes to a project. But making the big leap of actually moving SQLAlchemy and my other key projects over to it required the extra effort of learning Gerrit a little more fundamentally as well as installing and integrating it with SQLAlchemy’s existing workflows.

在过去的一周中,我开始将Gerrit用于SQLAlchemy的所有开发,包括拉取请求,功能分支开发以及跨维护分支的错误修复。 我最初是通过参与Openstack项目而被介绍给Gerrit的,最初我对此完全感到困惑。 最终,我大致弄清楚了要进行哪些工作才能产生成果,并最终更喜欢它为项目添加代码更改。 但是,要实现将SQLAlchemy和我的其他关键项目实际转移到其中的巨大飞跃,需要付出更多的努力,从根本上学习Gerrit,并将其安装并集成到SQLAlchemy的现有工作流程中。

什么是杰里特? (What is Gerrit?)

I don’t consider myself to be any authority on Gerrit, so my description here is based on my working impression of what it does; the details may not be entirely accurate. Gerrit is a “code review” tool, intended to allow collaboration around code changes targeted at a project. At that level, what Gerrit is doing can be compared to a pull request – allows proposal of a change, shows you how it’s different from what’s there already, allows comments on the change including against specific code, and then provides workflow to allow the change to be merged into the code repository. For those familiar with pull requests, this seems exactly the same, yet while I’ve always been very unsatisifed with pull requests for a long time, I am completely satisifed with Gerrit’s model. So to understand that requires more understanding of Gerrit.

我不认为自己是Gerrit的权威,因此此处的描述是基于我对Gerrit所做工作的印象。 详细信息可能并不完全准确。 Gerrit是一个“代码审查”工具,旨在允许围绕针对项目的代码更改进行协作。 在此级别上,可以将Gerrit的操作与请求请求进行比较–允许提出更改建议,向您显示更改与现有更改有何不同,允许对更改进行注释(包括针对特定代码),然后提供工作流程以允许更改合并到代码存储库中。 对于熟悉请求请求的人来说,这似乎是完全一样的,但是尽管我一直很不满意请求请求,但我对Gerrit的模型完全满意。 因此要了解这一点,需要对Gerrit有更多的了解。

At its core, Gerrit is maintaining a set of git repositories that are basically mirrors of your projects’ actual git repositories. Then, it adds some additional, non-mirrored (e.g. only to its local copy) reference paths to these git repositories such that you can push commits to them which represent Code Reviews. These commits resemble feature branches in that they are branched off of “master” or another maintenance branch, but the way we work with them is different. The most immediate difference is that unlike a traditional feature branch, the individual “code review” is always a single containing the entire new feature all at once, whereas a feature branch may consist of any number of commits. In order to allow additional changes and refinements to the “Code Review” as it proceeds, you create a brand new changeset that encompasses the previous changeset completely, plus your new changes. Since there is no longer a linear history in Git between these refinements, Gerrit adds an additional “Change-Id” identifier to the commit message which is how Gerrit keeps track of each change in a code review. So instead of a long-lived feature branch that changes by adding new revisions to the end of it, you have a series of discrete all-at-once commits, each one containing the whole feature at once.

Gerrit的核心是维护一组git仓库,这些仓库基本上是项目的实际git仓库的镜像。 然后,它向这些git存储库添加一些其他的非镜像(例如,仅到其本地副本)引用路径,以便您可以将提交推送到代表代码评论的提交。 这些提交类似于功能分支,因为它们是从“主”分支或另一个维护分支分支出来的,但是我们使用它们的方式是不同的。 最直接的区别是,与传统功能分支不同,单独的“代码审阅”始终是一次包含全部新功能的单个代码,而功能分支可以包含任意数量的提交。 为了允许对“代码审查”进行进一步的更改和完善,您需要创建一个全新的变更集,该变更集应完全包含以前的变更集以及新的变更。 由于这些改进之间在Git中不再存在线性历史记录,因此Gerrit在提交消息中添加了一个额外的“ Change-Id”标识符,这是Gerrit在代码审查中跟踪每个更改的方式。 因此,您可以使用一系列离散的一次性提交,而不是通过在其末尾添加新修订来更改的长期功能分支,每个提交一次包含整个功能。

In ASCII art parlance, the traditional Git branching model, also used by pull requests, can be seen like this:

用ASCII术语来说,传统的Git分支模型(也被pull请求使用)可以看成这样:

rev 1 -> rev 2 -> rev 3 -> rev 4 -> rev 5          -> master
                    |
                    +-> rev 3a -> rev 3b -> rev 3c -> feature branch
rev 1 -> rev 2 -> rev 3 -> rev 4 -> rev 5          -> master
                    |
                    +-> rev 3a -> rev 3b -> rev 3c -> feature branch

Above, the feature branch is forked off from the code at some point, and as development continues, a new revision is added to the feature branch. The multiple commits of the feature branch will eventually be merged into master, either using a traditional merge, or by “rebasing” the feature branch onto the top of master.

上面,功能分支有时会从代码中分叉出来,并且随着开发的继续,新的修订版将添加到功能分支。 特征分支的多个提交最终将使用传统的合并或通过将特征分支“重新定基”到母版顶部而合并到母版中。

In Gerrit, the open-ended nature of Git is taken advantage of in an entirely different way, and the mapping of Gerrit to Git might look more like this:

在Gerrit中,以完全不同的方式利用了Git的开放性,并且Gerrit到Git的映射可能更像这样:

The above model would be difficult to work with using Git alone; Gerrit maintains the list of changesets conforming to a Change Id in its own database, and provides a full blown GUI and command line API on top so that the history of development of the “Feature” is very clear and easy to work with.

仅使用Git很难使用上述模型; Gerrit会在自己的数据库中维护符合变更ID的变更集列表,并在顶部提供完整的GUI和命令行API,以便“功能”的开发历史非常清晰且易于使用。

习惯它 (Getting Used to It)

The major caveat with this whole approach is that there’s a non-trivial conceptual hill to climb in order to use it, particularly if you’re not accustomed to “git rebase” (as I wasn’t). When we are in our code change that we’ve pushed as a code review, and we want to revise it and push it up as a new version of that code review, we need to replace the old commit with the new one, not add it on as additional history. The most fundamental thing this means is that instead of saying git commit, we have to say, git commit --amend, meaning, squash our current changes into the most recent commit. The hash of the recent commit is replaced with a brand new one, and the old commit is essentially floating, except for the fact that Gerrit will track it.

整个方法的主要警告是,要使用它,需要攀登一个不平凡的概念性山丘,特别是如果您不习惯“ git rebase”(因为我不是)。 当我们进行代码更改时,已将其推送为代码审查,并且我们希望对其进行修改并将其作为该代码审查的新版本进行推送,我们需要用新的提交替换旧的提交,而不是添加作为其他历史记录。 最根本的还是这意味着,而不是说git的承诺 ,我们不得不说,git的承诺--amend,这意味着,壁球我们当前转变为最近的提交。 最近的提交的哈希值被全新的哈希替换,并且旧的提交基本上是浮动的,但Gerrit会跟踪它。

When we amend a single commit, we get the same commit message and we basically leave it alone; the commit message represents what the change will be a whole, not the little adjustment we’re making here. The commentary about adjustments to our feature occurs in the comment stream on the review itself. With Gerrit’s approach, you no longer have commits like, “fix whitespace”, “add test”, “add documentation” – at the end of the day there will be just one commit message with, “Add Feature XYZ; XYZ does QPR …”. I consider that an advantage, because intermediary commits within a feature branch like “fix whitespace” are really just noise; I don’t miss them.

当我们修改单个提交时,我们会得到相同的提交消息,并且基本上将其保留下来。 提交消息表示更改将是一个整体,而不是我们在此所做的小调整。 有关我们功能调整的评论出现在评论本身的评论流中。 使用Gerrit的方法,您将不再拥有诸如“修复空白”,“添加测试”,“添加文档”之类的提交-最终,只有一条提交消息带有“添加功能XYZ; XYZ执行QPR…”。 我认为这是一个优势,因为中介在诸如“固定空白”之类的功能分支中提交的内容实际上只是噪音; 我不会想念他们的。

The other thing that has to be dealt with is that Gerrit won’t allow you to push up a code review that can’t be cleanly merged into the working branch it will be a part of. Again, this is a conceptually odd thing to adjust to but when you start doing it you realize what a great idea it is; when we work with tradtional feature branches and pull requests, there’s always that time when we realize we’ve fallen so far behind master, and oh now we have to merge master into our branch, and fix all the conflicts, so that we can merge back up later without making a huge mess.

必须处理的另一件事是,Gerrit不允许您推送无法完全合并到将成为其中一部分的工作分支中的代码审查。 同样,从概念上来说,这是一件很奇怪的事情,但是当您开始这样做时,您会意识到这是个好主意。 当我们使用传统功能分支并提取请求时,总有一次我们意识到我们已经远远落后于master,哦,现在我们必须将master合并到我们的分支中,并解决所有冲突,以便我们可以合并稍后备份,不会造成大麻烦。

With Gerrit’s approach, nothing ever gets pushed up that can’t be immediately merged, which means if you’re targeting a project that has a lot of activity, you’ll find yourself having to rebase your code reviews every time – but the key word here is “rebase”. Instead of merging master back into our feature branch, we are doing a straight up rebase of our single commit against the state of master; and because there’s only one commit to deal with, this is usually a very simple process – we aren’t burdened with trying to knit the changesets together in just the right way, or worrying about awkward merge artifacts cluttering up our feature branch forever; we flatten everything, and all the clutter of how we merged things together is discarded. As long as you’re comfortable with “git rebase”, it’s a predictable and consistent process. If you are using Gerrit for any amount of time, you will be very comfortable with rebasing :).

使用Gerrit的方法,没有任何事情会立即被合并,这意味着如果您要针对一个活动很多的项目,您将发现自己每次都必须重新编写代码审查的基础,但是关键这里的单词是“ rebase”。 我们没有将master合并回我们的功能分支,而是针对master状态对单个提交进行了直接的基础调整。 而且由于只有一个提交要处理,因此这通常是一个非常简单的过程–我们不必以正确的方式将变更集编织在一起,也不必担心笨拙的合并工件会永远困扰我们的功能分支; 我们将一切都弄平了,而我们将事物合并在一起的所有杂物都被丢弃了。 只要您对“ git rebase”感到满意,这是一个可预测且一致的过程。 如果您在任何时间使用Gerrit,您都会对重新定级非常满意:)。

优点 (Advantages)

The advantages to the above Gerrit model are in my opinion huge wins, including:

我认为上述Gerrit模型的优势是巨大的胜利,其中包括:

  1. You always have a feature packaged up as a single, clean commit, so that even with “git show”, you can see the change represented by the feature all at once in its entirety, with no stream of trivial “work in progress” commits cluttering it up. Because Gerrit maintains its own database of past versions and commentary, the history of how this change was developed is persisted permanently, but outside of your Git repository.
  2. You don’t clutter up your Git repository with tons of old feature branches. Git of course allows you to delete feature branches, but then you lose all the change history. With Gerrit you get to hold on to a permanent record of how a new feature or change was produced and none of it clutters up your main repository.
  3. Any number of developers can collaborate on a single change with no bumps in the process. This is something that is just not practical at all with pull requests; if a user submits a pull request to me, and I’d like to add some changes to it myself, the usual answer is that I need to pull that pull request into my own feature branch somewhere, and change it there – totally separated from where the original pull request is. Or, the developer of that pull request can opt to give my account write access to their repository; however in practice, I’ve never seen anyone do this, and it also means I’m awkwardly working on my own project via someone else’s account. This is the most continuously frustrating thing about pull requests and is a key reason I’m so much happier with Gerrit. With pull requests I often found myself having to painstakingly describe exactly how some part of code should look in order to get the submitter to do it in their branch, so that I wouldn’t have to “take it over” and chase them away forever. With a Gerrit code review I can just push up a modification to their change, and the contributor can jump right back on with no bump in continuity. When I’ve made a lot of changes to someone’s change I add “Co-Authored By: ” with my name to the commit message so that a viewer can see it was a two-person job.
  4. The “change as a single commit” model means we no longer have to deal with pull requests that consist of two dozen changesets, re-merges of master, weird merge artifacts like where you see zillions of merged commits interspersed with the ones you care about, and so on. When I go to “git merge” someone’s pull request, I usually need to go through the effort to manually squash it and add my own changelog notes, which means that the Github or Bitbucket UX has no record at all of me “merging” their pull request, and most horribly on Bitbucket I have to explicitly mark the PR as “DECLINED”, when meanwhile I just wanted to squash it. Gerrit on the other hand is built for rebasing and squashing from the ground up, and represents an accurate record of exactly what was merged, with no chance that I went off and modified someone’s pull request locally after the fact in a short-lived feature branch. Everything on a code contribution from start to final merge to master is visible exactly within the Gerrit UX, with complete accuracy and permanence.
  5. The workflow model of Gerrit is far richer than that of a pull request and is also fully customizable. Different roles, such as that of developers, contributors, and automated CI systems, can each get their own workflow flags each with different meanings. Typically, a core developer of the project needs to assign a “+2 Approved” flag, the CI systems in play need to assign “+1 Verified” and in Openstack and on my own system there’s additionally a “+1 Ready to Merge” flag; when all those are up, you press “submit” and the code review is pushed to the target branch, and its done. With Bitbucket and Gerrit pull requests, I never pushed the button, as I was always having to add changelog notes, make fixes, squash it, fixing merge conflicts, all of which I can now push straight into that same code review under one consistent interface.
  6. Integration with Jenkins is way better for Gerrit than it is with Github, and for Bitbucket I’ve not even been able to find any Jenkins integration. Once I pull a code change into Gerrit I can easily grant the contributor access to continue working on it, or disallow changes, so that I can on a per-user basis decide who gets to push new changes that automatically kick off builds and not have to worry that someone will submit a malicious pull request when I’m not looking. With the Jenkins Github pull request plugin, it was simply not an option to enable automatic CI unless I were using something like Travis, which currently I’m not.
  1. 您始终将功能打包为一个干净的提交,因此即使使用“ git show”,您也可以一次全部看到该功能所代表的更改,而无需进行任何琐碎的“正在进行的工作”提交杂乱无章。 由于Gerrit维护着自己的过去版本和评论数据库,因此有关更改开发方式的历史将永久保留,但不在您的Git存储库中。
  2. 您不会因大量的旧功能分支而使Git存储库混乱。 Git当然允许您删除要素分支,但随后您将丢失所有更改历史记录。 使用Gerrit,您可以永久记录新功能或更改是如何产生的,并且不会使您的主存储库混乱。
  3. 任何数量的开发人员都可以在单个更改上进行协作,而不会遇到任何麻烦。 对于请求请求,这根本不实际。 如果用户向我提交了一个拉取请求,而我想自己添加一些更改,通常的答案是我需要将该拉取请求拉到我自己的功能分支中的某个地方,然后在其中进行更改–与原始拉取请求所在的位置。 或者,该拉取请求的开发人员可以选择授予我的帐户对其存储库的写访问权限; 但是在实践中,我从未见过有人这样做,这也意味着我很笨拙地通过别人的帐户来从事自己的项目。 这是关于拉取请求最令人沮丧的事情,也是我对Gerrit感到非常高兴的一个关键原因。 对于拉取请求,我经常发现自己不得不精心描述代码的某些部分,以便使提交者在其分支中进行操作,这样我就不必“接管”并将其永久删除。 通过Gerrit代码审查,我可以对他们的更改进行修改,并且贡献者可以立即恢复,而不会造成连续性的增加。 当我对某人的更改进行了大量更改后,我在提交消息中添加“ Co-Authored By:”和我的名字,以便查看者可以看到这是一个两人的工作。
  4. “将更改作为一次提交”模型意味着我们不再需要处理由两个打折集组成的拉取请求,主数据库的重新合并,怪异的合并工件,例如您可以看到成千上万的合并提交与您所关心的散布, 等等。 当我去“ git merge”某人的拉取请求时,我通常需要努力手动压扁它并添加我自己的变更日志记录,这意味着Github或Bitbucket UX根本没有记录“我”将它们“合并”拉请求,最可怕的是,在Bitbucket上,我不得不明确地将PR标记为“ DECLINED”,而与此同时,我只是想压榨它。 在另一方面格里特是专为基础重建,并从根本上挤压,并代表我去了的正是被合并了,根本没有机会的准确记录,并在短暂的特性分支事后本地修改别人的拉动请求。 从开始到最终合并再到母版,代码贡献中的所有内容都可以在Gerrit UX中准确看到,并且具有完全的准确性和永久性。
  5. Gerrit的工作流程模型比拉取请求的工作流程模型丰富得多,并且还可以完全自定义。 不同的角色(例如开发人员,贡献者和自动化CI系统的角色)可以各自获取各自的工作流标志,每个标志均具有不同的含义。 通常,项目的核心开发人员需要分配“ +2批准”标志,正在运行的CI系统需要分配“ +1验证”,在Openstack中,在我自己的系统上,另外需要“ +1合并准备就绪”旗; 当所有这些都准备就绪时,您按“提交”,然后将代码审阅推送到目标分支,并完成它。 在使用Bitbucket和Gerrit拉取请求时,我从来没有按过按钮,因为我总是不得不添加变更日志注释,进行修复,压扁,解决合并冲突,所有这些我现在都可以在一个一致的界面下直接推送到同一代码审查中。
  6. 对于Gerrit而言,与Jenkins的集成要比与Github更好。对于Bitbucket,我什至无法找到任何Jenkins集成。 将代码更改放入Gerrit中后,我可以轻松地授予贡献者访问权限以继续进行处理,或禁止更改,以便我可以基于每个用户确定谁可以推动自动启动构建而无需执行的新更改担心有人在我不看时会提交恶意的拉取请求。 使用Jenkins Github拉取请求插件,除非我使用的是Travis之类的东西,否则它根本不是启用自动CI的选项,而目前我还没有。

Overall, with Gerrit I now have a single, consistent way to do all new code; all of my feature branches, all of my random .patch files sitting in my home directory, pull requests from github or bitbucket; all of it goes straight into Gerrit where I have them all under one interface and where changes, history, code review, workflow, and comments on them are permanent, without creating any branch junk in my main git repo!

总的来说,有了Gerrit,我现在有一个一致的方法来编写所有新代码。 我所有的功能分支,我所有的随机.patch文件都位于我的主目录中,从github或bitbucket提取请求; 所有这些都直接进入Gerrit,在这里我可以将它们全部放在一个界面下,并且对它们的更改,历史记录,代码审查,工作流和注释是永久的,而无需在我的主要git repo中创建任何分支垃圾!

Gerrit has a ton more features as well, including an extremely detailed permissioning model which you can map to specific paths in each git repo; a Lucene-powered search feature, a replication engine so that changes to the local git repo can be pushed anywhere else; it is itself a full blown http and ssh server for the git repositories as well as for its command-line and GUI interfaces, and generally has all-around industrial style features.

Gerrit还具有许多其他功能,包括非常详细的许可模型,您可以将其映射到每个git repo中的特定路径; 一个由Lucene支持的搜索功能,一个复制引擎,以便可以将本地git repo的更改推到其他任何地方; 它本身是用于git存储库及其命令行和GUI界面的功能强大的http和ssh服务器,通常具有全方位的工业风格功能。

Where Gerrit is falling short is that on the “self-install” side, it is definitely a little obtuse and unforgiving about things. It was not that easy to set up, and you’ll likely have to spend a lot of time reading stack traces in the logs to figure out various issues – documentation, links to packages, and other online help is a little scattered and sometimes incomplete, especially for the plugins. It’s not very user friendly on the adminstration side yet. But hosting it yourself is still the way to go, so that when you hit a view like “all”, you see just your projects and not a huge list of other people’s projects.

Gerrit不足之处在于,在“自安装”方面,这肯定有点晦涩难懂。 设置起来并不那么容易,您可能不得不花费大量时间阅读日志中的堆栈跟踪信息以找出各种问题-文档,软件包链接以及其他在线帮助有些分散,有时不完整,尤其是对于插件。 在管理方面还不是很用户友好。 但是,仍然要自己托管它,因此,当您看到“全部”之类的视图时,您只会看到自己的项目,而看不到其他人的项目的巨大清单。

实作 (Implementation)

The move to self-hosting services again is kind of a pendulum swing for me; a couple of years ago I was entirely gleeful to move my issue tracking off of Trac and onto Bitbucket, but i think the reason for that was mostly due to Trac’s inability to limit spam accounts as well as a burdensome performance model and very little upstream development. With Gerrit, I’m self-hosting again, however all authorization is pushed up to Github with OAuth, so that I’m not dealing with fake user accounts and so far not any spammers either.

再次转向自助服务对我来说是一种钟摆。 几年前,我完全乐于将问题从Trac转移到Bitbucket,但我认为其原因主要是由于Trac无法限制垃圾邮件帐户,繁重的性能模型和很少的上游开发。 使用Gerrit,我可以再次自我托管,但是所有授权都会通过OAuth推送到Github,这样我就不会处理虚假的用户帐户,到目前为止,也没有垃圾邮件发送者。

I’m using the Gerrit Oauth plugin to provide Github logins, and this plugin also includes support for Google Oauth which I haven’t turned on yet, and Bitbucket OAuth which I couldn’t get working.

我正在使用Gerrit Oauth插件提供Github登录,该插件还包括对我尚未打开的Google Oauth和我无法使用的Bitbucket OAuth的支持。

For integration with community-supplied code submissions, I am still using pull requests as the primary entrypoint into the system. Github and Bitbucket require that you leave the “pull request” button on in any case, so people are naturally going to send you code on both of them no matter what you say, so we might as well use them. The pull request allows me to have a quick preview of what we’re dealing with, and I then import it into Gerrit using a semi-manual process. To achieve this consistently, I’ve created the prtogerrit script which communicates with both the Github and Bitbucket APIs to pull in any target pull request, squash it, push it into Gerrit as a new review, and add comments to the pull request referring the contributor to our Gerrit registration page. Pull requests submitted through Bitbucket still necessarily get that big ugly “DECLINED” status, however it is now consistent for all BB pull requests and the messaging is clear that the work is continued on Gerrit.

为了与社区提供的代码提交集成,我仍将拉取请求用作系统的主要入口点。 Github和Bitbucket在任何情况下都要求您保留“拉取请求”按钮,因此无论您说什么,人们自然都会向他们发送代码给他们,因此我们也可以使用它们。 拉取请求使我可以快速预览正在处理的内容,然后使用半手动过程将其导入Gerrit。 为了始终如一地实现这一目标,我创建了prtogerrit脚本,该脚本与Github和Bitbucket API进行通信,以拉入任何目标拉取请求,将其压缩 ,将其作为新的评论推入Gerrit,并在拉取请求中添加引用我们的Gerrit注册页面的贡献者。 通过Bitbucket提交的拉取请求仍然必须处于丑陋的“拒绝”状态,但是现在对于所有BB拉取请求都是一致的,并且消息传递很清楚,有关Gerrit的工作仍在继续。

总结 (Sum Up)

Since I’ve been using Gerrit, I’ve been surprised at how much more productive I became; being able to see every code change being worked on by anyone all under one interface is very freeing, and the workflow that lets me have the entire change from implementation to tests to changelog and migration notes in one clean, mergable commit, fully run through multiple Jenkins CI jobs before it gets merged has made me totally comfortable pushing a big red “merge” button at the end, which was never the case with pull requests and other ad-hoc patchfiles and feature branches.

自从我使用Gerrit以来,我惊讶于我的生产率提高了多少。 能够看到任何人在一个界面下进行的所有代码更改都非常轻松,而工作流程使我能够通过一次干净的,可合并的提交,将从实现到测试到更改日志和迁移说明的全部更改完整地运行到多个Jenkins CI的工作在被合并之前,让我完全自如地按下了一个大的红色“合并”按钮,而对于拉取请求以及其他临时补丁文件和功能分支却从来没有这样。

翻译自: https://www.pybloggers.com/2016/04/gerrit-is-awesome/

杰里6916

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值