Google App Engine for python中文教程

很牛!用Google App Engine开发自己的python应用程序中文教程

[url]http://gae.kangye.org/gettingstarted[/url]

欢迎来到Google App Engine!创建一个App Engine应用程序很简单的,只需要几分钟的时间。而且是免费的,只要你上传你的程序,你的网站就可以和用户见面了,不需要任何付款和投入。

在Google App Engine入门指南中,你将会创建一个用户留言簿,它可以让用户在公共的留言板上发表留言。用户可以匿名留言,也可以使用他们的Google帐户留言。

这个留言簿程序将会向你展示如何使用App Engine的数据库存储,如何将一个App Engine和谷歌帐户进行整合,以及如何使用一个简单的Python网站框架(在App Engine中叫做webap)。这个小程序还将会展示如何使用Django模板引擎。

你要通过App Engine的software development kit (SDK)来开发,上传你的Google App Engine程序。

这个SDK里包括了一个网站服务器(web server)程序,用来模拟App Engine的环境,包括一个本地版的数据库,Google帐户,以及获取远程地址(URLs)和从你的本地计算机发送邮件这一些列App Engine的API。本SDK需要运行在Python 2.5上,Python 2.5的各个版本支持Windows,Mac OS X和Linux。

如果需要,可以从Python的官方网站下载[url]http://www.python.org/[/url]适合你的操作系统的Python 2.5并安装在你的电脑上。Mac OS X 10.5 Leopard用户的电脑上默认已经安装好了Python 2.5。

下载App Engine SDK,然后按照下载页面上的指示将SDK安装在你的计算机上。
[url]http://code.google.com/appengine/downloads.html[/url]

在本入门指南中,你将会用到SDK中的两个命令:

dev_appserver.py, 开发版网站服务器
appcfg.py, 用来上传你的程序到 App Engine网站
对于Windows用户:App Engine SDK的Windows安装程序会将这些命令添加到命令path里,在安装之后,你可以直接在命令行中使用这些命令。

对于Mac用户:Google App Engine Launcher会将这些命令包含在程序当中,你可以将这些命令添加到命令path通过选择"GoogleAppEngineLauncher"菜单里的“Mak Symlinks...”。或者,你可以直接用Launcher来运行开发服务器以及展示你的网页程序。

如果你使用的是Zip存档版的SDK,,你将在google_appengine目录中找到相应的python程序。

创建一个简单的Request Handler
创建一个文件夹名字叫 helloworld.。所有的这个程序的文件都放在这个目录下面。

在helloworld 文件夹里,创建一个名字叫helloworld.py,的文件,然后在里面输入:

print 'Content-Type: text/plain'
print ''
print 'Hello, world!'

这段Python脚本代码将会对每一个WEB请求响应一段HTTP header(用来描述内容),一个空行,以及一则信息Hello, world!。

创建配置文件
每个App Engine程序都有一个配置文件叫做app.yaml。这个文件用来告诉服务器哪一段URLs用哪一个handler代码来处理。

在helloworld 文件夹里,创建一个名叫app.yaml的文件,内容如下:

application: helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
script: helloworld.py

从上到下,这个配置文件讲了如下几个关于应用程序的内容:

这个应用程序的ID是helloworld。当你在最后一步中上传你的程序时,你得选择一个唯一的ID,然后代替掉这个helloworld。在调试开发阶段,这个ID可以是任何值。现在,我们把它设成helloworld。
这个程序代码的版本号是1,如果你上传了新的版本的程序,App Engine会保留以前的版本,通过管理控制台,你可以将你的App Engine程序恢复到以前的版本。
这段代码是在python 运行时环境下运行的,版本号是1.其他的runtime环境和语言可能会在将来得到支持。
每个符合正则表达式/.* (即所有URLs)都将用helloworld.py 这段代码进行处理。
The syntax of this file is YAML. For a complete list of configuration options, see the app.yaml reference.

这个文件的语法是 YAML.。关于完整的配置选项,参见 the app.yaml reference。

调试程序
有了处理脚本代码和用来映射URL的配置文件之后,这个程序就完整了。你现在可以使用App Engine SDK内置的服务器测试你的程序了。

用下面的命令来启动web服务器,将helloworld的文件夹地址添加到命令后面:

google_appengine/dev_appserver.py helloworld/
这个web服务器现在就开始运行了,监听端口为8080.使用下面的地址就可以在浏览器中调试你的程序了:

[url]http://localhost:8080/[/url]

想要获取更多关于运行开发web服务器的信息,比如如何改变监听端口,参见the Dev Web Server reference,或者在命令后面添加--help。

