从头开始vue创建项目_您不需要聊天机器人创建工具-让我们从头开始构建Messenger机器人...

从头开始vue创建项目

There are many many chatbot creation tools out there. To paraphrase Dr. Seuss, some are good and some are sad and some are very, very bad. I know, I’ve reviewed a bunch of them.

有很多聊天机器人创建工具。 用苏斯博士的话来解释,有些很好,有些很难过,有些非常非常不好。 我知道,我已经审查了很多。

But what if you want to write one yourself, from scratch, without using any fancy tools? Is that even possible? And can you make something useful? The answer is yes, because I’ve done it, and I’m going to show you how.

但是,如果您想不使用任何奇特的工具从头开始编写自己的文字呢? 那有可能吗? 而且您能做点有用的事吗? 答案是肯定的,因为我已经做到了,并且我将向您展示如何做。

All the code is available here on Github. We will be creating a bot for Facebook Messenger, and we will use Google App Engine to host our bot, which will be written in Python.

所有代码都可以在这里 Github上。 我们将为Facebook Messenger创建一个机器人,并将使用Google App Engine托管我们的机器人,该机器人将以Python编写。

But wait, why would you want to do this? Using a graphical user interface to make your bot is much easier. Well, here are a few reasons:

但是等等,您为什么要这样做? 使用图形用户界面使您的机器人更容易。 好吧,这有几个原因:

  • It’s free. App Engine’s free tier is so generous it’s unlikely you will exceed it unless you have many thousands of bot users — in which case, you’ll be laughing.

    免费。 App Engine的免费套餐是如此慷慨,除非您有成千上万的机器人用户,否则您不可能超过免费套餐-在这种情况下,您会大笑。

  • To learn. See what it really takes to build a chatbot.

    学习。 了解构建聊天机器人的实际需求。
  • Go beyond what the chatbot creation tools can do. Feeling ambitious? Make something totally original, or make your own chatbot platform.

    超越了聊天机器人创建工具的功能。 感到野心勃勃? 制作完全原创的东西,或制作自己的聊天机器人平台。

选择聊天机器人频道 (Choosing a chatbot channel)

You can build a bot for many different channels. Some of the most popular are Facebook Messenger, Kik, Slack, Twitter, and Telegram. If you need to support several platforms, you’ll be better off using a bot framework. This way you won’t have to write the code to integrate with all the platforms you want to support.

您可以为许多不同的渠道构建漫游器。 一些最受欢迎的是Facebook Messenger,Kik,Slack,Twitter和Telegram。 如果需要支持多个平台,则使用bot框架会更好。 这样,您无需编写代码即可与您要支持的所有平台集成。

In this article we’re going to build a chatbot for Facebook Messenger. Why? Well, firstly, it’s the most popular platform for chatbots. Nearly all the tools for building chatbots target Messenger, and quite a few of them only support Messenger. And with good reason: it had 1.2 billion monthly active users in 2017. That’s a lot of potential chatbot users.

在本文中,我们将为Facebook Messenger构建一个聊天机器人。 为什么? 首先,它是聊天机器人最受欢迎的平台。 几乎所有用于构建聊天机器人的工具都针对Messenger,并且其中只有少数工具支持Messenger。 有充分的理由:它在2017年每月有12亿活跃用户。这是很多潜在的聊天机器人用户。

There’s another reason we want to target Messenger: quick replies. These are buttons that your chatbot can offer users as a shortcut so they don’t have to type. Not only do they make your bot much more engaging (who likes typing on a mobile phone?), but they also make your job as a chatbot developer a whole lot easier.

我们要定位Messenger的另一个原因是:快速回复。 这些是您的聊天机器人可以为用户提供的快捷方式按钮,因此用户无需键入。 它们不仅使您的机器人更具吸引力(谁喜欢在手机上打字?),而且还使您作为聊天机器人开发人员的工作变得更加轻松。

If you offer users buttons, they’ll press those buttons. This means you don’t have to worry about parsing arbitrary queries from users who want to know whether it’s going to rain tomorrow or where they can get pizza. Guiding users is good for them and us.

如果您提供用户按钮,则他们将按这些按钮。 这意味着您不必担心解析来自想知道明天是否会下雨或在哪里可以买到披萨的用户的任意查询。 引导用户对他们和我们都有好处。

