ansible实施任务控制

1、 循环语句

通过利用循环,我们无需编写多个使用同一模块的任务。

1.1 循环任务

简单循环

[root@ansible ansible]# vim playbook/test.yml 
[root@ansible ansible]# cat playbook/test.yml 
---
- name: xunhuan
  gather_facts: no
  hosts: 192.168.47.129
  tasks:
    - name: 创建用户 {{ item }}
      user:
        name: "{{ item }}"
        state: present
      loop:
        - wjj2
        - wjj3
        - wjj4
        - wjj5
        - wjj6
        - wjj7
        - wjj8
        - wjj9
        - wjj10

[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [xunhuan] *****************************************************************

TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item=wjj2)
changed: [192.168.47.129] => (item=wjj3)
changed: [192.168.47.129] => (item=wjj4)
changed: [192.168.47.129] => (item=wjj5)
changed: [192.168.47.129] => (item=wjj6)
changed: [192.168.47.129] => (item=wjj7)
changed: [192.168.47.129] => (item=wjj8)
changed: [192.168.47.129] => (item=wjj9)
changed: [192.168.47.129] => (item=wjj10)

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@apache ~]# id wjj2
uid=1003(wjj2) gid=1003(wjj2)=1003(wjj2)
[root@apache ~]# id wjj3
uid=1004(wjj3) gid=1004(wjj3)=1004(wjj3)
[root@apache ~]# id wjj4
uid=1005(wjj4) gid=1005(wjj4)=1005(wjj4)
[root@apache ~]# id wjj5
uid=1006(wjj5) gid=1006(wjj5)=1006(wjj5)
[root@apache ~]# id wjj6
uid=1007(wjj6) gid=1007(wjj6)=1007(wjj6)
[root@apache ~]# id wjj7
uid=1008(wjj7) gid=1008(wjj7)=1008(wjj7)
[root@apache ~]# id wjj8
uid=1009(wjj8) gid=1009(wjj8)=1009(wjj8)
[root@apache ~]# id wjj9
uid=1010(wjj9) gid=1010(wjj9)=1010(wjj9)
[root@apache ~]# id wjj10
uid=1011(wjj10) gid=1011(wjj10)=1011(wjj10)

将变量放入列表

[root@ansible ansible]# vi playbook/vars/user.yml
[root@ansible ansible]# cat playbook/vars/user.yml
user:
  - wjj2
  - wjj3
  - wjj4
  - wjj5
  - wjj6
  - wjj7
  - wjj8
  - wjj9
  - wjj10
[root@ansible ansible]# vim playbook/test.yml 
[root@ansible ansible]# cat playbook/test.yml 
---
- name: xunhuan
  gather_facts: no
  hosts: 192.168.47.129
  vars_files:
    - vars/user.yml
  tasks:
    - name: 创建用户 {{ item }}
      user:
        name: "{{ item }}"
        state: absent
      loop: "{{ user }}"

1.2 循环散列或字典列表

[root@ansible ansible]# vim playbook/test.yml 
[root@ansible ansible]# cat playbook/test.yml 
---
- name: xunhuan
  gather_facts: no
  hosts: 192.168.47.129
  vars_files:
    - vars/user.yml
  tasks:
    - name: 创建用户 {{ item }}
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        state: present
      loop:
        - name: wang1
          uid: 2000
        - name: wang2
          uid: 2050
        - name: wang3
          uid: 2060

                    