继续开发
你可以在服务器运行的时候继续开发你的程序,web服务器会查看你的源文件的改变,并在需要的时候重新加载。

试试看:让web服务器保持运行,然后编辑helloworld.py 里的文件内容,将 Hello, world! 改成其他的内容,然后重新访问[url]http://localhost:8080/[/url] ,你会看到页面发生了改变~

想要关闭web服务器,只要在terminal窗口激活的情况下,按Control-C(或者对于你的控制台来说正确的打断命令)。

你可以在本使用指南的下面的部分中一直保持web服务器运行,如果你需要关闭它,下次你仍可以使用上面的代码来将web服务器重新开启。

使用webapp框架|中文版Google App Engine入门指南
CGI标准是很简单的,但是要把所有的代码一一写出来还是很繁重的。WEB app框架帮你解决了这些具体问题,这样你就可以将你的精力集中在你的程序的功能上了。Google App Engine支持所有用Python写的关于CGI的网站框架(包括使用CGI adaptor的 WSGI-compliant框架),包括 Django,CherryPy, Pylons, 以及 web.py.。你只需要吧这个框架的代码复制到你的程序目录下就可以使用这个框架了。

App Engine包括了一个很简单的web应用框架,叫做webapp。这个webapp框架已经在App Engine开发环境和SDK中安装好了,所以你不需要添加任何代码到你的程序中去,就可以使用这个框架了。在下面的教程中我们将使用webapp框架。

Hello, webapp!
一个 webapp 程序包含三个部分:

一个或多个 RequestHandler类用来处理请求和产生响应。
一个 WSGIApplication 实例用来根据发送请求的URL对应到相应的类
一个主程序用来运行 WSGIApplication(使用CGI adaptor)
下面让我们来把我们的欢迎辞改写成一个 webapp 程序. 编辑 helloworld/helloworld.py 文件,替换为下面的代码:
程序代码:

from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, webapp World!')
application = webapp.WSGIApplication(
[('/',MainPage)],
debug=True)
def main():
run_wsgi_app(application)
if __name__ == "__main__":
main()


在你的浏览器中重新加载 [url]http://localhost:8080/[/url] ,你将会看到改变。 (如果你关闭了web server,那么可以重新打开,方法详见 "Hello, World!".)

webapp做了些什么呢?
这个 webapp 模块是在 google.appengine.ext 包里面的。这个模块由SDK提供,在发布版的运行环境中也会包括。

上面这段代码定义了一个request handler,MainPage,映射到根目录URL(/)。当webapp接收到一个来自URL/ 的HTTP GET请求后,它就会初始化MainPage类,然后调用这个实例的get方法。在这个方法里面,关于请求的信息可以通过self.request来获得。通常,这个方法都会设置 self.response的属性以进行响应,然后退出方法。webapp将会根据MainPage实例的生命期最后的状态发送出响应。

应用程序本身由一个 webapp.WSGIApplication 实例所代表。 参数 debug=true 将会传递给生产函数,告诉 webapp 如果在程序运行过程中遇到错误,输出堆栈调用的记录。对于产品版的程序,你可能会去掉这个参数。

函数 run_wsgi_app() 接收 WSGIApplication 实例 (或者其他 WSGI-compatible 程序对象),然后将这个程序在App Engine's CGI environment里运行。 run_wsgi_app()和 Python标准库里提供的wsgiref模块中的WSGI-to-CGI adaptor ,但提供了一些额外的功能。比如,它可以自动检测程序是否是运行在调试环境,并且可以在调试环境中输出错误。

我们将会在下面的入门指南中使用很多webapp的功能,想要了解更多关于webapp的内容,访问:the webapp reference。

使用Google帐户服务|中文版Google App Engine入门指南
Google App Engine提供了很多基于Google框架下的有用的服务,可以通过SDK中提供的类库来调用这些服务。一个很重要的服务就是用户服务,它可以让你的应用程序和Google账户用户集成,有了这个用户服务,你的用户只需要拥有Google帐号就可以登录到你的网站了。

下面我们用用户类服务来个性化我们的欢迎辞:

使用 Users类
编辑 helloworld/helloworld.py , 替换为以下代码:

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
def get(self):
user = users.get_current_user()

if user:
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, ' + user.nickname())
else:
self.redirect(users.create_login_url(self.request.uri))