机器人做什么? (What’s a bot to do?)

Your bot needs a purpose. It can’t do everything. My friend Naré Vardanyan and I designed a bot to help people navigate the turbid waters of visa applications to the UK. We’ll be using a simplified version of that bot as an example in this article.

您的机器人程序需要一个目标。 它不能做所有事情。 我和我的朋友纳雷·瓦尔丹扬 ( NaréVardanyan)设计了一个机器人,以帮助人们驾驭前往英国的签证申请的泥潭 。 本文将以该机器人的简化版本为例。

树遍历魔术 (Tree Traversal Magic)

We’ll be using a bot design method based around trees. Each node of the tree represents a possible conversation state. Each child of a node corresponds to a possible user message that we understand as being relevant from this particular state.

我们将使用围绕树木的机器人设计方法。 树的每个节点代表一个可能的对话状态。 节点的每个子节点对应于一条可能的用户消息,我们认为该消息与该特定状态相关。

say: "What is the purpose of your visit? (options: travel, study, business/work, medical treatment, join family/get married, visit child at school, diplomatic/government visit)"
answers:
  travel:
    say: You need a Standard Visitor Visa
  study:
    say: How long are you going to stay in the UK? up to 6 months; more than 6 months
    answers:
      up to 6 months:
        say: You can apply for a Short-term Study Visa
      more than 6 months:
        say: You need a Study Visa (Tier 4)
  business/work:
    say: How long are you going to stay in the UK? up to 6 months; more than 6 months
    answers:
      up to 6 months:
        say: You need a Standard Visitor Visa
      more than 6 months:
        say: Are you an 1. entrepreneur 2.investor 3. leader in arts or sciences 4. none of the above
        answers:
          '1':
            say: You can apply for a Tier 1 Entrepreneur
          '2':
            say: You can apply for Tier 1 Investor
          '3':
            say: You can apply for Tier 1 (Exceptional Talent)
          '4':
            say: Are you offered  1. a skilled job 2. role in the UK branch of your employer 3. job in a religious community 4. job as an elite sportsman or coach
            answers:
              '1':
                say: You can apply for a Tier 2 (General) visa
              '2':
                say: You can apply for a Tier 2 (Intra-company transfer)
              '3':
                say: Tier 2 (Minister of Religion)
              '4':
                say: Tier 2 (Sportsperson)
  medical treatment:
    say: You need a Standard Visitor Visa
  join family/get married:
    say: You need a Family of a settled person visa if your family/partner are settled in the UK or a 'dependant' visa of their visa category if they are working or studying
  visiting a child:
    say: You need a Parent visa if you're visiting for over 6 months and a Standard Visitor visa if your visit is  for less than 6 months
  diplomatic or government visit:
    say: You can apply for exempt vignette (exempt from immigration control)

This is a simplified version of our visa advice bot, in tree form. It’s in YAML (Yet Another Markup Language) format, which makes it easy to read. The root node specifies the first message the bot sends to the user, in this case, asking the user “What is the purpose of your visit?” The child nodes (specified under “answers”) contain the possible answers we will accept, namely “travel,” “study,” “business/work,” and so on.

这是我们的签证咨询机器人的简化版本,为树形。 它采用YAML(又一种标记语言)格式,因此易于阅读。 根节点指定漫游器发送给用户的第一条消息,在这种情况下,询问用户“您的访问目的是什么?” 子节点(在“答案”下指定)包含我们将接受的可能答案,即“旅行”,“学习”,“业务/工作”等。

入门 (Getting started)

To create our bot, we need to set up a bunch of things in Facebook. The official instructions are here, but in summary, you will need:

要创建我们的机器人,我们需要在Facebook中设置一堆东西。 官方说明在这里 ,但总而言之,您将需要:

  • A Facebook page — each bot needs a different Facebook page.

    一个Facebook页面—每个机器人都需要一个不同的Facebook页面。
  • A developer account to allow you to create apps.

    开发者帐户,允许您创建应用。
  • A Facebook app to get a secret access token which will be needed later.

    一个Facebook应用程序,用于获取秘密访问令牌,稍后需要使用该令牌。

Facebook bots work with webhooks, which are just URLs that Facebook Messenger uses to interact with your bot.

