Ansible入门---ad hoc命令(ansible automation)
toqiye 2024-10-01 22:55 2 浏览 0 评论
下面的例子中,我们将演示如何使用 /usr/bin/ansible 运行 ad hoc 任务.
1.ad-hoc 命令
ad-hoc 命令其实是一个概念性的名字,是相对于写 Ansible playbook 来说的.类似于在命令行敲入shell命令和 写shell scripts两者之间的关系
如果我们敲入一些命令去比较快的完成一些事情,而不需要将这些执行的命令特别保存下来, 这样的命令就叫做 ad-hoc 命令.
Ansible提供两种方式去完成任务,一是 ad-hoc 命令,一是写 Ansible playbook.前者可以解决一些简单的任务, 后者解决较复杂的任务.在学习了 playbooks 之后,你才能体会到 Ansible 真正的强大之处在哪里.
2. 简单例子【执行命令】
(1)密钥管理
先设置 SSH-agent,将私钥纳入其管理:
[root@centos1 ansible]# ssh-agent bash [root@centos1 ansible]# ssh-add ~/.ssh/id_rsa Identity added: /root/.ssh/id_rsa (/root/.ssh/id_rsa)
【注释】 ssh-agent是一个密钥管理器,运行ssh-agent以后,使用ssh-add将私钥交给ssh-agent保管。
如果本机A可以通过秘钥免密码登录B ,C 两个服务器,但B,C无法免密码登录,那么如果在A机器,执行这两条命令,把A机器秘钥交给ssh-agent 管理,那么从A登录B 用
ssh -A root@B,在B机器的/tmp 目录下就会看到/tmp/ssh-xxxxxxxxx/agent.xxxxx 类似这种形式的文件生成,然后你就可以在B机器免密码登录C机器了。
如果你登录C机器的时候也带了 -A 这个参数,那么你还可以从C 机器免密码登录另一台可以从A机器免密码直接登录的机器。
(2)执行命令
确保主机文件中有要控制的机器:
[root@centos1 ansible]# cat /etc/ansible/hosts [webservers] 192.168.87.137 192.168.87.128
现在执行如下命令,这个命令中,webservers是一个组,这个组里面有很多服务器,”/bin/touch”命令会在webservers组下 的所有机器上执行.这里ssh-agent会fork出10个子进程(bash),以并行的方式执行touch命令. 即是以这种方式实现:
[root@centos1 ansible]# ansible webservers -a "/bin/touch /tmp/test.txt" -f 2 192.168.87.137 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.87.137 port 22: No route to host", "unreachable": true } [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.87.128 | CHANGED | rc=0 >>
可以再次执行一遍:
[root@centos1 ansible]# ansible webservers -a "/bin/touch /tmp/test.txt" -f 2 [WARNING]: Consider using the file module with state=touch rather than running 'touch'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message. 192.168.87.128 | CHANGED | rc=0 >> 192.168.87.137 | CHANGED | rc=0 >>
(3)指定用户
在执行 /usr/bin/ansible 时,默认是以当前用户的身份去执行这个命令.如果想以指定的用户执行ansible, 请添加 “-u username”选项,如下:
ansible webservers -a "/usr/bin/touch /tmp/test.txt" -f 2 -u mqm
在前面写出的命令中, -f 10 选项表示使用10个并行的进程.这个选项也可以在 Ansible的配置文件 中设置, 在配置文件中指定的话,就不用在命令行中写出了.这个选项的默认值是 5,是比较小的.如果同时操作的主机数比较多的话, 可以调整到一个更大的值,只要不超出你系统的承受范围就没问题.如果主机数大于设置的并发进程数,Ansible会自行协调, 花得时间会更长一点.
(4)模块
ansible有许多模块,默认是 ‘command’,也就是命令模块,我们可以通过 -m 选项来指定不同的模块。
command 模块不支持 shell 变量,也不支持管道等 shell 相关的东西.如果你想使用 shell相关的这些东西, 请使用’shell’ 模块.
ansible raleigh -m shell -a 'echo $TERM'
使用 Ansible ad hoc 命令行接口时(与使用 Playbooks 的情况相反),尤其注意 shell 引号的规则. 比如在上面的例子中,如果使用双引号”echo $TERM”,会求出TERM变量在当前系统的值,而我们实际希望的是把这个命令传递 到其它机器执行.
3. copy模块
文件传输/拷贝【执行命令】
Ansible 能够以并行的方式同时 SCP 大量的文件到多台机器. 命令如下:
[root@centos1 ~]# ansible webservers -m copy -a "src=/etc/hosts dest=/tmp/hosts.copyed" 192.168.87.137 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.87.137 port 22: No route to host", "unreachable": true } 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "c130cddb98e004d6099a1d4610aa5b179f944752", "dest": "/tmp/hosts.copyed", "gid": 0, "group": "root", "md5sum": "333b0507250f501e03bf42bb63f63ea6", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 226, "src": "/root/.ansible/tmp/ansible-tmp-1558667934.28-254713374961885/source", "state": "file", "uid": 0 } [root@centos1 ~]# ansible webservers -m copy -a "src=/etc/hosts dest=/tmp/hosts.copyed" 192.168.87.137 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "checksum": "c130cddb98e004d6099a1d4610aa5b179f944752", "dest": "/tmp/hosts.copyed", "gid": 0, "group": "root", "md5sum": "333b0507250f501e03bf42bb63f63ea6", "mode": "0644", "owner": "root", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 226, "src": "/root/.ansible/tmp/ansible-tmp-1558668170.29-139809620689423/source", "state": "file", "uid": 0 } 192.168.87.128 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "checksum": "c130cddb98e004d6099a1d4610aa5b179f944752", "dest": "/tmp/hosts.copyed", "gid": 0, "group": "root", "mode": "0644", "owner": "root", "path": "/tmp/hosts.copyed", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 226, "state": "file", "uid": 0 }
4. file模块
(1)修改文件的属主和权限
使用 file 模块可以做到修改文件的属主和权限
[root@centos1 ~]# ansible webservers -m file -a "dest=/tmp/hosts.copyed mode=600" 192.168.87.137 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "gid": 0, "group": "root", "mode": "0600", "owner": "root", "path": "/tmp/hosts.copyed", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 226, "state": "file", "uid": 0 } 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 0, "group": "root", "mode": "0600", "owner": "root", "path": "/tmp/hosts.copyed", "secontext": "unconfined_u:object_r:admin_home_t:s0", "size": 226, "state": "file", "uid": 0 }
(2)创建目录
[root@centos1 ~]# ansible webservers -m file -a "dest=/path/to/create mode=755 owner=mqm group=mqm state=directory" 192.168.87.137 | FAILED! => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "gid": 0, "group": "root", "mode": "0755", "msg": "chown failed: failed to look up user mqm", "owner": "root", "path": "/path", "secontext": "unconfined_u:object_r:default_t:s0", "size": 6, "state": "directory", "uid": 0 } 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "gid": 501, "group": "mqm", "mode": "0755", "owner": "mqm", "path": "/path/to/create", "secontext": "unconfined_u:object_r:default_t:s0", "size": 4096, "state": "directory", "uid": 496 }
(3)删除目录
[root@centos1 ~]# ansible webservers -m file -a "dest=/path/to/create state=absent" 192.168.87.137 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "path": "/path/to/create", "state": "absent" } 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "path": "/path/to/create", "state": "absent" }
【注释】上面创建目录的时候137是创建失败的,所以此处的SUCCESS只是命令执行成功了,但没有任何改变(CHANGED)。
5. yum模块
管理软件包
Ansible 提供对 yum 和 apt 的支持.这里是关于 yum 的示例.
(1)安装软件包
[root@centos1 ~]# ansible webservers -m yum -a "name=gcc state=present" 192.168.87.137 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "msg": "", "rc": 0, "results": [ "gcc-4.8.5-36.el7.x86_64 providing gcc is already installed" ] } 192.168.87.128 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "msg": "", "rc": 0, "results": [ "gcc-4.4.7-4.el6.x86_64 providing gcc is already installed" ] }
(2)卸载软件包
ansible webservers -m yum -a "name=gcc state=absent"
6. user模块
(1)创建账户
[root@centos1 ~]# ansible webservers -m user -a "name=newuser password=newuser" 192.168.87.137 | UNREACHABLE! => { "changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.87.137 port 22: Connection timed out", "unreachable": true } [WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly. 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "append": false, "changed": true, "comment": "", "group": 503, "home": "/home/********", "move_home": false, "name": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "password": "NOT_LOGGING_PASSWORD", "shell": "/bin/bash", "state": "present", "uid": 502 } [root@centos1 ~]# ansible webservers -m user -a "name=newuser password=newuser" [WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly. 192.168.87.137 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "comment": "", "create_home": true, "group": 1001, "home": "/home/********", "name": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "password": "NOT_LOGGING_PASSWORD", "shell": "/bin/bash", "state": "present", "system": false, "uid": 1001 } 192.168.87.128 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "append": false, "changed": false, "comment": "", "group": 503, "home": "/home/********", "move_home": false, "name": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", "password": "NOT_LOGGING_PASSWORD", "shell": "/bin/bash", "state": "present", "uid": 502 } [root@centos1 ~]#
(2)删除用户:
[root@centos1 ~]# ansible webservers -m user -a "name=newuser state=absent" 192.168.87.137 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "force": false, "name": "newuser", "remove": false, "state": "absent" } 192.168.87.128 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": true, "force": false, "name": "newuser", "remove": false, "state": "absent" }
7.service模块
(1)确保服务状态启动
[root@centos1 ~]# ansible webservers -m service -a "name=sshd state=started" 192.168.87.128 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "name": "sshd", "state": "started" } 192.168.87.137 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "name": "sshd", "state": "started", "status": { "ActiveEnterTimestamp": "Fri 2019-05-24 11:19:50 CST", "ActiveEnterTimestampMonotonic": "47607327", "ActiveExitTimestampMonotonic": "0", "ActiveState": "active", "After": "systemd-journald.socket basic.target network.target sshd-keygen.service system.slice", "AllowIsolate": "no", "AmbientCapabilities": "0", "AssertResult": "yes", "AssertTimestamp": "Fri 2019-05-24 11:19:48 CST", "AssertTimestampMonotonic": "46256667", "Before": "shutdown.target multi-user.target", "BlockIOAccounting": "no", "BlockIOWeight": "18446744073709551615", "CPUAccounting": "no", "CPUQuotaPerSecUSec": "infinity", "CPUSchedulingPolicy": "0", "CPUSchedulingPriority": "0", "CPUSchedulingResetOnFork": "no", "CPUShares": "18446744073709551615", "CanIsolate": "no", "CanReload": "yes", "CanStart": "yes", "CanStop": "yes", "CapabilityBoundingSet": "18446744073709551615", "ConditionResult": "yes", "ConditionTimestamp": "Fri 2019-05-24 11:19:48 CST", "ConditionTimestampMonotonic": "46256667", "ConflictedBy": "sshd.socket", "Conflicts": "shutdown.target", "ConsistsOf": "sshd-keygen.service", "ControlGroup": "/system.slice/sshd.service", "ControlPID": "0", "DefaultDependencies": "yes", "Delegate": "no", "Description": "OpenSSH server daemon", "DevicePolicy": "auto", "Documentation": "man:sshd(8) man:sshd_config(5)", "EnvironmentFile": "/etc/sysconfig/sshd (ignore_errors=no)", "ExecMainCode": "0", "ExecMainExitTimestampMonotonic": "0", "ExecMainPID": "6903", "ExecMainStartTimestamp": "Fri 2019-05-24 11:19:48 CST", "ExecMainStartTimestampMonotonic": "46286542", "ExecMainStatus": "0", "ExecReload": "{ path=/bin/kill ; argv[]=/bin/kill -HUP $MAINPID ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }", "ExecStart": "{ path=/usr/sbin/sshd ; argv[]=/usr/sbin/sshd -D $OPTIONS ; ignore_errors=no ; start_time=[Fri 2019-05-24 11:19:48 CST] ; stop_time=[n/a] ; pid=6903 ; code=(null) ; status=0/0 }", "FailureAction": "none", "FileDescriptorStoreMax": "0", "FragmentPath": "/usr/lib/systemd/system/sshd.service", "GuessMainPID": "yes", "IOScheduling": "0", "Id": "sshd.service", "IgnoreOnIsolate": "no", "IgnoreOnSnapshot": "no", "IgnoreSIGPIPE": "yes", "InactiveEnterTimestampMonotonic": "0", "InactiveExitTimestamp": "Fri 2019-05-24 11:19:48 CST", "InactiveExitTimestampMonotonic": "46286621", "JobTimeoutAction": "none", "JobTimeoutUSec": "0", "KillMode": "process", "KillSignal": "15", "LimitAS": "18446744073709551615", "LimitCORE": "18446744073709551615", "LimitCPU": "18446744073709551615", "LimitDATA": "18446744073709551615", "LimitFSIZE": "18446744073709551615", "LimitLOCKS": "18446744073709551615", "LimitMEMLOCK": "65536", "LimitMSGQUEUE": "819200", "LimitNICE": "0", "LimitNOFILE": "4096", "LimitNPROC": "7155", "LimitRSS": "18446744073709551615", "LimitRTPRIO": "0", "LimitRTTIME": "18446744073709551615", "LimitSIGPENDING": "7155", "LimitSTACK": "18446744073709551615", "LoadState": "loaded", "MainPID": "6903", "MemoryAccounting": "no", "MemoryCurrent": "18446744073709551615", "MemoryLimit": "18446744073709551615", "MountFlags": "0", "Names": "sshd.service", "NeedDaemonReload": "no", "Nice": "0", "NoNewPrivileges": "no", "NonBlocking": "no", "NotifyAccess": "main", "OOMScoreAdjust": "0", "OnFailureJobMode": "replace", "PermissionsStartOnly": "no", "PrivateDevices": "no", "PrivateNetwork": "no", "PrivateTmp": "no", "ProtectHome": "no", "ProtectSystem": "no", "RefuseManualStart": "no", "RefuseManualStop": "no", "RemainAfterExit": "no", "Requires": "basic.target", "Restart": "on-failure", "RestartUSec": "42s", "Result": "success", "RootDirectoryStartOnly": "no", "RuntimeDirectoryMode": "0755", "SameProcessGroup": "no", "SecureBits": "0", "SendSIGHUP": "no", "SendSIGKILL": "yes", "Slice": "system.slice", "StandardError": "inherit", "StandardInput": "null", "StandardOutput": "journal", "StartLimitAction": "none", "StartLimitBurst": "5", "StartLimitInterval": "10000000", "StartupBlockIOWeight": "18446744073709551615", "StartupCPUShares": "18446744073709551615", "StatusErrno": "0", "StopWhenUnneeded": "no", "SubState": "running", "SyslogLevelPrefix": "yes", "SyslogPriority": "30", "SystemCallErrorNumber": "0", "TTYReset": "no", "TTYVHangup": "no", "TTYVTDisallocate": "no", "TasksAccounting": "no", "TasksCurrent": "18446744073709551615", "TasksMax": "18446744073709551615", "TimeoutStartUSec": "1min 30s", "TimeoutStopUSec": "1min 30s", "TimerSlackNSec": "50000", "Transient": "no", "Type": "notify", "UMask": "0022", "UnitFilePreset": "enabled", "UnitFileState": "enabled", "WantedBy": "multi-user.target", "Wants": "sshd-keygen.service system.slice", "WatchdogTimestamp": "Fri 2019-05-24 11:19:50 CST", "WatchdogTimestampMonotonic": "47607219", "WatchdogUSec": "0" } }
(2) 确保服务状态停止
ansible webservers -m service -a "name=sshd state=stopped"
相关推荐
- 完美解决MAC电脑空间不足问题(完美解决mac电脑空间不足问题的办法)
-
很多用MAC(苹果笔记本)电脑的人,特别是做iOS开发的,都会遇到一个头疼的问题,那就是电脑磁盘空间不足的问题。这个问题也困扰了我好久,我的开发机是256G的SSD(固态硬盘),但是用着用着就会空间不...
- 系统清理软件Omni Remover for Mac版
-
内容介绍你是否需要一款可以帮你清理Mac系统顽固垃圾的工具呢?试试OmniRemoverforMac吧!OmniRemoverMac版是一款运行在Mac平台上的系统清理软件。OmniRem...
- mac上一款好用的多功能系统清理软件Omni Remover for Mac
-
mac上一款好用的多功能系统清理软件——OmniRemoverforMac。OmniRemovermac破解版是Mac平台上的一款软件清理工具。OmniRemoverMac版专为优化内存...
- 清理重建mac OS图标缓存(mac系统清空)
-
关于macos缓存问题你了解多少?今天macdown小编带大家了解下有关Mac清除图标缓存的相关知识!你知道吗?为了提升图形界面加载速度,默认情况下macOS针对Finder和Dock中的...
- iOS 9 人机界面指南(五):图标与图形设计
-
来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。文章索引5.1图标与图像尺寸(IconandImageSizes)5.2应用图标(AppIcon)5.2....
- 你中招了吗?盘古团队发布XcodeGhost病毒检测应用
-
最近大批知名iOS应用被感染XcodeGhost病毒事件闹得沸沸扬扬,虽然该病毒作者发表声明称,XcodeGhost源于自己的实验,没有任何威胁性行为,同时公开了源代码。但依然无法消除众多用户的担忧,...
- iOS应用感染Xcode真是无恶意实验?感染APP最新名单及版本号
-
前瞻科技快讯9月19日消息,一向号称是最安全的iOS真的不安全了?对于这两天闹得沸沸扬扬的多款iOS应用感染XcodeGhost病毒事件,今日凌晨4点左右,网友@XcodeGhost-Author在微...
- 苹果应用签名失败怎么处理(ios应用签名什么意思)
-
在移动应用开发过程中,苹果应用签名失败是一种常见的问题,它可能由多种原因引起。本文将介绍一些处理苹果应用签名失败的方法,帮助开发者解决这个问题。检查证书和描述文件:首先,开发者应该检查使用的证书和描述...
- 好用的系统扫描和清理工具推荐:OS Cleaner Pro for Mac
-
为大家推荐一款全面的系统扫描和清理工具,OSCleanerProforMac...
- 系统清理软件 Omni Remover for Mac
-
你是否需要一款可以帮你清理Mac系统顽固垃圾的工具呢?试试OmniRemoverforMac吧!OmniRemoverMac版是一款运行在Mac平台上的系统清理软件。OmniRemover...
- 优秀的Mac系统清理软件(mac清理系统占用空间)
-
OmniRemoverforMac是一款优秀的系统清理软件,功能有清洁卸载膨胀且顽固的应用程序,在macOSCatalina上清除32位不兼容的应用程序,iTunes,Xcode和Sketc...
- 苹果app安卓apk应用内用微信登录游戏时会显示登录失败怎么解决?
-
解决苹果iOS应用和安卓APK应用在使用微信授权登录时出现“登录失败,签名不一致”的问题,可以按照以下步骤进行排查和解决:1.核实AppID和AppSecret:确保iOS和安卓项目中使用的微信开放...
- Cleaner for Xcode(遗留废弃文件清理工具)
-
Mac上的Xcode总是占用很大空间,并且用的时间越久越大!可通过删除不需要的和不建议使用的文件来帮助您加快Xcode的运行速度,你可以每月或者每周运行一次进行清理。有需要的朋友,赶快来下载吧~Cle...
- Cleaner for Xcode mac(xcode清理工具)
-
Xcode文件太多,如何检测清理?试试CleanerforⅩcode吧!CleanerforXcodeforMac可以检测您的Xcode占用磁盘的情况,统计各个部件所占用的空间。并帮助您...
- 柠檬清理一款Mac设备必备的实用工具
-
简介柠檬清理是针对macOS系统专属制定的清理工具。主要功能包括重复文件和相似照片的识别、软件的定制化垃圾扫描、可视化的全盘空间分析、内存释放、浏览器隐私清理以及设备实时状态的监控等。重点聚焦清理功能...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 完美解决MAC电脑空间不足问题(完美解决mac电脑空间不足问题的办法)
- 系统清理软件Omni Remover for Mac版
- mac上一款好用的多功能系统清理软件Omni Remover for Mac
- 清理重建mac OS图标缓存(mac系统清空)
- iOS 9 人机界面指南(五):图标与图形设计
- 你中招了吗?盘古团队发布XcodeGhost病毒检测应用
- iOS应用感染Xcode真是无恶意实验?感染APP最新名单及版本号
- 苹果应用签名失败怎么处理(ios应用签名什么意思)
- 好用的系统扫描和清理工具推荐:OS Cleaner Pro for Mac
- 系统清理软件 Omni Remover for Mac
- 标签列表
-
- systemproperties (65)
- show-overflow-tooltip (53)
- canvas图片 (57)
- npm版本管理 (61)
- localhost:15672 (59)
- materialtheme (86)
- node-ssh (68)
- 全局路由守卫 (57)
- springbootcachemanager (57)
- 图床搭建 (62)
- vue3addeventlistener (60)
- mybatisselectone (78)
- css圆形进度条 (69)
- androidble蓝牙开发 (62)
- vue-router的实现原理 (53)
- usememo (53)
- log4j.xml (54)
- maven跳过测试打包 (57)
- npmjsencrypt (55)
- android-gif-drawable (60)
- appender-ref (64)
- springbootmockito (68)
- css边框渐变色和圆角 (58)
- gsonfastjson (59)
- 依赖注入的方式 (62)