application = webapp.WSGIApplication(
[('/', MainPage)],
debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()

重新加载你的网页,你的程序将会重定向到一个本地版的Google登录界面,输入你想要的用户名,那么你的应用程序将会看到这个基于你给的用户名所创建的一个虚拟的User类对象。

当你的应用程序运行在App Engine上之后,用户将会被重定向到Google账户登录页面,然后会返回到成功登陆前或者创建用户之前用户所在的页面。

Users类的 API
让我们仔细来看看这些代码:

user = users.get_current_user()

如果用户已经登录了, get_current_user() 将会返回一个 User 对象,否则,将会返回 None。

if user: self.response.headers['Content-Type'] = 'text/plain' self.response.out.write('Hello, ' + user.nickname())

如果用户已经登录了,将会根据用户的账户,输出一段含有用户昵称的欢迎辞。

else: self.redirect(users.create_login_url(self.request.uri))

如果用户没有登录,则告诉 webapp 让它将页面重定向到Google账户登录页面。 这个重定向包含了用户所在的页面URI (self.request.uri) 所以之后将会返回到成功登陆前或者创建用户之前用户所在的页面。

用webapp处理表单|中文版Google App Engine入门指南
如果你希望用户自己可以留言,那么你需要一种处理用户输入信息的办法。而webapp 让数据处理变得很简单。

用webapp处理Web表单的数据
用下面的代码替换helloworld/helloworld.py:

import cgi

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write("""
<html>
<body>
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")


class Guestbook(webapp.RequestHandler):
def post(self):
self.response.out.write('<html><body>You wrote:<pre>')
self.response.out.write(cgi.escape(self.request.get('content')))
self.response.out.write('</pre></body></html>')

application = webapp.WSGIApplication(
[('/', MainPage),
('/sign', Guestbook)],
debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()


重新加载你的程序页面,你将会看到表单,试着写点东西提交吧。

这个版本的程序有两个handler:MainPage, 映射到 URL /, 用来展示表单. Guestbook, 映射到 URL /sign, 用来展示用户提交表单的内容。

Guestbook handler 有一个 post() 方法(而不是get() 方法)。 这是因为用 MainPage 所展示的页面里用了HTTP POST方法 (method="post")来提交表单里的数据。如果你需要在一个类中同时使用这两个方法(post() get()),只需要各自分别定义在一个类下面就可以了。

post() 方法里的代码可以从self.request中获取表单数据,在将这些数据返回并展示给用户之前,它调用了cgi.escape()方法来去掉用户输入中的一些HTML代码标识符。 cgi是标准Python类库中的一个模块,详见the documentation for cgi。

使用数据库存储|中文版Google App Engine入门指南
对于一个数据量大的网站应用来说数据存储是个很有技巧的的事情。用户可能在一个特定的时间发出了一个数据请求,但是下一个时间又发出了另外一个完全不同的数据请求。所有的WEB服务都需要协调这些相互影响的请求,并且这些请求可能来自世界的各个地方。

由于有了Google App Engine,你再也不需要为这些发愁了。Google App Engine架构将为提供分布式的数据处理,解决负载平衡的问题,并且提供了API来实现所有关于数据存储的问题。

数据存储完整实例
下面是一个最新版的 helloworld/helloworld.py 代码,用来存储用户的留言。下面的所有文字都是用来解释这段代码的。

import cgi

from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
from google.appengine.ext import db

class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>')

greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

for greeting in greetings:
if greeting.author:
self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
else:
self.response.out.write('An anonymous person wrote:')
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))

# Write the submission form and the footer of the page
self.response.out.write("""
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")

class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()

if users.get_current_user():
greeting.author = users.get_current_user()

greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')

application = webapp.WSGIApplication(
[('/', MainPage),
('/sign', Guestbook)],
debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()


将上面的代码替换掉 helloworld/helloworld.py 原有的代码,然后重新加载 [url]http://localhost:8080/[/url] 试着发布一条留言,看看你的留言是否被正确地存储并且正常地显示了。

存储用户提交的留言

App Engine 包含了一个基于Python的数据存储模型. 这个模型类似于 Django's data modelling API, 但是使用了Google自己的存储环境.

对于上一章实现的留言程序,我们想要把用户提交的留言保存起来,每个留言都包含作者名称,消息内容,发布时间等等,并且按照留言的先后将其显示出来。


为了使用data modeling API,在代码顶部添加 google.appengine.ext.db 模块:

from google.appengine.ext import db

下面的这段代码定义了一个用来存储用户留言的模块:

class Greeting(db.Model):
author = db.UserProperty()
content = db.StringProperty(multiline=True)
date = db.DateTimeProperty(auto_now_add=True)

这段代码定义了 Greeting 模型的三个属性: author 是一个 User 对象, content 是一个字符串对象,anddate 是一个datetime.datetime对象。

其中一些属性包含了默认值:比如db.StringProperty 类型中 multiline=True 表明该字符串中可以包含换行符;db.DateTimeProperty类型中 auto_now_add=True 表明当Greeting对象创建的时候,将使用当前时间初始化这个属性。关于数据模型的属性的更多帮助,请查看 the Datastore reference。

既然我们已经定义了一个数据对象模型,接下来,我们创建一个Greeting对象,并且把它保存起来。编辑 Guestbook handler :

class Guestbook(webapp.RequestHandler):
def post(self):
greeting = Greeting()

if users.get_current_user():
greeting.author = users.get_current_user()

greeting.content = self.request.get('content')
greeting.put()
self.redirect('/')


这个新的 Guestbook handler 创建了一个新的 Greeting 对象,然后根据用户提交的数据设置 author 和 content 的属性值。 它并没有甚至 date 的值,所以 date 会自动设成当前时间,因为我们在模型建立的时候已经设置了。

最后一行, greeting.put() 将新创建的对象保存进数据库,如果put()进去的是从数据库中提取的对象,put() 会更新那条数据记录,而现在我们是新创建的一个对象,所以 put() 会添加一条新的记录到数据存储里。

使用 GQL 获取数据记录
App Engine datastore 使用了一套复杂的数据储存系统.但是它并不是一个标准的关系数据库,所以不能使用标准的Sql语句进行查询。作为一个替代,Google准备了一套类Sql的查询语句,称之为GQL.GQL 提供了和SQL基本类似的语法来读取数据。

下面是新版的 MainPage handler 代码,用来查询数据库中的所有留言。

class MainPage(webapp.RequestHandler):
def get(self):
self.response.out.write('<html><body>')

greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

for greeting in greetings:
if greeting.author:
self.response.out.write('<b>%s</b> wrote:' % greeting.author.nickname())
else:
self.response.out.write('An anonymous person wrote:')
self.response.out.write('<blockquote>%s</blockquote>' %
cgi.escape(greeting.content))

# Write the submission form and the footer of the page
self.response.out.write("""
<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>
</body>
</html>""")


查询语句出现在这一行:


greetings = db.GqlQuery("SELECT * FROM Greeting ORDER BY date DESC LIMIT 10")

或者,你也可以在Greeting类里面调用 gql(...) 方法,那样就不必使用 SELECT * FROM Greeting 这样的查询语句了:

greetings = Greeting.gql("ORDER BY date DESC LIMIT 10")

和SQL语句类似,关键字 (比如 SELECT) 是大小写无视的,字段名是区分大小写的。

要注意的是,GQL语句总是返回完整的对象,所以GQL查询语句不能指定要查询的字段名。也就是说,所有的GQL语句都是以SELECT * FROM model 开头的。

一个GQL查询语句可以用 WHERE指定查询条件,你可以指定一个或多个条件。 和SQL不同的是,GQL查询不能包含变量值:GQL使用参数绑定查询中所有的变量.例如 , 获取当前登录用户的留言:


if users.get_current_user():
greetings = Greeting.gql("WHERE author = :1 ORDER BY date DESC",
users.get_current_user())

你也可以使用命名参数代替之:


greetings = Greeting.gql("WHERE author = :author ORDER BY date DESC",
author=users.get_current_user())

另外, Google datastore API 还提供了另外一种获取数据的方法::

greetings = Greeting.all()
greetings.filter("author =", users.get_current_user())
greetings.order("-date")

想了解 GQL查询语法的更多内容, 请查看 the Datastore reference。

清空开发版服务器中的数据存储
为了方便你测试自己的应用,GAE开发环境使用了一个临时文件来保存本地的数据,要清空本地开发环境的数据,可以使用如下的命令行:
dev_appserver.py --clear_datastore helloworld/

使用模板|中文版Google App Engine入门指南
HTML里面嵌入在编程代码里是非常杂乱的,所以我们最好使用一个独立的文件来专门处理HTML代码,以便于将界面显示和数据获取的过程相互独立出来。有很多使用Python实现的模板系统,比如: EZT, Cheetah,ClearSilver, Quixote, Django 等等.你可以选择这里面的任意一个。


为了大家方便, webapp 模块默认包含了Django的模板系统.Django模板是Google App Engine的一部分,所以你不需要单独进行绑定就可以直接使用。


使用 Django 模板
首先在 helloworld/helloworld.py中引入template模块:

import os
from google.appengine.ext.webapp import template


重新编写 MainPage handler:

class MainPage(webapp.RequestHandler):
def get(self):
greetings_query = Greeting.all().order('-date')
greetings = greetings_query.fetch(10)

if users.get_current_user():
url = users.create_logout_url(self.request.uri)
url_linktext = 'Logout'
else:
url = users.create_login_url(self.request.uri)
url_linktext = 'Login'

template_values = {
'greetings': greetings,
'url': url,
'url_linktext': url_linktext,
}

path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, template_values))


最后, 在 helloworld 的目录下面创建一个新文件index.html, 内容如下:

<html>
<body>
{% for greeting in greetings %}
{% if greeting.author %}
<b>{{ greeting.author.nickname }}</b> wrote:
{% else %}
An anonymous person wrote:
{% endif %}
<blockquote>{{ greeting.content|escape }}</blockquote>
{% endfor %}

<form action="/sign" method="post">
<div><textarea name="content" rows="3" cols="60"></textarea></div>
<div><input type="submit" value="Sign Guestbook"></div>
</form>

<a href="{{ url }}">{{ url_linktext }}</a>

</body>
</html>

刷新浏览器,然后查看网页上的变化。

template.render(path, template_values) 接受输入一个文件路径和一个dictionary类型的数据字典,并输出一段经过处理的文本。这个模板使用了Django模板的语法.在模板文件中可以直接使用引入的值,并且可以使用这些对象的属性。在许多实际例子里,你可以直接使用GAE数据模型,并访问他们的属性。


提示: App Engine 应用程序对所有上传的文件的访问权限都是只读的,所以对文件的写操作是被禁止的;当前工作路径是应用程序的根目录,所以 index.html 的路径可以简单写成"index.html"。

想要了解更多关于Django模板引擎的内容,参见 the Django 0.96 template documentation。

使用静态文件|中文版Google App Engine入门指南
和其他的web发布环境不同,Google App Engine 不支持直接将应用目录下的文件直接输出的功能。也就是说,如果我们将模板文件取名为 index.html, 我们并不能直接通过URL /index.html来访问这个文件。但是现在有非常多的应用需要我们提供文件直接输出的功能,例如图片,CSS,JavaScript等等,这些类型的文件都需要直接输出到客户端。GAE提供了这样的功能,你不需要编写自己的处理模块来进行额外的处理。

使用静态文件
编辑helloworld/app.yaml 修改里面的代码:

application: helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /stylesheets
static_dir: stylesheets

- url: /.*
script: helloworld.py


新加的handlers 部分定义了两个URL处理模块,其中 /stylesheets开头的所有URL都定义并转向了静态文件夹stylesheets ,如果在这个文件夹中发现了请求的文件,就会直接把这个文件的内容返回给客户端;而其他请求都会由helloworld.py 脚本进行处理。

默认情况下,App Engine 按照文件名后缀处理静态文件,如 .css 结尾的文件就会使用MIME类型text/css。

GAE按照在 app.yaml定义的顺序对URL进行处理。在这个例子里 /stylesheets 将先于 /.* 对路径进行处理。

想要了解更多在 app.yaml中的选项,请查看 the app.yaml reference。


下面,我们创建 helloworld/stylesheets目录,并且在这个目录下创建一个新文件 main.css :

body {
font-family: Verdana, Helvetica, sans-serif;
background-color: #DDDDDD;
}


最后,编辑 helloworld/index.html 插入如下几行:


<head>
<link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
</head>

刷新并查看效果,新版本的程序使用了样式表。


上传你的程序|中文版Google App Engine入门指南
用户创建和管理GAE应用程序,都需要使用GAE控制台程序来进行。其中,用于上传的命令行工具叫做appcfg.py。

注意: 到目前为止,还没有办法删除已经发布的应用。这个功能会在稍后提供。在现在的预览测试阶段,每个用户可以注册3个应用程序ID,如果你不想使用你自己的应用程序ID,你可以仅仅读一下这一章,到你真正想发布自己的应用的时候再尝试。

注册应用程序
访问[url]http://appengine.google.com/[/url],使用你的Google帐号登录到App Engine管理平台。(如果你还没有Google帐号,请先申请一个)。


为了创建一个新的GAE应用,请点击按钮 "Create an Application" ,按照提示注册应用程序ID,应用程序ID的名字必须是唯一的。创建ID后,你就可以拥有一个http://application-id.appspot.com/这样的URL地址来访问你的WEB应用了.当然,如果你拥有自己的域名的话,也可以将其绑定到你自己的应用。

修改 app.yaml , 吧 application: 的值设置为你刚刚申请的应用程序ID。


上传你的Application
上传你的程序,使用下面的命令:

appcfg.py update helloworld/

按照提示,输入您自己的Google 用户名和密码。

现在你已经可以使用如下地址访问您刚刚上传的WEB应用了:


http://application-id.appspot.com

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值