Facebook机器人与webhooks一起使用, webhooks只是Facebook Messenger与您的机器人进行交互所使用的URL。

To create our webhook, we’ll use Google App Engine. The advantage of this is that it’s free for low volumes, and automatically scales as you get more traffic. For this article, I’ve used Python, but there are lots of other languages you can use. You will need to download the Python SDK and create a Google Cloud Project if you don’t already have one.

要创建我们的webhook,我们将使用Google App Engine 。 这样做的好处是,它对于低容量是免费的,并随着您获得更多流量而自动扩展。 在本文中,我使用了Python,但是您可以使用许多其他语言。 如果您还没有Python SDK ,则需要下载它创建一个Google Cloud Project

创建我们的webhook (Creating our webhook)

The first thing our webhook needs to do is to allow Facebook to verify that we are really the correct webhook. We do that by handling a GET request which contains a “verify token.” This is a secret random string that we’ve shared with Facebook. This part of our code is based on the excellent Facebook Messenger Bot repository.

Webhook需要做的第一件事是允许Facebook验证我们确实是正确的Webhook。 我们通过处理包含“验证令牌”的GET请求来做到这一点。 这是我们与Facebook分享的秘密随机字符串。 我们代码的这一部分基于出色的Facebook Messenger Bot 存储库

class MainPage(webapp2.RequestHandler):
    def __init__(self, request=None, response=None):
        super(MainPage, self).__init__(request, response)
        logging.info("Initialising with new bot.")
        self.bot = TreeBot(send_message, UserEventsDao(), TREE)

    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        mode = self.request.get("hub.mode")
        if mode == "subscribe":
            challenge = self.request.get("hub.challenge")
            verify_token = self.request.get("hub.verify_token")
            if verify_token == VERIFY_TOKEN:
                self.response.write(challenge)
        else:
            self.response.write("Ok")

Here we first initialize a class to handle requests within the webapp2 framework. We first log a message to say that the bot is being initialized, then construct the class TreeBot that will handle all the bot logic, discussed below.

在这里,我们首先初始化一个类以处理webapp2框架内的请求。 我们首先记录一条消息,指出该机器人已被初始化,然后构造将处理所有机器人逻辑的TreeBot类,如下所述。

Next we check for “subscribe” requests from Facebook and check that the verify token sent in the request is the same as the secret one we shared with Facebook.

接下来,我们检查来自Facebook的“订阅”请求,并检查请求中发送的验证令牌是否与我们与Facebook共享的秘密令牌相同。

处理来自用户的消息 (Handling messages from users)

Next we need to interpret messages from users, which are sent by Facebook to our webhook using POST requests.

接下来,我们需要解释来自用户的消息,这些消息是使用POST请求由Facebook发送到我们的Webhook的。

def post(self):
        data = json.loads(self.request.body)
        logging.info("Got data: %r", data)

        if data["object"] == "page":

            for entry in data["entry"]:
                for messaging_event in entry["messaging"]:
                    sender_id = messaging_event["sender"]["id"]

                    if messaging_event.get("message"):
                        message = messaging_event['message']
                        if message.get('is_echo'):
                            logging.info("Ignoring echo event: " + message.get('text', ''))
                            continue
                        message_text = messaging_event['message'].get('text', '')
                        logging.info("Got a message: %s", message_text)
                        self.bot.handle(sender_id, message_text)

                    if messaging_event.get("postback"):
                        payload = messaging_event['postback']['payload']
                        self.bot.handle(sender_id, payload)
                        logging.info("Post-back")

Here we first parse the JSON data from Facebook and log it to help with debugging. We then iterate over the messaging events in the data. First we extract the sender ID, which we’ll need to send replies back to the user. There are two types of events, messages (which the user has typed) and “postback” events, which are sent when a user clicks a quick reply button.

在这里,我们首先解析来自Facebook的JSON数据,并将其记录下来以帮助调试。 然后,我们遍历数据中的消息传递事件。 首先,我们提取发件人ID,将其发送回给用户。 事件有两种类型,消息(用户已键入)和“回发”事件,它们在用户单击快速答复按钮时发送。

For the first of these, we need to ignore “echo” events. We then extract the message text and send it to our bot logic to handle. We do the same with the postback events, extracting the payload, which in our case is just the text of the button.

