1.python利用flask框架和tornado框架搭建api微服务——HelloWorld的实现(一)
2.python利用flask框架和tornado框架搭建api微服务——结合html网页实现get和post(二)
3.python利用flask框架和tornado框架搭建api微服务——连接数据库返回带参求情结果(三)
4.python利用flask框架和tornado框架搭建api微服务——python虚拟机启动API(四)
5.python利用flask框架和tornado框架搭建api微服务——Linux下查看某个端口对应的进程并kill进程的操作(关闭API服务进程)(五)
6.python利用flask框架和tornado框架搭建api微服务——完善API文档以及API调用(六)
上篇我们写了个单机版的,但是实际操作中,肯定是用数据库的,我这里用的SQL SERVER
,所以环境内要新增pip install pymssql
来安装连接SQL SERVER
的包,至于MYSQL的,请自己发挥,毕竟子曾经曰过,“举一隅不以三隅反,则不复也。”
和上一篇一样,首先是我们构建一个日志,方便收集api服务的各种操作日志,就命名为logUtil.py吧,这个日志包需要在logUtil.py文件的当前目录下新建一个log文件夹,不然会报错,我偷懒了,应该写个如果没该文件,就创建一下的,logUtil.py的具体代码如下:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# vim:fenc=utf-8
"""
log module, need a ./log directory
"""
import logging.config
config = {
'version': 1,
'formatters': {
'simple': {
'format': '%(asctime)s-%(thread)d-%(filename)s[line:%(lineno)d]%(levelname)s: %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
'encoding': 'utf-8',
'filemode': 'a'
}
# other formater
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'simple'
},
'file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': './log/' + __name__ + '.log',
'level': 'DEBUG',
'formatter': 'simple',
'when': 'h',
'interval': 1
}
# other handler
},
'loggers': {
'StreamLogger': {
'handlers': ['console'],
'level': 'DEBUG',
},
'FileLogger': {
# have console and file
'handlers': ['console', 'file'],
'level': 'DEBUG',
}
# other logger
}
}
logging.config.dictConfig(config)
StreamLogger = logging.getLogger("StreamLogger")
FileLogger = logging.getLogger("FileLogger")
每次我们下载一个软件解压后,都可以看到各个文件和文件夹井然有序,当然我们自己构建的项目也自然不能太low,不然对不起我们这张帅气的脸对吧,这个项目主要是获取iot设备的信息,总体文件夹命名为iot_location_desc_match,分布如下:
iot_location_desc_match/ # 总文件夹
├ iot_location_desc_match.py # 应用启动程序
├ get_location_from_mssql.py #从数据库获取
├ logUtil.py # 日志包
├ log/ # log文件夹,存放操作日志
│ ├ logUtil.log
│ └ main.logUtil.log
├ templates/ #flask规定,不能改名,存放html文件
│ ├ get.html
│ └ post.html
├ conf/ #自定义,用来存放配置文件,数据库的登录配置信息
└ myconfig,ini #数据库的登录配置信息就写在这里
这里主要的实现代码有两部分,一部分是重数据库上获取数据的get_location_from_mssql.py
,另一个是总体的API功能iot_location_desc_match.py
其中大概思路就是API去调用数据库的数据,数据库返回json格式的数据给到API,再返回给用户,具体访问数据库的代码如下:
get_location_from_mssql.py
[mssql_config]
host = 10.232.19.98
port = 1433
user = dw_user_reader
passwd = iloveyou123
db = db_iot
然后开始我们的get_location_from_mssql.py
代码如下:
注意:代码内用到json.dumps方法是用来将python字符类型转成json字典类,注意如果返回的json内包含中文的话,一定要在里面加上参数ensure_ascii=False
,不然会导致中文返回的是/u的unicode,影响心情
#! /usr/bin/env python
#encoding=utf-8
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
import pymssql
import configparser
import os
import sys
import logUtil
import json
logger = logUtil.FileLogger #导入日志包
def get_fdo_coon(dianzino): #定义获取数据的函数,其中dianzino是用户给的参数
if len(dianzino) != 0 and dianzino.replace(',','').isdigit(): #因为dianzino都是数字,所以简单的处理下判断是否为空以及是否是数据,传过来的是列表[021312319,0293123120932,0283219241],所以去掉逗号
dianzino=dianzino.replace(',','\',\'') #传过来的是单纯的字符,在数据库里面用 in语法的话要拼接成 dianzi in ('021312319','0293123120932','0283219241')
#print(dianzino)
# 从该项目的conf folder下获取配置
config = configparser.ConfigParser()
cur_path = os.path.dirname(__file__)
# print(cur_path)
config_path = "./conf/myconfig.ini" #利用配置文件的包读取数据库登录信息
logger.info("config_path:%s" % config_path) #收集日志
config.read(config_path)
config_host = config['mssql_config']["host"]
# config_port = int(config['mssql_config']["port"])
config_user = config['mssql_config']["user"]
config_passwd = config['mssql_config']["passwd"]
config_db = config['mssql_config']["db"]
# 连接fdo的sql server
conn = pymssql.connect(config_host, config_user, config_passwd, config_db,charset = "GBK")
cursor = conn.cursor()
sql = """select DianziNo
,CityName
,PatrolArea
,PatrolMan
,BuildingName
,b.LocationKey
,LocationDesc
,DeviceStyleName
,InstallStatus
,InstallStatusName
,IsPresent
,convert(varchar(10),PreRemoveDate,121) as PreRemoveDate
FROM media.tbb_Building a
JOIN media.tbb_MediaLocation b ON a.BuildingID=b.BuildingID
JOIN assemble.tbb_MediaDevice c ON b.LocationID=c.LocationID
join assemble.tbd_DeviceStyle ds on c.DeviceStyleID=ds.DeviceStyleID
left join media.tbi_LocationPresentType lp on c.LocationID=lp.LocationID
WHERE a.ValidStatus>=0 AND b.ValidStatus>=0 AND c.ValidStatus>=0
and c.DianziNo in('%s')
order by Dianzino asc"""
#排一下序,虽然浪费点时间,但是确保每次获取返回的值不会乱序
my_json_str = []
sql = sql % (dianzino)
logger.info("sql:%s" % sql)
cursor.execute(sql) #执行sql语句
result = cursor.fetchall() #用先把所有数据都拿出来
conn.close()
if len(result) != 0: #如果数据量不为空,则返回json串,注意ensure_ascii=False一定要写,不然有中文的话会返回乱码
return json.dumps({"Status Code": 200,\
"Data": [\
{\
"DianziNo": row[0],\
"CityName": row[1],\
"PatrolArea": row[2],\
"PatrolMan": row[3],\
"BuildingName": row[4],\
"locationid": row[5],\
"location_desc": row[6],\
"DeviceStyleName": row[7],\
"InstallStatus": row[8],\
"InstallStatusName": row[9],\
"IsPresent": row[10],\
"PreRemoveDate": row[11]\
} for row in result\
]\
},ensure_ascii=False,indent=4)
else:
return json.dumps({"Status Code": 404,"Data":"sorry,this DianziNo not found any location!"},indent=4)#如果没数据,返回状态码404
else:
return json.dumps({"Status Code": 400,"Data":"sorry,your parameter was empty or wrong,such as the DianziNo not digit,please try again!"},indent=4)
#如果参数不对,返回状态码400
#conn.close()
def main(argv): #定义带参的main函数
dianzino = argv[1]
jsondata=get_fdo_coon(dianzino) #调用get_fdo_coon来获取json数据
print(jsondata)
if __name__ == '__main__':
main(sys.argv) #因为用户要传入带参的dianzino,所以要定义一个带参的main函数
iot_location_desc_match.py
iot_location_desc_match.py
,用来总体实现根据参数dianzino返回用户需要的信息,期间涉及的HTTP状态码
(HTTP Status Code
)是用以表示网页服务器超文本传输协议响应状态的3位数字,反正4开头的都是不好的,具体可以看看HTTP基础知识了解下,当然我们也不可能去写所有的状态码返回,挑选几个典型的即可。代码具体代码如下:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
from flask import Flask
from flask import render_template
from flask import request
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
import json
import logUtil #导入自定义的日志包
import get_location_from_mssql.py #导入自定义的获取sql server的数据的包
app = Flask(__name__) #初始化Flask类的app对象
logger = logUtil.FileLogger #初始化日志的对象
# set route
@app.route('/') #设置默认路由
def hello_world():
return 'Hello, Guys,Welcome!!' #将之前的语句helloworld替换,返回个问候语句
# render_template search templates dir default
@app.route('/get.html') #定义get.html路由
def get_html():
return render_template('get.html') #返回get.html网页的响应
#这里的post界面就没什么用了,因为后面处理post是直接用python调用api,没要走网页的form形式
@app.route('/post.html')
def post_html():
return render_template('post.html')
@app.route('/iot_location_desc_match', methods = ['GET', 'POST']) #定义iot_location_desc_match路由
def iot_location_desc_match(): #定义iot_location_desc_match对应的方法
if request.method == "GET": #如果用get方法
# get通过request.args.get("param_name","")形式获取参数值
logger.info('a GET request') #收集日志
for key,value in request.args.items(): #将get的参数和值收集
logger.info('{key}:{value}'.format(key = key, value = value))
try:
print(type(value))
myjs=json.dumps(value)
pdata=json.loads(value)
if(len(pdata['dianzino'])>2000): #传入的dianzino是一个list,限制下长度,不要无限长
get_result=json.dumps({"Status Code": 500,"Data":"sorry,your parameter amount more than 2000,please decrease some!"},indent=4) #超过两千跑出500的状态码
else:
dianzinos=''
for str in pdata['dianzino']:
dianzinos=(dianzinos+str+',') #dianzino是个列表,把这个列表的数据串起来,做成如 '0232103,02489021,02401421,'的逗号隔开
dianzinos=dianzinos[:-1] #把最后的逗号去掉
get_result=get_location_from_mssql.get_fdo_coon(dianzinos) #调用上面自定义的get_location_from_mssql.get_fdo_coon获取数据库的数据,并且以json串返回
except Exception:
get_result=json.dumps({"Status Code": 500,"Data":"sorry,the api was wrong,please mail to [email protected]!"},indent=4) #捕获意想不到的异常,返回状态码500
return get_result #返回结果
elif request.method == "POST": #如果方法是POST
# post通过request.form["param_name"]形式获取参数值 这个需要抛弃了,改用request.get_data()获取
logger.info('a POST request') #收集日志
value = request.get_data() #不能用request.form["param_name"]了,改成 request.get_data(),具体request的获取数据方法可以百度下官网,种类繁多,有详细介绍
logger.info('{value}'.format(value = value)) #key已经没用了,我们只要客户传入的value
try:
#myjs=json.dumps(value)
pdata=json.loads(value) #将用户传入的json串转化成python的字符类型
if(len(pdata['dianzino'])>2000): #限制下客户传入的dianzino长度,别太长,太长了让用户多调几次
get_result=json.dumps({"Status Code": 500,"Data":"sorry,your parameter amount more than 2000,please decrease some!"},indent=4) #返回参数太长的状态码500
else:
dianzinos=''
for str in pdata['dianzino']:
dianzinos=(dianzinos+str+',') #dianzino是个列表,把这个列表的数据串起来,做成如 '0232103,02489021,02401421,'的逗号隔开
dianzinos=dianzinos[:-1] #把最后的逗号去掉
post_result=get_location_from_mssql.get_fdo_coon(dianzinos) #调用上面自定义的get_location_from_mssql.get_fdo_coon获取数据库的数据,并且以json串返回
except Exception:
post_result=json.dumps({"Status Code": 500,"Data":"sorry,the api was wrong,please mail to [email protected]!"},indent=4) #捕获意想不到的异常,返回状态码500
return post_result
else:
logger.warn('a request is neither a GET nor a POST')
if __name__ == '__main__':
#app.debug = True
#app.run(port=5001)
#app.run('0.0.0.0', 5000, debug=True)
port=7000 #设置程序启动的端口
http_server = HTTPServer(WSGIContainer(app)) #将api的初始化对应委托给tornado的HTTPServer
http_server.listen(port) #监听端口
logger.info('Listening on {}'.format(port))
IOLoop.instance().start() #启动微服务
这里主要讲的是用python调用api,后面用户调用也是用python直接调用api的,不再使用章节二里面说到的浏览器了访问了,如果对网页感兴趣,可以在篇二上修改,这里就直接把api启动起来,先进入总目录下iot_location_desc_match,然后shell启动脚本如下,返回
2020-04-07 19:36:50-140333704324928-iot_location_desc_match.py[line:100]INFO: Listening on 7000
就正常了。
python iot_location_desc_match.py
2020-04-07 19:36:50-140333704324928-iot_location_desc_match.py[line:100]INFO: Listening on 7000