031、template模块
本文最后更新于 65 天前,其中的信息可能已经过时,如有错误请发送邮件到wuxianglongblog@163.com

template模块

1. 概要

  • template模块可以将Ansible主机上面的模板文件复制到远程被控主机上。
  • template模块与copy模块的区别是,copy模块会原样复制文件,而template模板在复制时会对模板文件进行渲染后再进行复制。
  • 官方文档:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/template_module.html
  • 模板由Jinja2 templating language处理,也就是模板Jinja2模板语言处理。
  • 模板模式的文档,可以参考 Template Designer Documentation
  • 以下附加变量可以用于模板中。
  • ansible_managed (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid. 包含一个字符串,可用于描述模板名称、主机、模板文件的修改时间和所有者uid。
  • template_host contains the node name of the template’s machine.模板节点名称。
  • template_uid is the numeric user id of the owner. 所有者的UID。
  • template_path is the path of the template. 模板的路径。
  • template_fullpath is the absolute path of the template. 模板的绝对路径。
  • template_destpath is the path of the template on the remote system (added in 2.8). 模板在远程系统上面的路径。
  • template_run_date is the date that the template was rendered. 模板渲染的时间。

2. 参数

参数 可选值 默认值 说明
attributes string,文件最终的属性
backup truefalse false boolean,是否创建文件备份
block_end_string "%}" string,块结束标志
block_start_string "{%" string,块开始标志
comment_end_string string,备注声明结束标志
comment_start_string string,备注声明开始标志
dest path,必须字段,模板在远程主机上渲染的路径
follow truefalse false boolean,是否跟随软链接
force truefalse true boolean,如果目标已经存在,则确定何时传输文件
group string,文件的组属性
lstrip_blocks truefalse false boolean,删除开头的空格或tab
mode any,文件的权限模式,如0644u=rw,g=r,o=r
newline_sequence "\n"、"\r"、"\r\n" "\n" string,指定用于模板文件的换行序列
output_encoding "utf-8" string,目标文件的文件编码,为了同质性,源模板文件必须始终使用 utf-8 编码。
owner string,文件的属主属性
src path必须字段,在Ansible管理主机上模板文件路径,文件必须是UTF-8编码
trim_blocks truefalse false boolean,是否删除块后面的第一个换行符
validate string,在实际执行生效前执行校验的命令
variable_end_string "}}" string,模板中变量结束标记
variable_start_string "{{" string,模板中变量开始标记

3. 官方示例

- name: Template a file to /etc/file.conf
  ansible.builtin.template:
    src: /mytemplates/foo.j2
    dest: /etc/file.conf
    owner: bin
    group: wheel
    mode: '0644'

- name: Template a file, using symbolic modes (equivalent to 0644)
  ansible.builtin.template:
    src: /mytemplates/foo.j2
    dest: /etc/file.conf
    owner: bin
    group: wheel
    mode: u=rw,g=r,o=r

- name: Copy a version of named.conf that is dependent on the OS. setype obtained by doing ls -Z /etc/named.conf on original file
  ansible.builtin.template:
    src: named.conf_{{ ansible_os_family }}.j2
    dest: /etc/named.conf
    group: named
    setype: named_conf_t
    mode: 0640

- name: Create a DOS-style text file from a template
  ansible.builtin.template:
    src: config.ini.j2
    dest: /share/windows/config.ini
    newline_sequence: '\r\n'

- name: Copy a new sudoers file into place, after passing validation with visudo
  ansible.builtin.template:
    src: /mine/sudoers
    dest: /etc/sudoers
    validate: /usr/sbin/visudo -cf %s

- name: Update sshd configuration safely, avoid locking yourself out
  ansible.builtin.template:
    src: etc/ssh/sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: '0600'
    validate: /usr/sbin/sshd -t -f %s
    backup: yes

4. 剧本的使用

我们参照官方示例进行一些测试。

4.1 附加变量的使用

剧本文件如下:

- hosts: node1
  tasks:
    - name: Template a file to /etc/file.conf
      ansible.builtin.template:
        src: /home/ansible/ansible_playbooks/mytemplates/foo.j2
        dest: /etc/file.conf
        owner: bin
        group: wheel
        mode: '0644'
      become: yes

查看foo.j2文件:

[ansible@master ansible_playbooks]$ cat mytemplates/foo.j2
# this is foo.j2 template
{{ ansible_managed }} (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid.
{{ template_host }} contains the node name of the template’s machine.
{{ template_uid }} is the numeric user id of the owner.
{{ template_path }} is the path of the template.
{{ template_fullpath }} is the absolute path of the template.
{{ template_destpath }} is the path of the template on the remote system (added in 2.8).
{{ template_run_date }} is the date that the template was rendered.

[ansible@master ansible_playbooks]$

检查并执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file to /etc/file.conf] *******************************************************************************
changed: [node1] => {"changed": true, "checksum": "cc8cc5fe90a021e969e45d5fbd557747f7c76292", "dest": "/etc/file.conf", "gid": 10, "group": "wheel", "md5sum": "906ff80444e5060b72253961d1c8f2a7", "mode": "0644", "owner": "bin", "size": 653, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677019726.88-14563-141905969605338/source", "state": "file", "uid": 1}

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

[ansible@master ansible_playbooks]$

可以看到,剧本执行完成。文件成功复制到node1节点。

此时在node1节点上验证一下:

[root@node1 ~]# cat /etc/file.conf
# this is foo.j2 template
Ansible managed (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid.
master contains the node name of the template’s machine.
ansible is the numeric user id of the owner.
/home/ansible/ansible_playbooks/mytemplates/foo.j2 is the path of the template.
/home/ansible/ansible_playbooks/mytemplates/foo.j2 is the absolute path of the template.
/etc/file.conf is the path of the template on the remote system (added in 2.8).
2023-02-22 06:48:46.905245 is the date that the template was rendered.

[root@node1 ~]# ll /etc/file.conf
-rw-r--r-- 1 bin wheel 653 Feb 22 06:48 /etc/file.conf
[root@node1 ~]#

通过查看文件内容、文件属性情况可知,在foo.j2文件中配置的附件变量都被正常的渲染,复制到节点后,文件内容已经变成渲染后的信息了。

4.2 校验模板文件名后缀文件备份

  • 模板文件不需要以.j2后缀结尾。

我们修改一下剧本文件:

- hosts: node1
  tasks:
    - name: Template a file to /etc/file.conf
      ansible.builtin.template:
        src: /home/ansible/ansible_playbooks/mytemplates/foo
        dest: /etc/file.conf
        owner: bin
        group: wheel
        mode: '0644'
        backup: true
      become: yes

然后再执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file to /etc/file.conf] *******************************************************************************
changed: [node1] => {"backup_file": "/etc/file.conf.466.2023-02-22@07:15:23~", "changed": true, "checksum": "221f3f5fc94776fa085f496ac607a762f9e096ae", "dest": "/etc/file.conf", "gid": 10, "group": "wheel", "md5sum": "9a1840f5160388fbce6d7a1c95823412", "mode": "0644", "owner": "bin", "size": 647, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677021322.92-18240-53394303783237/source", "state": "file", "uid": 1}

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

[ansible@master ansible_playbooks]$

可以看到执行成功。

再在节点上检查一下:

[root@node1 ~]# ll /etc/file.conf*
-rw-r--r-- 1 bin wheel 647 Feb 22 07:15 /etc/file.conf
-rw-r--r-- 1 bin wheel 653 Feb 22 06:48 /etc/file.conf.466.2023-02-22@07:15:23~
[root@node1 ~]# cat /etc/file.conf
# this is foo.j2 template
Ansible managed (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid.
master contains the node name of the template’s machine.
ansible is the numeric user id of the owner.
/home/ansible/ansible_playbooks/mytemplates/foo is the path of the template.
/home/ansible/ansible_playbooks/mytemplates/foo is the absolute path of the template.
/etc/file.conf is the path of the template on the remote system (added in 2.8).
2023-02-22 07:15:22.946431 is the date that the template was rendered.

[root@node1 ~]# diff /etc/file.conf /etc/file.conf.466.2023-02-22@07\:15\:23~
5,6c5,6
< /home/ansible/ansible_playbooks/mytemplates/foo is the path of the template.
< /home/ansible/ansible_playbooks/mytemplates/foo is the absolute path of the template.
---
> /home/ansible/ansible_playbooks/mytemplates/foo.j2 is the path of the template.
> /home/ansible/ansible_playbooks/mytemplates/foo.j2 is the absolute path of the template.
8c8
< 2023-02-22 07:15:22.946431 is the date that the template was rendered.
---
> 2023-02-22 06:48:46.905245 is the date that the template was rendered.
[root@node1 ~]#

可以看到,原来存在的文件已经备份了,并生成了新的文件,新的文件内容也重新被渲染了。

4.3 使用模板默认目录templates存放模板文件

上面示例中我们通过使用src: /home/ansible/ansible_playbooks/mytemplates/foo.j2src: /home/ansible/ansible_playbooks/mytemplates/foo来指定模板文件的绝对路径,如果我们将模板文件存放到剧本文件同级目录的templates目录下,Ansible也会自动查找到对应的剧本文件。

我们先复制一下文件:

[ansible@master ~]$ cd ansible_playbooks/
[ansible@master ansible_playbooks]$ mkdir templates
[ansible@master ansible_playbooks]$ cp mytemplates/foo templates/bar
[ansible@master ansible_playbooks]$ ll templates/bar
-rw-rw-r-- 1 ansible ansible 631 Feb 23 06:46 templates/bar

然后修改一下剧本文件template.yml:

- hosts: node1
  tasks:
    - name: Template a file to /etc/file.conf
      ansible.builtin.template:
        src: bar
        dest: /etc/file.conf
        owner: bin
        group: wheel
        mode: '0644'
        backup: true
      become: yes

即此处src: bar仅指定模板的名称,并没有指定其路径,尝试执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file to /etc/file.conf] *******************************************************************************
changed: [node1] => {"backup_file": "/etc/file.conf.30029.2023-02-23@06:51:46~", "changed": true, "checksum": "a94f50c2599e8d495e8da5cab4469dec619463fd", "dest": "/etc/file.conf", "gid": 10, "group": "wheel", "md5sum": "0c55c96745d17da7d76708ecea925683", "mode": "0644", "owner": "bin", "size": 643, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677106306.24-14023-230229998303305/source", "state": "file", "uid": 1}

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

[ansible@master ansible_playbooks]$

可以看到,剧本成功执行,在node1节点上检查一下:

[root@node1 ~]# ll /etc/file.conf*
-rw-r--r-- 1 bin wheel 643 Feb 23 06:51 /etc/file.conf
-rw-r--r-- 1 bin wheel 647 Feb 22 07:15 /etc/file.conf.30029.2023-02-23@06:51:46~
-rw-r--r-- 1 bin wheel 653 Feb 22 06:48 /etc/file.conf.466.2023-02-22@07:15:23~
[root@node1 ~]# cat /etc/file.conf
# this is foo.j2 template
Ansible managed (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid.
master contains the node name of the template’s machine.
ansible is the numeric user id of the owner.
/home/ansible/ansible_playbooks/templates/bar is the path of the template.
/home/ansible/ansible_playbooks/templates/bar is the absolute path of the template.
/etc/file.conf is the path of the template on the remote system (added in 2.8).
2023-02-23 06:51:46.259152 is the date that the template was rendered.

[root@node1 ~]#

可以看到,Ansible自动从template.yml剧本文件同级目录/home/ansible/ansible_playbooks/templates下查找到了模板文件bar,并成功渲染!!

4.4 使用符号模式设置文件权限

设置文件权限时,不仅可以使用mode: '0644'这样数字的形式,也可以使用mode: u=rw,g=r,o=r这样的符号模式,示例如下:

- hosts: node1
  tasks:
    - name: Template a file, using symbolic modes (equivalent to 0644)
      ansible.builtin.template:
        src: bar
        dest: /etc/bar.conf
        owner: bin
        group: wheel
        mode: u=rw,g=r,o=r
      become: yes

检查并执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file, using symbolic modes (equivalent to 0644)] ******************************************************
changed: [node1] => {"changed": true, "checksum": "77e8b25c9a6bb1890dcbe4ea5e7a801b8ce80f6a", "dest": "/etc/bar.conf", "gid": 10, "group": "wheel", "md5sum": "e018da0b98953ef66cbd88ee39ba077a", "mode": "0644", "owner": "bin", "size": 642, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677194447.48-16981-220559966580830/source", "state": "file", "uid": 1}

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

[ansible@master ansible_playbooks]$

可以看到,也成功执行。

在节点node1上面查看文件属性和内容:

[root@node1 ~]# cat /etc/bar.conf
# this is foo.j2 template
Ansible managed (configurable via the defaults section of ansible.cfg) contains a string which can be used to describe the template name, host, modification time of the template file and the owner uid.
master contains the node name of the template’s machine.
ansible is the numeric user id of the owner.
/home/ansible/ansible_playbooks/templates/bar is the path of the template.
/home/ansible/ansible_playbooks/templates/bar is the absolute path of the template.
/etc/bar.conf is the path of the template on the remote system (added in 2.8).
2023-02-24 07:20:47.499481 is the date that the template was rendered.

[root@node1 ~]#

可以看到,文件权限和内容已经配置好了。

4.5 使用for循环渲染Web页面

编写web.html文件:

<!DOCTYPE html>
<html lang="en">
<head>
    {# 下面这一行解决页面中文乱码 #}
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My Webpage</title>
</head>
<body>
    <ul id="navigation">
    {% for item in navigation %}
        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
    {% endfor %}
    </ul>

    <h1>My Webpage</h1>
    {{ author }}

    {# a comment #}
</body>
</html>

编写剧本文件:

- hosts: node1
  vars:
    author: "meizhaohui"
    navigation:
      - {"href": "https://jinja.palletsprojects.com/", "caption": "JinJa2文档" }
      - {"href": "http://www.ansible.com.cn/", "caption": "Ansible中文权威指南" }
      - {"href": "https://www.baidu.com/", "caption": "百度一下" }
  tasks:
    - name: Template a file to /usr/share/nginx/html/web.html
      ansible.builtin.template:
        src: web.html
        dest: /usr/share/nginx/html/web.html
        owner: root
        group: root
        mode: '0644'
      become: yes

检查并执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file to /usr/share/nginx/html/web.html] ***************************************************************
changed: [node1] => {"changed": true, "checksum": "50662b657416087866018d74921dc878a4ec7bab", "dest": "/usr/share/nginx/html/web.html", "gid": 0, "group": "root", "md5sum": "7b8747b936d31c37fb59e34fb91b2fca", "mode": "0644", "owner": "root", "size": 505, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677897655.16-662-131792568332109/source", "state": "file", "uid": 0}

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

[ansible@master ansible_playbooks]$

注意,在本机/etc/hosts中绑定一下服务器IP与域名:

192.168.56.111 node1 node1.com

在节点上检查生成的html文件:

[root@node1 ~]# ls -lah /usr/share/nginx/html/web.html
-rw-r--r-- 1 root root 505 Mar  4 10:40 /usr/share/nginx/html/web.html
[root@node1 ~]# cat /usr/share/nginx/html/web.html
<!DOCTYPE html>
<html lang="en">
<head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My Webpage</title>
</head>
<body>
    <ul id="navigation">
            <li><a href="https://jinja.palletsprojects.com/">JinJa2文档</a></li>
            <li><a href="http://www.ansible.com.cn/">Ansible中文权威指南</a></li>
            <li><a href="https://www.baidu.com/">百度一下</a></li>
        </ul>

    <h1>My Webpage</h1>
    meizhaohui

    </body>
</html>
[root@node1 ~]#

在浏览器中访问 http://node1.com/web.html

我们也可以这样编写剧本文件:

- hosts: node1
  vars:
    author: "meizhaohui"
    # navigation:
    #   - {"href": "https://jinja.palletsprojects.com/", "caption": "JinJa2文档" }
    #   - {"href": "http://www.ansible.com.cn/", "caption": "Ansible中文权威指南" }
    #   - {"href": "https://www.baidu.com/", "caption": "百度一下" }
    navigation:
      - href: "https://jinja.palletsprojects.com/"
        caption: "JinJa2文档"
      - href: "http://www.ansible.com.cn/"
        caption: "Ansible中文权威指南"
      - href: "https://www.baidu.com/"
        caption: "百度一下"
  tasks:
    - name: Template a file to /usr/share/nginx/html/web.html
      ansible.builtin.template:
        src: web.html
        dest: /usr/share/nginx/html/web.html
        owner: root
        group: root
        mode: '0644'
      become: yes

可以看到页面已经正常渲染了:

Snipaste_2023-03-04_10-47-53

4.6 for/if同时使用改变页面样式

我们修改一下web.html文件模板内容:

<!DOCTYPE html>
<html lang="en">
<head>
    {# 下面这一行解决页面中文乱码 #}
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>My Webpage</title>
</head>
<body>
    <ul id="navigation">
    {% for item in navigation %}
        {% if item.caption == "百度一下" %}
        <li>{{ loop.index }}: <a href="{{ item.href }}" style="color:red">{{ item.caption }}</a></li>
        {% else %}
        <li>{{ loop.index }}: <a href="{{ item.href }}" style="color:yellowgreen">{{ item.caption }}</a></li>
        {% endif %}
    {% endfor %}
    </ul>

    <h1>My Webpage</h1>
    {{ author }}

    {# a comment #}
</body>
</html>

然后再次执行剧本:

[ansible@master ansible_playbooks]$ ansible-lint template.yml
[ansible@master ansible_playbooks]$ ansible-playbook template.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Template a file to /usr/share/nginx/html/web.html] ***************************************************************
changed: [node1] => {"changed": true, "checksum": "0e642550562ca94145242e65176322d95d47012c", "dest": "/usr/share/nginx/html/web.html", "gid": 0, "group": "root", "md5sum": "dceb1340e3c598851036226b4d10e2a7", "mode": "0644", "owner": "root", "size": 632, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677928807.05-5972-261767178767114/source", "state": "file", "uid": 0}

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

[ansible@master ansible_playbooks]$

此时刷新页面,再次看看页面效果:

Snipaste_2023-03-04_19-22-39

4.7 过滤器的使用

编写模板文件filter.html:

<!DOCTYPE html>
<html lang="en">
<head>
    {# 下面这一行解决页面中文乱码 #}
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>filter</title>
</head>
<body>
    <center>
        <h1> The hostname of this webserver is {{ ansible_hostname }}</h1>
        <h3> It is running on {{ ansible_os_family }} system </h3>
     </center>
     <center>
        <h1> The hostname of this webserver is {{ ansible_hostname | upper }}</h1>
        <h3> It is running on {{ ansible_os_family | upper }} system </h3>
     </center>
     <center>
        <h1> The hostname of this webserver is {{ ansible_hostname | lower }}</h1>
        <h3> It is running on {{ ansible_os_family | lower }} system </h3>
     </center>
</body>
</html>

编写剧本文件template_filter.yml:

- hosts: node1
  tasks:
    - name: Make Strings Appear in Uppercase/lowercase
      ansible.builtin.template:
        src: filter.html
        dest: /usr/share/nginx/html/filter.html
        owner: root
        group: root
        mode: '0644'
      become: yes

检查并执行剧本:

[ansible@master ansible_playbooks]$ ls -lah templates/filter.html
-rw-rw-r-- 1 ansible ansible 766 Mar  5 10:29 templates/filter.html
[ansible@master ansible_playbooks]$ ansible-lint template_filter.yml
[ansible@master ansible_playbooks]$ ansible-playbook template_filter.yml -v
Using /etc/ansible/ansible.cfg as config file

PLAY [node1] ***********************************************************************************************************

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

TASK [Make Strings Appear in Uppercase/lowercase] **********************************************************************
changed: [node1] => {"changed": true, "checksum": "5886f7f25dfb396441fcf97dcf5e0e831a4be038", "dest": "/usr/share/nginx/html/filter.html", "gid": 0, "group": "root", "md5sum": "185d22860ba1d864bcf39243b7215b8f", "mode": "0644", "owner": "root", "size": 586, "src": "/home/ansible/.ansible/tmp/ansible-tmp-1677983374.42-32655-63839758258909/source", "state": "file", "uid": 0}

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

[ansible@master ansible_playbooks]$

此时检查页面效果:

Snipaste_2023-03-05_10-33-20
可以看到,在模板中正常使用大小写转换的过滤器!!

过滤器的使用请参考:

Snipaste_2023-03-05_10-51-11

当后面学会使用role角色功能后,template模块更加方法。

谨此笔记,记录过往。凭君阅览,如能收益,莫大奢望。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