首先,我们需要忽略“回声”事件。 然后,我们提取消息文本并将其发送到我们的机器人逻辑进行处理。 我们对回发事件进行相同的操作,以提取有效负载,在我们的示例中,有效负载只是按钮的文本。

向用户发送消息 (Sending messages to users)

When we constructed our TreeBot class, we passed a function send_message that allows the bot logic to send return messages to the user. Here it is:

当我们构造TreeBot类时,我们传递了一个send_message函数,该函数允许bot逻辑将返回消息发送给用户。 这里是:

def send_message(recipient_id, message_text, possible_answers):
    logging.info("Sending message to %r: %s", recipient_id, message_text)
    headers = {
        "Content-Type": "application/json"
    }
    message = get_postback_buttons_message(message_text, possible_answers)
    if message is None:
        message = {"text": message_text}

    raw_data = {
        "recipient": {
            "id": recipient_id
        },
        "message": message
    }
    data = json.dumps(raw_data)
    r = urlfetch.fetch("https://graph.facebook.com/v2.6/me/messages?access_token=%s" % ACCESS_TOKEN,
                       method=urlfetch.POST, headers=headers, payload=data)
    if r.status_code != 200:
        logging.error("Error sending message: %r", r.status_code)


def get_postback_buttons_message(message_text, possible_answers):
    if possible_answers is not None and len(possible_answers) <= 3:
        buttons = []
        for answer in possible_answers:
            if len(answer) > 20:
                return None
            buttons.append({
                "type": "postback",
                "title": answer,
                "payload": answer,
            })
        return {
            "attachment": {
                "type":"template",
                "payload": {
                    "template_type": "button",
                    "text": message_text,
                    "buttons": buttons,
                }
            }
        }
    return None

The recipient ID will be the sender ID we extracted earlier. Along with that we have the message text, and some quick reply buttons for the user to press. We first make sure that the request headers specify our content as JSON, and then construct our postback buttons part of the message. We specify the recipient ID and message text and convert to JSON. We make our request to the Facebook Graph API, passing in the secret access token that Facebook gave us when we created our app.

收件人ID将是我们之前提取的发件人ID。 除此之外,我们还有消息文本和一些供用户按下的快速回复按钮。 我们首先确保请求标头将我们的内容指定为JSON,然后构造消息的回发按钮部分。 我们指定收件人ID和消息文本,然后转换为JSON。 我们向Facebook Graph API发出请求,并传递了Facebook在创建应用程序时提供给我们的秘密访问令牌。

运行机器人服务器 (Running the bot server)

The last piece of code in this file just constructs the main class and runs it:

该文件中的最后一段代码只是构造了主类并运行它:

app = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

僵尸脑 (Bot brains)

Now we come to the interesting bit: how does the bot know what to say? The brains of the bot are in the file bot.py.

现在我们来看看有趣的一点:机器人如何知道要说什么? 该机器人的大脑位于bot.py文件中。

class TreeBot(object):
    def __init__(self, send_callback, users_dao, tree):
        self.send_callback = send_callback
        self.users_dao = users_dao
        self.tree = tree

    def handle(self, user, message):
        self.users_dao.add_user_event(user, 'received', message)
        history = self.users_dao.get_user_events(user)
        tree = self.tree
        logging.debug("History items: %d", len(history))
        restarting = False
        nothing_sent = True
        response = DEFAULT
        possible_answers = DEFAULT_POSSIBLE_ANSWERS
        for direction, content in history:
            response = DEFAULT
            possible_answers = DEFAULT_POSSIBLE_ANSWERS
            if direction == 'received':
                key = get_content_match(content, tree)
                if nothing_sent:
                    response = tree['say']
                    possible_answers = tree['answers'].keys()
                elif key is not None:
                    tree = tree[key]
                    if 'say' in tree:
                        response = tree['say']
                        possible_answers = None
                        if 'answers' in tree:
                            possible_answers = tree['answers'].keys()
                    restarting = False
                elif restarting:
                    if content == 'yes':
                        tree = self.tree
                        response = tree['say']
                        possible_answers = tree['answers'].keys()
                        restarting = False
            elif direction == 'sent':
                nothing_sent = False
                if 'answers' in tree and direction == 'sent' and content == tree.get('say'):
                    tree = tree['answers']
                elif direction == 'sent' and content == DEFAULT:
                    restarting = True
            else:
                raise ValueError("Unexpected direction: " + direction)

        logging.debug("Responding: %s", response)

        self.send_callback(user, response, possible_answers)
        self.users_dao.add_user_event(user, 'sent', response)