[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [xunhuan] *****************************************************************

TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wang1', 'uid': 2000})
changed: [192.168.47.129] => (item={'name': 'wang2', 'uid': 2050})
changed: [192.168.47.129] => (item={'name': 'wang3', 'uid': 2060})

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

将变量放入列表

[root@ansible ansible]# vi playbook/vars/user.yml 
[root@ansible ansible]# cat playbook/vars/user.yml 
user:
  - name: wjj11
    uid: 3000
  - name: wjj12
    uid: 2010
  - name: wjj13
    uid: 2020
[root@ansible ansible]# vim playbook/test.yml 
[root@ansible ansible]# cat playbook/test.yml 
---
- name: xunhuan
  gather_facts: no
  hosts: 192.168.47.129
  vars_files:
    - vars/user.yml
  tasks:
    - name: 创建用户 {{ item }}
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        state: present
      loop: "{{ user }}"
[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [xunhuan] *****************************************************************

TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wjj11', 'uid': 3000})
ok: [192.168.47.129] => (item={'name': 'wjj12', 'uid': 2010})
ok: [192.168.47.129] => (item={'name': 'wjj13', 'uid': 2020})

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

1.3 老样式的循环关键字

较早样式的Ansible循环

循环关键字描述
with_items行为与简单列表的loop关键字相同,例如字符串列表或散列/字典列表。
但与loop不同的是,如果为with_items提供了列表的列表,它们将被扁平化为单级列表。
循环变量item保存每次迭代过程中使用的列表项。
with_file此关键字需要控制节点文件名列表。循环变量item在每次迭代过程中保存文件列表中相应文件的内容。
with_sequence此关键字不需要列表,而是需要参数来根据数字序列生成值列表。
循环变量item在每次迭代过程中保存生成的序列中的一个生成项的值
[root@ansible ansible]# vim playbook/test.yml 
[root@ansible ansible]# cat playbook/test.yml 
---
- name: xunhuan
  gather_facts: no
  hosts: 192.168.47.129
  vars_files:
    - vars/user.yml
  tasks:
    - name: 创建用户 {{ item }}
      user:
        name: "{{ item.name }}"
        uid: "{{ item.uid }}"
        state: absent
      with_items: "{{ user }}"     //早些版本的循环方式
[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [xunhuan] *****************************************************************

TASK [创建用户 {{ item }}] *********************************************************
changed: [192.168.47.129] => (item={'name': 'wjj11', 'uid': 3000})
changed: [192.168.47.129] => (item={'name': 'wjj12', 'uid': 2010})
changed: [192.168.47.129] => (item={'name': 'wjj13', 'uid': 2020})

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

建议使用loop关键字编写循环

1.4 将注册变量(register)与loop一起使用

[root@ansible ansible]# cat playbook/wjj.yml 
--- 
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: nini
      command: "echo hello {{ item }},hello."
      loop:
        - lisa
        - mike
        - nana
      register: result

    - debug:
        var: result
[root@ansible ansible]# ansible-playbook playbook/wjj.yml 

PLAY [192.168.47.129] **********************************************************

TASK [nini] ********************************************************************
changed: [192.168.47.129] => (item=lisa)
changed: [192.168.47.129] => (item=mike)
changed: [192.168.47.129] => (item=nana)

TASK [debug] *******************************************************************
ok: [192.168.47.129] => {
    "result": {
        "changed": true,
        "msg": "All items completed",
        "results": [

2、 条件判断(任务)

任务是YAML散列/字典,when语句只是任务中的又一个键,就如任务的名称以及它所使用的模块一样。通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面

2.1 条件判断使用场景

  • 可以在变量中定义硬限制(如min_memory)并将它与受管主机上的可用内存进行比较。
  • Ansible可以捕获并评估命令的输出,以确定某一任务在执行进一步操作前是否已经完成。例如,如果某一程序失败,则将路过批处理。
  • 可以利用Ansible事实来确定受管主机网络配置,并决定要发送的模板文件(如,网络绑定或中继)。
  • 可以评估CPU的数量,来确定如何正确调节某一Web服务器。
  • 将注册的变量与预定义的变量进行比较,以确定服务是否已更改。例如,测试服务配置文件的MD5检验以和查看服务是否已更改。

2.2 条件判断语法

when语句用于有条件地运行任务。它取要测试的条件为值。如果条件满足,则运行任务。如果条件不满足,则跳过任务。

测试的一个最简单条件是某一布尔变量是True还是False

注:由于when语句不是模块变量,它必须通过缩进到任务的最高级别,放置在模块的外面

[root@ansible ansible]# cat playbook/test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  vars:     //定义变量
    power: true   // 如果为false,则跳过任务,不执行,也不报错
  tasks:
    - name: wjj
      yum:
        name: vsftpd
        state: present
      when: power    //查看power这个变量的值是否为真,为真就条件执行,为假(空)条件不成立则跳过

[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [192.168.47.129] *****************************************************************************************************

TASK [wjj] *************************************************************************************************************
changed: [192.168.47.129]

PLAY RECAP ****************************************************************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@apache ~]# rpm -qa|grep vsftpd
vsftpd-3.0.3-31.el8.x86_64

测试my_service变量是否具有值。若有值,则将my_service的值用作要安装的软件包的名称。如果未定义my_service变量,则跳过任务且不显示错误

[root@ansible ansible]# cat playbook/test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  vars:
  tasks:
    - name: wjj
      yum:
        name: "{{ service_name }}"
        state: present
      when: service_name is defined
[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
skipping: [192.168.47.129]    //变量没有被定义,引用了不存在的变量,skipping意为跳过

PLAY RECAP *********************************************************************
192.168.47.129             : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   

ansible_distribution变量是在Gathering Facts任务期间确定的事实,用于标识托管主机的操作系统分支。变量supported_distros由playbook创建,包含该playbook支持的操作系统分发列表。如果ansible_distribution的值在supported_distros列表中,则条件通过且任务运行。

[root@ansible ansible]# ansible all -m setup|less   //获取事实 
        "ansible_distribution": "RedHat",
        "ansible_distribution_file_parsed": true,
        "ansible_distribution_file_path": "/etc/redhat-release",
        "ansible_distribution_file_search_string": "Red Hat",
        "ansible_distribution_file_variety": "RedHat",
        "ansible_distribution_major_version": "8",
        "ansible_distribution_release": "Ootpa",
        "ansible_distribution_version": "8.2",
        "ansible_dns": {
            "nameservers": [
                "8.8.8.8"

[root@ansible ansible]# cat playbook/test.yml 
---
- hosts: 192.168.47.129
  vars:    // 定义变量
    platform:    //平台
      - RedHat     //区分大小写
      - centos
  tasks:
    - name: wjj
      yum:
        name: vsftpd
        state: present
      when: ansible_facts['distribution'] in platform    //用ansible_facts里面的distribution获取的值是否在platform变量里面
[root@ansible ansible]# ansible-playbook playbook/test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.47.129]

TASK [wjj] *********************************************************************
changed: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@apache ~]# rpm -qa|grep vsftpd
vsftpd-3.0.3-31.el8.x86_64

2.3 判断多个条件

使用and运算符组合多个条件语句
所有条件必须成立,否则执行失败。

when:
  - ansible_distribution_version == "7.5"
  - ansible_kernel == "3.10.0-327.el7.x86_64"

使用or运算符组合多个条件语句
通过使用括号分组条件,可以表达更复杂的条件语句。
多个条件只需要有一个条件成立就会执行

when: >    //换行
  ( ansible_distribution == "Redhat" and
    ansible_distribution_major_version == "7" )
  or
  ( ansible_distribution == "Fedora" and
    ansible_distribution_major_version == "28" )

2.4 循环语句和条件任务

ansible_mounts事实是一组字典,各自代表一个已挂载文件系统的相关事实。循环迭代列表中每一字典,只有找到了代表两个条件都为真的已挂载文件系统的字典时,条件语句才得到满足。
循环和条件可以组合使用

- name: install mariadb-server if enough space on root
  yum:
    name: mariadb-server
    state: latest
  loop: "{{ ansible_mounts }}"
  when: item.mount == "/" and item.size_available > 300000000

组合使用条件和注册变量

---
- name: Restart HTTPD if Postfix is Running
  hosts: 172.16.103.129
  tasks:
    - name: Get Postfix server status
      command: /usr/bin/systemctl is-active postfix     # Postfix是否在运行?
      ignore_errors: yes    # 如果它不在运行并且命令失败,则不停止处理。
      register: result      # 将模块的结果信息保存在名为result的变量中
      
    - name: Restart Apache HTTPD based on Postfix status
      service:
        name: httpd
        state: restarted
      when: result.rc == 0      # 评估Postfix任务的输出。如果systemctl命令的退出代码为0,\
                                # 则Postfix激活并且此任务重启httpd服务

3、 ansible处理程序

3.1 处理程序

ansible模块设计具有幂等性。执行多遍playbook是不会有影响的,只有更改了配置才会发生改变

[root@ansible playbook]# cd files/
[root@ansible files]# scp 192.168.47.129:/etc/vsftpd/vsftpd.conf .
vsftpd.conf                100% 5098     2.2MB/s   00:00    
[root@ansible files]# ls
[root@ansible files]# vsftpd.conf    //修改配置文件
12 anonymous_enable=YES   //把no改为yes,只有把配置文件改了,handlers和notify写了,这三个都满足才会执行下面任务
13 #

[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: wjj     //安装
      yum:
        name: vsftpd
        state: present

    - name: config     //配置
      copy:
        src: files/vsftpd.conf
        dest: /etc/vsftpd/vsftpd.conf
      notify:       //通知restart vsftpd,只有通知了才会去触发,触发了才会被执行
        - restart vsftpd   //要运行的处理程序的名称

    - name: service     //启动服务
      service:
        name: vsftpd
        state: started
  handlers:      //handlers级别与tasks平级,
    - name: restart vsftpd    //被任务调用的处理程序的名称
      service:     //用于该处理程序的模块
        name: vsftpd
        state: restarted
[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
ok: [192.168.47.129]

TASK [config] ******************************************************************
changed: [192.168.47.129]

TASK [service] *****************************************************************
changed: [192.168.47.129]

RUNNING HANDLER [restart vsftpd] ***********************************************
changed: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tasks:
  - name: copy demo.example.conf configuration template
    template:
      src: /var/lib/templates/demo.exammple.conf.template
      dest: /etc/httpd/conf.d/demo.example.conf
    notify:     //并不是谁先通知谁先执行,而是看handlers写的顺序,谁在前面谁先执行
      - restart mysql
      - restart apache

handlers:
  name: restart mysql
    service:
      name: mariadb
      state: restarted
      
  name: restart apache
    service:
      name: httpd
      state: restarted

3.2 处理程序的好处

  1. 处理程序始终按照play的handlers部分指定的顺序运行。它们不按在任务中由notify语句列出的顺序运行,或按任务通知它们的顺序运行。
  2. 处理程序通常在相关play中的所有其他任务完成后运行。playbook的tasks部分中某一任务调用的处理程序,将等到tasks下的所有任务都已处理后才会运行。
  3. 处理程序名称存在于各play命名空间中。如果两个处理程序被错误地给予相同的名称,则仅会运行一个。
  4. 即使有多个任务通知处理程序,该处理程序依然仅运行一次。如果没有任务通知处理程序,它就不会运行。
  5. 如果包含notify语句的任务没有报告changed结果(例如,软件包已安装并且任务报告ok),则处理程序不会获得通知。处理程序将被跳过,直到有其他任务通知它。只有相关任务报告了changed状态,Ansible才会通知处理程序。

4、 任务失败处理方式

4.1 管理play中的任务错误

当任务失败时,Ansible将立即在该主机上中止play的其余部分并且跳过所有后续任务。

4.2 忽略任务失败

默认情况下,任务失败时play会中止。不过,可以通过忽略失败的任务来覆盖此行为。可以在任务中使用ignore_errors关键字来实现此目的。

[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: wjj
      yum:
        name: vsftpd23
        state: present
      ignore_errors: yes

    - name: config
      copy:
        src: files/vsftpd.conf
        dest: /etc/vsftpd/vsftpd.conf
      notify:
        - restart vsftpd

    - name: service
      service:
        name: vsftpd
        state: started
  handlers:
    - name: restart vsftpd
      service:
        name: vsftpd
        state: restarted
[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
fatal: [192.168.47.129]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "failures": ["No package vsftpd23 available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
...ignoring      vsftpd23是不存在的,直接执行就一定会报错,但是加上ignore_errors: yes命令,就不会报错,会直接忽略错误的那一部分执行下一部分

TASK [config] ******************************************************************
ok: [192.168.47.129]

TASK [service] *****************************************************************
ok: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   

4.3 任务失败后强制执行处理程序

配置强制执行handlers处理程序的命令:force_handlers
handlers前面的有部分错了,则不会中止任务而是会先跳过,先执行handlers部分
注: 必须先通知,不然如果前面执行失败了,来不及通知,任务就会被中止执行失败。错误必须在通知之后发生的

[root@ansible files]# vsftpd.conf    //修改配置文件
12 anonymous_enable=NO   //把yes改为no,就算写了强制执行handlers,把通知写在了错误之前,不改配置文件也会报错
13 #
[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  force_handlers: yes
  tasks:
    - name: wjj
      yum:
        name: vsftpd
        state: present
      ignore_errors: yes

    - name: config
      copy:
        src: files/vsftpd.conf
        dest: /etc/vsftpd/vsftpd.conf
      notify:
        - restart vsftpd

    - name: service
      service:
        name: vsftpd1
        state: started
  handlers:
    - name: restart vsftpd
      service:
        name: vsftpd
        state: restarted

[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
ok: [192.168.47.129]

TASK [config] ******************************************************************
changed: [192.168.47.129]

TASK [service] *****************************************************************
fatal: [192.168.47.129]: FAILED! => {"changed": false, "msg": "Could not find the requested service vsftpd1: host"}

RUNNING HANDLER [restart vsftpd] ***********************************************
changed: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=3    changed=2    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

4.4 指定任务失败条件

可以在任务中使用failed_when关键字来指定表示任务已失败的条件(达到那个条件就失败了)。这通常与命令模块搭配使用,这些模块可能成功执行了某一命令,但命令的输出可能指示了失败。

tasks:
  - name: Run user creation script
    shell: /usr/local/bin/create_users.sh    //执行的脚本
    register: command_result    //把脚本的输出做成一个变量
    failed_when: "'Password missing' in command_result.stdout"    // 如果脚本输出结果里面出现了一个Password missing就说明任务执行失败了

fail模块强制任务失败
当fail发生失败时,when条件如果成立,就会被认为失败了,失败了之后打印一句话

[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: wjj
      command: echo "hello wjj"
      register: result

    - name: nihao
      fail:
        msg: "任务执行失败"
      when: "'wjj' in result['stdout']"
[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
changed: [192.168.47.129]

TASK [nihao] *******************************************************************
fatal: [192.168.47.129]: FAILED! => {"changed": false, "msg": "任务执行失败"}

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

4.5 指定何时任务报告“changed”结果

如果是false,不管上面执行成功或者是改了东西还是没有没改,都报告ok
如果是true,把上面执行的都报告changed

//没改配置文件,直接重启
[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: wjj
      copy:
        src: files/vsftpd.conf
        dest: /etc/vsftpd/vsftpd.conf
      changed_when: true
[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
changed: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

//改了配置文件,再重启
[root@ansible playbook]# cat test.yml 
---
- hosts: 192.168.47.129
  gather_facts: no
  tasks:
    - name: wjj
      copy:
        src: files/vsftpd.conf
        dest: /etc/vsftpd/vsftpd.conf
      changed_when: false    //不报告changed状态,就用false
[root@ansible playbook]# ansible-playbook test.yml 

PLAY [192.168.47.129] **********************************************************

TASK [wjj] *********************************************************************
ok: [192.168.47.129]

PLAY RECAP *********************************************************************
192.168.47.129             : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

4.6 块和错误处理

block块中的任务执行失败了,则会执行rescue块中的任务;block块中的任务执行成功了,则不会执行rescue块中的任务。但always块中的任务始终执行,不管前面哪个模块成功错误,都不会妨碍到always块。

  1. **block:**定义要运行的主要任务
  2. **rescue:**定义要在block子句中定义的任务失败时运行的任务
  3. **always:**定义始终都独立运行的任务,不论block和rescue子句中定义的任务是成功还是失败
tasks:
  - name: Upgrade DB
    block:
      - name: upgrade the database
        shell:
          cmd: /usr/local/lib/upgrade-database
    rescue:
      - name: revert the database upgrade
        shell:
          cmd: /usr/local/lib/revert-database
    always:
      - name: always restart the database
        service:
          name: mariadb
          state: restarted
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值