标签:lookup queue 主机批量管理 div exe emc 功能 拒绝 string
最近在使用salt-api做主机批量管理部署,整理一下文档。之前使用saltstack 多用于命令行管理,自己做web版的自动化管理平台时,发现命令行的些许局限性,接触到salt-api,找到了替代方式。本文使用的saltstack 版本是2017.7.2最新版本,这个版本中官方加入了python3的支持,之前在使用salt时都是用的Python2版本,现在使用的架构是python3版本的,所在在此探讨下python3使用saltstack以及salt-api的一些方式方法。
CentOS7 + python2/python3 + saltstack2017.7.2
# 更新yum yum update # 安装epol源, 安装的版本是2017版本的salt,添加了对于python3的支持 yum install https://repo.saltstack.com/yum/redhat/salt-repo-latest-2.el7.noarch.rpm2.el7.noarch.rpm # 安装必要软件(mariadb是mysql,用于存储salt命令执行结果和jobid,可不安装) yum -y install mariadb mariadb-devel mariadb-server wget python-devel gcc c++ make openssl openssl-devel passwd libffi libffi-devel nginx # 安装 saltstack yum -y install salt-master salt-minion salt-api
# 上一步已经安装了,写下单独安装的命令 # yum install salt-api -y # 创建证书 [root@centos7 ~]# cd /etc/pki/tls/certs/ # 生成自签名证书,用于ssl [root@centos7 certs]# make testcert umask 77 ; /usr/bin/openssl genrsa -aes128 2048 > /etc/pki/tls/private/localhost.key Generating RSA private key, 2048 bit long modulus ...................................................................+++ ..+++ e is 65537 (0x10001) Enter pass phrase: # 输入加密密语,4到8191个字符 Verifying - Enter pass phrase: # 确认加密密语 umask 77 ; /usr/bin/openssl req -utf8 -new -key /etc/pki/tls/private/localhost.key -x509 -days 365 -out /etc/pki/tls/certs/localhost.crt -set_serial 0 Enter pass phrase for /etc/pki/tls/private/localhost.key: # 再次输入密语 You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter ‘.‘, the field will be left blank. ----- Country Name (2 letter code) [XX]:CN # 选填,可不填写直接回车 State or Province Name (full name) []:Shanghai # 选填,可不填写直接回车 Locality Name (eg, city) [Default City]:Shanghai # 选填,可不填写直接回车 Organization Name (eg, company) [Default Company Ltd]: # 选填,可不填写直接回车 Organizational Unit Name (eg, section) []: # 选填,可不填写直接回车 Common Name (eg, your name or your server‘s hostname) []: # 选填,可不填写直接回车 Email Address []: # 选填,可不填写直接回车 [root@centos7 certs]# cd ../private/ # 解密key文件,生成无密码的key文件, 过程中需要输入key密码,该密码为之前生成证书时设置的密码 [root@centos7 private]# openssl rsa -in localhost.key -out localhost_nopass.key Enter pass phrase for localhost.key: writing RSA key [root@centos7 private]# ls localhost.key localhost_nopass.key # 备注 如果make testcert出现错误,则删除/etc/pki/tls/private/localhost.key文件,然后再make testcert # 创建用户(用于salt-api认证) useradd -M -s /sbin/nologin saltapi && echo "password"|/usr/bin/passwd saltapi --stdin
# 升级下pip pip install --upgrade pip # pip 安装一些项目所需环境,这个是我自己的项目所需要的, 可以根据自己需求安装 pip install Django==1.9 pip install MySQL-python pip install xlwt pip install pyOpenSSL # 这个是你必须安装的 # 备注下,单独安装pip的方式 wget https://bootstrap.pypa.io/get-pip.py python get-pip.py
# 添加配置文件,可以把eauth.conf和api.conf合二为一为api.conf [root@centos7 ~]# mkdir -p /etc/salt/master.d/ # 这个目录默认不存在,需要手动创建,在/etc/salt/master主配置文件中有指定,类似include [root@centos7 ~]# vim /etc/salt/master.d/eauth.conf # 处于安全因素,一般只给特定模块的使用权限,这里给saltapi用户所有模块的使用权限 external_auth: pam: saltapi: - .* - ‘@wheel‘ - ‘@runner‘ [root@centos7 ~]# vim /etc/salt/master.d/api.conf rest_cherrypy: port: 8000 # salt-api 监听端口 ssl_crt: /etc/pki/tls/certs/localhost.crt # ssl认证的证书 ssl_key: /etc/pki/tls/private/localhost_nopass.key # 备注: 注意所有的缩进都是两个空格,要注意‘:‘后面都有一个空格
# salt-api 配置文件详解 port : 必须填写,salt-api启动的端口 host :默认启动于0.0.0.0,可以不填写 debug : 默认为False,True开启后,会输出debug日志 log_access_file : HTTP访问日志的路径,在2016.11.0版本添加的 log_error_file : HTTP错误日志路径,在2016.11.0版本添加的 ssl_crt : SSL证书的绝对路径 ssl_key: SSK证书的私钥绝对路径 ssl_chain : 在使用PyOpenSSL时可选参数,将证书出递给‘ Context.load_verify_locations ‘ disable_ssl : 禁用SSL标识。认证证书将会被送进clear webhook_disable_auth : False webhook_url : /hook thread_pool : 100 socket_queue_size : 30 expire_responses : True max_request_body_size : 1048576 collect_stats : False stats_disable_auth : False 更多详细参数请见:https://github.com/saltstack/salt/blob/develop/salt/netapi/rest_cherrypy/app.py
# 启动 systemctl start salt-master systemctl start salt-minion systemctl start salt-api
Salt-Api 的使用,启动master, minion , api后,测试通过https操作saltstack
# salt-api 使用 # 登陆认证获取token [root@aliyuntest ~]# curl -sSk https://localhost:8000/login -H ‘Accept: application/x-yaml‘ -d username=saltapi -d password=password -d eauth=pam return: - eauth: pam expire: 1511805994.166656 perms: - .* - ‘@wheel‘ - ‘@runner‘ start: 1511762794.166655 token: 1bc26f7a595eb08c70780352c5724180d5062876 # 关键 user: saltapi # 使用获取的token进行命令操作 [root@aliyuntest ~]# curl -sSk https://localhost:8000 -H ‘Accept: application/x-yaml‘ -H ‘X-Auth-Token: 12ff12468f7ae98d4880fd9a627bf8ef87942d5a‘ -d client=local -d tgt=‘*‘ -d fun=test.ping return: - minion: true 参数解释: client : 模块,python处理salt-api的主要模块,‘client interfaces <netapi-clients>’ local : 使用‘LocalClient <salt.client.LocalClient>’ 发送命令给受控主机,等价于saltstack命令行中的‘salt‘命令 local_async : 和local不同之处在于,这个模块是用于异步操作的,即在master端执行命令后返回的是一个jobid,任务放在后台运行,通过产看jobid的结果来获取命令的执行结果。 runner : 使用‘RunnerClient<salt.runner.RunnerClient>‘ 调用salt-master上的runner模块,等价于saltstack命令行中的‘salt-run‘命令 runner_async : 异步执行runner模块 wheel : 使用‘WheelClient<salt.wheel.WheelClient>‘, 调用salt-master上的wheel模块,wheel模块没有在命令行端等价的模块,但它通常管理主机资源,比如文件状态,pillar文件,salt配置文件,以及关键模块<salt.wheel.key>功能类似于命令行中的salt-key。 wheel_async : 异步执行wheel模块 备注:一般情况下local模块,需要tgt和arg(数组),kwarg(字典),因为这些值将被发送到minions并用于执行所请求的函数。而runner和wheel都是直接应用于master,不需要这些参数。 tgt : minions fun : 函数 arg : 参数 expr_form : tgt的匹配规则 ‘glob‘ - Bash glob completion - Default ‘pcre‘ - Perl style regular expression ‘list‘ - Python list of hosts ‘grain‘ - Match based on a grain comparison ‘grain_pcre‘ - Grain comparison with a regex ‘pillar‘ - Pillar data comparison ‘nodegroup‘ - Match on nodegroup ‘range‘ - Use a Range server for matching ‘compound‘ - Pass a compound match string
从开始到现在,所有的操作都是在python2环境下安装完成的,如果要使用python3呢?
# 首先安装python3 1. tar zxvf Python-3.5.1.tgz 2. cd Python-3.5.1 3. ./configure 4. make 5. make install 6. mv /usr/bin/python /usr/bin/python2 # 如果是软连接,可以直接删除 7. ln -s /usr/local/bin/python3.5 /usr/bin/python 8. vim /usr/bin/yum # 修改Yum,使yum依然有效,yum依靠老版本的python 9. #!/usr/bin/python 修改为#!/usr/bin/python2 # 修改完/usr/bin/yum 依然还有问题,可以尝试修改/usr/libexec/urlgrabber-ext-down的文件python抬头 # 使用Python3直接启动salt,因为默认环境已经切换的python3, 所以直接启动即可 systemctl start salt-master systemctl start salt-minion systemctl start salt-api # 之前有朋友遇到了切换到python3后产生了很多问题,可以试着执行下 python3 -m pip install salt==2017.7.2
现在所有的操作还是基于命令行模式,在项目中不能这么使用,我们可以写一个基于salt-api的类,方便项目代码的调用。在这里特别附上python2、python3两个版本的salt-api class, 在使用中发现,python3版本的salt-api class 是可以直接去请求管理python2版本下的saltstack,这样就解决了一些跨python版本的问题,毕竟现在主流操作系统默认安装的还是python2,避免了手动升级python3, 可以让saltstack在python2下继续运行,而我们可以通过python3去管理saltstack。
1 #coding:utf8 2 import urllib, json 3 import urllib.request 4 import urllib.parse 5 import ssl 6 ssl._create_default_https_context = ssl._create_unverified_context 7 8 9 class SaltAPI(object): 10 __token_id = ‘‘ 11 12 def __init__(self,url,username,password): 13 self.__url = url.rstrip(‘/‘) 14 self.__user = username 15 self.__password = password 16 17 def token_id(self): 18 """ 19 用户登陆和获取token 20 :return: 21 """ 22 params = {‘eauth‘: ‘pam‘, ‘username‘: self.__user, ‘password‘: self.__password} 23 encode = urllib.parse.urlencode(params) 24 obj = urllib.parse.unquote(encode).encode(‘utf-8‘) 25 content = self.postRequest(obj,prefix=‘/login‘) 26 try: 27 self.__token_id = content[‘return‘][0][‘token‘] 28 except KeyError: 29 raise KeyError 30 31 def postRequest(self,obj,prefix=‘/‘): 32 url = self.__url + prefix 33 headers = {‘X-Auth-Token‘: self.__token_id} 34 req = urllib.request.Request(url, obj, headers) 35 opener = urllib.request.urlopen(req) 36 content = json.loads(opener.read()) 37 return content 38 39 def list_all_key(self): 40 """ 41 获取包括认证、未认证salt主机 42 """ 43 44 params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.list_all‘} 45 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 46 self.token_id() 47 content = self.postRequest(obj) 48 minions = content[‘return‘][0][‘data‘][‘return‘][‘minions‘] 49 minions_pre = content[‘return‘][0][‘data‘][‘return‘][‘minions_pre‘] 50 return minions, minions_pre 51 52 def delete_key(self, node_name): 53 ‘‘‘ 54 拒绝salt主机 55 ‘‘‘ 56 57 params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.delete‘, ‘match‘: node_name} 58 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 59 self.token_id() 60 content = self.postRequest(obj) 61 ret = content[‘return‘][0][‘data‘][‘success‘] 62 return ret 63 64 def accept_key(self,node_name): 65 ‘‘‘ 66 接受salt主机 67 ‘‘‘ 68 69 params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.accept‘, ‘match‘: node_name} 70 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 71 self.token_id() 72 content = self.postRequest(obj) 73 ret = content[‘return‘][0][‘data‘][‘success‘] 74 return ret 75 76 def salt_get_jid_ret(self,jid): 77 ‘‘‘ 78 通过jid获取执行结果 79 ‘‘‘ 80 81 params = {‘client‘:‘runner‘, ‘fun‘:‘jobs.lookup_jid‘, ‘jid‘: jid} 82 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 83 self.token_id() 84 content = self.postRequest(obj) 85 ret = content[‘return‘][0] 86 return ret 87 88 def salt_running_jobs(self): 89 ‘‘‘ 90 获取运行中的任务 91 ‘‘‘ 92 93 params = {‘client‘:‘runner‘, ‘fun‘:‘jobs.active‘} 94 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 95 self.token_id() 96 content = self.postRequest(obj) 97 ret = content[‘return‘][0] 98 return ret 99 100 def remote_noarg_execution(self, tgt, fun): 101 """ 102 执行命令没有参数 103 :param tgt: 目标主机 104 :param fun: 执行模块 105 :return: 106 """ 107 params = {‘client‘: ‘local‘, ‘tgt‘:tgt, ‘fun‘: fun} 108 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 109 self.token_id() 110 content = self.postRequest(obj) 111 ret = content[‘return‘][0].values() 112 return ret 113 114 def remote_execution(self,tgt,fun,arg): 115 ‘‘‘ 执行命令有参数 ‘‘‘ 116 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg} 117 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 118 self.token_id() 119 content = self.postRequest(obj) 120 print(content) 121 ret = content[‘return‘][0][tgt] 122 return ret 123 124 def remote_execution_module(self,tgt,fun,arg): 125 ‘‘‘ 126 异步执行远程命令、部署模块 127 ‘‘‘ 128 129 params = {‘client‘: ‘local_async‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg, ‘expr_form‘: ‘list‘} 130 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 131 self.token_id() 132 content = self.postRequest(obj) 133 print(content) 134 jid = content[‘return‘][0][‘jid‘] 135 return jid 136 137 def remote_localexec(self,tgt,fun,arg): 138 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg, ‘expr_form‘: ‘list‘} 139 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 140 self.token_id() 141 content = self.postRequest(obj) 142 print(content) 143 ret = content[‘return‘][0] 144 return ret 145 146 def salt_state(self,tgt,arg,expr_form): 147 ‘‘‘ 148 sls文件 149 ‘‘‘ 150 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: ‘state.sls‘, ‘arg‘: arg, ‘expr_form‘: expr_form} 151 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 152 self.token_id() 153 content = self.postRequest(obj) 154 ret = content[‘return‘][0] 155 return ret 156 157 def project_manage(self,tgt,fun,arg1,arg2,arg3,arg4,arg5,expr_form): 158 ‘‘‘ 159 文件上传、备份到minion、项目管理 160 ‘‘‘ 161 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg1, ‘expr_form‘: expr_form} 162 # 拼接url参数 163 params2 = {‘arg‘:arg2} 164 arg_add = urllib.parse.urlencode(params2) 165 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 166 obj = obj + ‘&‘ + arg_add 167 params3 = {‘arg‘: arg3} 168 arg_add = urllib.parse.urlencode(params3) 169 obj = obj + ‘&‘ + arg_add 170 params4 = {‘arg‘: arg4} 171 arg_add = urllib.parse.urlencode(params4) 172 obj = obj + ‘&‘ + arg_add 173 params5 = {‘arg‘: arg5} 174 arg_add = urllib.parse.urlencode(params5) 175 obj = obj + ‘&‘ + arg_add 176 self.token_id() 177 content = self.postRequest(obj) 178 ret = content[‘return‘][0] 179 return ret 180 181 def file_copy(self,tgt,fun,arg1,arg2,expr_form): 182 ‘‘‘ 183 文件上传、备份到minion、项目管理 184 ‘‘‘ 185 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg1, ‘expr_form‘: expr_form} 186 # 拼接url参数 187 params2 = {‘arg‘:arg2} 188 arg_add = urllib.parse.urlencode(params2) 189 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 190 obj = obj + ‘&‘ + arg_add 191 self.token_id() 192 content = self.postRequest(obj) 193 ret = content[‘return‘][0] 194 return ret 195 196 def file_bak(self,tgt,fun,arg,expr_form): 197 ‘‘‘ 198 文件备份到master 199 ‘‘‘ 200 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg, ‘expr_form‘: expr_form} 201 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 202 self.token_id() 203 content = self.postRequest(obj) 204 ret = content[‘return‘][0] 205 return ret 206 207 def file_manage(self,tgt,fun,arg1,arg2,arg3,expr_form): 208 ‘‘‘ 209 文件回滚 210 ‘‘‘ 211 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg1, ‘expr_form‘: expr_form} 212 params2 = {‘arg‘: arg2} 213 arg_add = urllib.parse.urlencode(params2) 214 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 215 obj = obj + ‘&‘ + arg_add 216 params3 = {‘arg‘: arg3} 217 arg_add_2 = urllib.parse.urlencode(params3) 218 obj = obj + ‘&‘ + arg_add_2 219 self.token_id() 220 content = self.postRequest(obj) 221 ret = content[‘return‘][0] 222 return ret 223 224 def salt_alive(self,tgt): 225 ‘‘‘ 226 salt主机存活检测 227 ‘‘‘ 228 229 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: ‘test.ping‘} 230 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 231 self.token_id() 232 content = self.postRequest(obj) 233 ret = content[‘return‘][0] 234 return ret 235 236 def remote_server_info(self,tgt,fun): 237 ‘‘‘ 238 获取远程主机信息 239 ‘‘‘ 240 params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun} 241 obj = urllib.parse.urlencode(params).encode(‘utf-8‘) 242 self.token_id() 243 content = self.postRequest(obj) 244 ret = content[‘return‘][0][tgt] 245 246 return ret 247 248 249 if __name__ == ‘__main__‘: 250 salt = SaltAPI(url=‘https://localhost:8000‘,username=‘saltapi‘,password=‘password!‘) 251 minions, minions_pre = salt.list_all_key() 252 print(minions)
# -*- coding: utf-8 -*- import urllib2,urllib import time import ssl ssl._create_default_https_context = ssl._create_unverified_context try: import json except ImportError: import simplejson as json class SaltAPI(object): __token_id = ‘‘ def __init__(self,url,username,password): self.__url = url.rstrip(‘/‘) self.__user = username self.__password = password def token_id(self): ‘‘‘ user login and get token id ‘‘‘ params = {‘eauth‘: ‘pam‘, ‘username‘: self.__user, ‘password‘: self.__password} encode = urllib.urlencode(params) obj = urllib.unquote(encode) content = self.postRequest(obj,prefix=‘/login‘) try: self.__token_id = content[‘return‘][0][‘token‘] except KeyError: raise KeyError def postRequest(self,obj,prefix=‘/‘): url = self.__url + prefix headers = {‘X-Auth-Token‘ : self.__token_id} req = urllib2.Request(url, obj, headers) opener = urllib2.urlopen(req) content = json.loads(opener.read()) return content def list_all_key(self): ‘‘‘ 获取包括认证、未认证salt主机 ‘‘‘ params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.list_all‘} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) # 认证的主机列表 minions = content[‘return‘][0][‘data‘][‘return‘][‘minions‘] # 未认证的主机列表 minions_pre = content[‘return‘][0][‘data‘][‘return‘][‘minions_pre‘] return minions,minions_pre def delete_key(self,node_name): ‘‘‘ 拒绝salt主机,删除主机 ‘‘‘ params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.delete‘, ‘match‘: node_name} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) ret = content[‘return‘][0][‘data‘][‘success‘] return ret def accept_key(self,node_name): ‘‘‘ 接受salt主机 ‘‘‘ params = {‘client‘: ‘wheel‘, ‘fun‘: ‘key.accept‘, ‘match‘: node_name} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) ret = content[‘return‘][0][‘data‘][‘success‘] return ret def remote_noarg_execution(self,tgt,fun): ‘‘‘ 执行命令没有参数 tgt:目标主机 fun: 执行模块,例如“test.ping” ‘‘‘ params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) ret = content[‘return‘][0].values() # return ret return ret def remote_execution(self,tgt,fun,arg): ‘‘‘ 执行命令有参数 ‘‘‘ params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) ret = content[‘return‘][0][tgt] return ret def target_remote_execution(self,tgt,fun,arg): ‘‘‘ 异步执行远程命令,部署模块 ‘‘‘ params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: fun, ‘arg‘: arg, ‘expr_form‘: ‘nodegroup‘} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) jid = content[‘return‘][0][‘jid‘] return jid def deploy(self,tgt,arg): ‘‘‘ 模块部署 ‘‘‘ params = {‘client‘: ‘local‘, ‘tgt‘: tgt, ‘fun‘: ‘state.sls‘, ‘arg‘: arg} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) return content def async_deploy(self,tgt,arg): ‘‘‘ 异步执行向客户端发送命令 ‘‘‘ params = {‘client‘: ‘local_async‘, ‘tgt‘: tgt, ‘fun‘: ‘state.sls‘, ‘arg‘: arg} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) jid = content[‘return‘][0][‘jid‘] return jid def target_deploy(self,tgt,arg): ‘‘‘ Based on the node group forms deployment ‘‘‘ params = {‘client‘: ‘local_async‘, ‘tgt‘: tgt, ‘fun‘: ‘state.sls‘, ‘arg‘: arg, ‘expr_form‘: ‘nodegroup‘} obj = urllib.urlencode(params) self.token_id() content = self.postRequest(obj) jid = content[‘return‘][0][‘jid‘] return jid def main(): sapi = SaltAPI(url=‘https://localhost:8000‘,username=‘saltapi‘,password=‘password!‘) sapi.token_id() #print sapi.list_all_key() if __name__ == ‘__main__‘: main()
标签:lookup queue 主机批量管理 div exe emc 功能 拒绝 string
原文地址:http://www.cnblogs.com/wang-yc/p/7904376.html