The class is initialized with three parameters:

该类使用三个参数初始化:

  • a callback function (that was defined above) to send messages back to users

    一个回调函数(在上面定义),用于将消息发送回用户
  • a data access object for storing information about users

    数据访问对象,用于存储有关用户的信息
  • the tree that contains the logic of what should be said when. This is parsed from the YAML we showed above.

    包含何时应该说的逻辑的树。 这是从上面显示的YAML中解析出来的。

First, we record that we received the user’s message, and then retrieve all of the user’s past actions from the data access object. We then replay the user’s actions to figure out where they are currently in the tree.

首先,我们记录我们已收到用户的消息,然后从数据访问对象中检索用户过去的所有操作。 然后,我们重播用户的操作,以确定他们当前在树中的位置。

We initialize the response to a default message that will be returned when the user says something we don’t understand. In our case, this is “I’m sorry, I didn’t understand, shall we start again?” There are also some default possible answers, which are “yes” and “no”. We also keep a record of whether we think we are restarting the conversation from scratch.

我们初始化对默认消息的响应,该默认消息将在用户说出我们不理解的内容时返回。 在我们的案例中,这是“对不起,我不明白,我们可以重新开始吗?” 还有一些默认的可能答案,分别是“是”和“否”。 我们还会记录我们是否认为自己从头开始重新对话。

We then start iterating over the user’s history. For each message, we check whether it was sent by us, or whether we received it from the user. If it was received, we check for a match with the current options in the tree. This uses the following function:

然后,我们开始遍历用户的历史记录。 对于每条消息,我们都会检查该消息是由我们发送的,还是我们从用户那里收到的。 如果收到,我们将检查树中当前选项是否匹配。 这使用以下功能:

def get_content_match(content, tree):
    matches = []
    for key in sorted(tree):
        if content.lower() in key.lower():
            matches.append(key)
    if len(matches) == 1:
        return matches[0]

This checks the content of the user’s message to see if it occurs as a substring of one of the current options in the tree. If there is exactly a single match, we return that match, otherwise either the user’s response is ambiguous or there is no match at all.

这将检查用户消息的内容,以查看它是否作为树中当前选项之一的子字符串出现。 如果只有一个匹配项,则返回该匹配项,否则用户的响应不明确或根本没有匹配项。

Next we check whether we have sent anything at all to the user before. If not, we set our response to be the first response in the tree, and set the possible answers to the first set from the tree.

接下来,我们检查之前是否向用户发送过任何内容。 如果不是,我们将响应设置为树中的第一个响应,然后将可能的答案设置为树中的第一个响应。

Then we check whether we found a match for the user’s message. If we did, we update the tree to be the appropriate child branch, and extract the correct response and possible answers from the tree.

然后,我们检查是否找到与用户消息匹配的内容。 如果这样做,我们将树更新为适当的子分支,并从树中提取正确的响应和可能的答案。

We then check if we have suggested restarting and if the user has confirmed they do want to restart the conversation. In that case, we reset the tree back to its initial state and use the first response as we did before.

然后,我们检查是否建议重新启动,以及用户是否确认他们确实希望重新启动对话。 在这种情况下,我们将树重置回其初始状态,并像以前一样使用第一个响应。

For each message in the history that was sent by the bot, we update the tree accordingly. Or if we sent the default message, record that we may restart the conversation.

对于漫游器发送的历史记录中的每条消息,我们都会相应地更新树。 或者,如果我们发送了默认消息,请记录我们可以重新开始对话。

Finally, after traversing all the history, we log our response, send the message back to the user, and record the message we sent in our data access object.

最后,遍历所有历史记录后,我们记录响应,将消息发送回用户,并在数据访问对象中记录我们发送的消息。

最后的难题 (The last piece of the puzzle)

The only piece of code left to discuss is the data access object that stores all user interactions. We made the design decision to store all user actions and replay them as we did above because it allowed us to easily change the logic of the bot and still be able to deduce an appropriate state for the bot and the user. If we had chosen instead to label each node of the tree and store that label, then any change to the tree would render old conversations invalid.

剩下需要讨论的唯一代码是存储所有用户交互的数据访问对象。 我们做出了存储所有用户动作并像上面一样重播这些动作的设计决策,因为它允许我们轻松更改机器人的逻辑,并且仍然能够为机器人和用户推断出适当的状态。 如果我们选择标记树的每个节点并存储该标签,则对树的任何更改都将使旧对话无效。

So our data access object needs to be able to do two things: store a new user event, and retrieve all the events for a particular user.

因此,我们的数据访问对象需要能够做两件事:存储一个新的用户事件,并检索特定用户的所有事件。

class UserEvent(ndb.Model):
    user = ndb.StringProperty()
    direction = ndb.StringProperty()
    message = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)


class UserEventsDao(object):
    def add_user_event(self, user, direction, message):
        event = UserEvent()
        event.user = user
        event.direction = direction
        event.message = message
        logging.info("Adding event: %r", event)
        event.put()

    def get_user_events(self, user):
        events = UserEvent.query(UserEvent.user == user)
        sorted_events = sorted(events, key=lambda x: x.date)
        return [(event.direction, event.message) for event in sorted_events]

Our data access object makes use of Google Datastore, which is very easy to use from App Engine, and has a generous free usage tier. The Python API makes using Datastore very easy. First we create a model class, UserEvent which specifies the fields and their types. In our case, the user ID, direction of the message and the message itself are strings, and finally the date of the event has a date-time type.

我们的数据访问对象利用了Google数据存储区 ,它很容易从App Engine中使用,并具有大量的免费使用层。 Python API使使用数据存储区变得非常容易。 首先,我们创建一个模型类UserEvent ,它指定字段及其类型。 在我们的例子中,用户ID,消息的方向和消息本身是字符串,最后事件的日期具有日期时间类型。

To create and store a new user event, we simply construct this class, set the properties, and then call put() on the object.

要创建和存储新的用户事件,我们只需构造此类,设置属性,然后在对象上调用put()

To retrieve the events for user, we call the query() function on the class, passing in the user ID. We then sort the events by date, and return a list of direction-message pairs.

为了检索用户的事件,我们在类上调用query()函数,传入用户ID。 然后,我们按日期对事件进行排序,并返回方向消息对的列表。

部署方式 (Deployment)

That’s all the bits of our bot! Now to deploy it and connect it up to Messenger.

这就是我们机器人的全部要素! 现在部署它并将其连接到Messenger。

To deploy your app to App Engine, use the gcloud command that came with the App Engine SDK:

要将您的应用部署到App Engine,请使用App Engine SDK随附的gcloud命令:

gcloud app deploy --project [YOUR_PROJECT_ID]

Once deployed, the URL of your webhook is

部署后,您的Webhook的URL为

http://[YOUR_PROJECT_ID].appspot.com/

Update your Facebook app with this webhook URL (follow the instructions here) and you should be good to go!

使用此webhook URL更新您的Facebook应用(按照此处的说明进行操作),您应该一切顺利!

世界是您的聊天机器人牡蛎 (The world is your chatbot oyster)

You can make many kinds of chatbot using these techniques. I once had fun making a Choose Your Own Adventure style bot, but I’m sure you’ll be able to come up with much more inventive things. Oh, and if you want to try out the visa bot, you can try it here (although this version is a little more sophisticated).

您可以使用这些技术制作多种聊天机器人。 我曾经做过一个“选择自己的冒险”风格的机器人很有趣,但是我敢肯定,您将能够提出更多具有创造性的东西。 哦,如果您想试用Visa漫游器,可以在这里尝试(尽管此版本稍微复杂一些)。

And, if you don’t fancy all this hard work, you could always try one of the many chatbot creation tools.

而且,如果您不喜欢所有这些辛苦的工作,则可以随时尝试使用许多聊天机器人创建工具之一

翻译自: https://www.freecodecamp.org/news/you-dont-needs-chatbot-creation-tools-let-s-build-a-messenger-bot-from-scratch-8fcbb40f073b/

从头开始vue创建项目

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值