tornado图片服务

来源:互联网 发布:instagram软件下载 编辑:程序博客网 时间:2024/06/07 23:08

tornado上传获取图片的服务,上传限制图片大小,防止重复上传相同图片

对比图片相同的原理是对比图片的大小和图片sha1加密值


目录结构:

OssService

|----common

        | ----__init__.py

        |----rest_result.py(定义响应的错误信息)

|----handler

        |----__init__.py

        |----file_handler.py(tornado的handler)

        |----index.html(测试页面)

|----lib

        |----__init__.py

        |----redis_cache.py(token信息的设置获取)

        |----rest_response.py(响应信息处理)

        |----sqlite_conne.py(sqlite处理相同图片上传)

|----service

        |----__init__.py

        |----file_service.py(tornado的service)

|----utils

        |----__init.py

        |----url_utils.py(url和加密处理)

|----ztouxiang(头像图片目录)

|----zhuida(回答图像目录)

|----ztiwen(问题图片目录)

|----config.py(配置文件)

|----test.db(sqlite3数据库文件)

|----tasks.py(celery的tasks)

|----application.py(tornado主函数)


rest_result.py代码:

# -*- coding: utf-8 -*-SUCCESS = [0, "成功"]KEY_FIELD_MISSING = [101, "关键字段不能为空"]INVALID_OSS_CODE = [102, "无效的oss code"]INVALID_DATA_VALUE = [103, "无效的参数值"]UPLOAD_IMG_ERROR = [104,"上传图片失败"]GET_IMG_ERROR = [105,"获取图片失败"]IMG_MAXSEIZ_ERROR = [106,"图片大小超过最大限制"]IMG_CONTENT_ERROR = [107,"图片不存在"]

file_handler.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"import tornado.webimport tornado.genfrom service.file_service import FileServicefrom lib.rest_response import RestResponsefrom common.rest_result import INVALID_DATA_VALUE,KEY_FIELD_MISSING,IMG_MAXSEIZ_ERROR,IMG_CONTENT_ERRORfrom tasks import addfrom config import imgmaxseizimport timeclass MainHandler(tornado.web.RequestHandler):    def get(self):        self.render('index.html')        # self.finish()class UploadHandler(tornado.web.RequestHandler):    @tornado.web.asynchronous    @tornado.gen.coroutine    def post(self):        # 接收token        token = self.get_argument('token')        # 接收文件        files = self.request.files        resp = RestResponse()        # 没有token或者文件返回错误        if not token or not files:            resp.error(KEY_FIELD_MISSING)            self.write(resp.get_json())            self.finish()        if files.get('file',{}):            myfile = files['file'][0]            # 文件名称            filename = myfile["filename"]            # 文件内容            filebody = myfile["body"]            if filebody and filename:                # 限制图片大小                if len(filebody)>imgmaxseiz:                    resp.error(IMG_MAXSEIZ_ERROR)                    self.write(resp.get_json())                    self.finish()            else:                resp.error(IMG_CONTENT_ERROR)                self.write(resp.get_json())                self.finish()            fs = FileService()            # fun = fs.Upload(token,myfile["filename"],myfile["body"],resp)            # result = add.delay(fs.Upload(token,myfile["filename"],myfile["body"],resp))            # while not result.ready():            #     time.sleep(0.1)            # 回掉函数,参数            result = yield tornado.gen.Task(self.Upload,token,filename,filebody,resp,fs)            # self.write(resp.get_json())            # print(result.get())            # print(type(result.get()))            self.write(resp.get_json())            self.finish()    @tornado.gen.coroutine    # 这里的参数顺序与上面回掉函数后面的参数顺序一致    def Upload(self,a,b,c,d,e):        # 用celery做任务队列        return add.delay(e.Upload(a,b,c,d))# 获取图片class GetImage(tornado.web.RequestHandler):    @tornado.web.asynchronous    @tornado.gen.coroutine    def get(self):        # 获得请求的URL        url = self.request.uri        resp = RestResponse()        if url.count("/")<3:            resp.error(INVALID_DATA_VALUE)            self.write(resp.get_json())        # 图片地址标识        image_path = url.split("/")[2]        # 图片名称        image_name = url.split("/")[3]        rs = FileService()        # data = rs.get_image(image_path,image_name,resp)        # 回掉函数,参数        data = yield tornado.gen.Task(self.get_image,image_path,image_name,resp,rs)        self.write(data)        # 设置读取的格式        self.set_header("Content-type", "image/png")        self.finish()    @tornado.gen.coroutine    # 这里的参数顺序与上面回掉函数后面的参数顺序一致    def get_image(self, a, b, c, e):        return e.get_image(a, b, c)

index.html代码:

<html><body><form action="/save" enctype="multipart/form-data" method="post"><input name="file" type="file"><input name="token" type="text"><input type="submit" value="Submit"></form></body></html>

redis_cache.py代码:

# -*-coding:utf-8 -*-import redisfrom config import redis_host, redis_port, redis_password, image_db# redis队列管理器class RedisManager(object):    def __init__(self, host=redis_host, port=redis_port, paw=redis_password, db=image_db):        self.pool = redis.ConnectionPool(host=host, port=port, password=paw, db=db)        self.r = redis.StrictRedis(connection_pool=self.pool)    # 设置token处理    def setToken(self, key, seconds, value):        self.r.setex(key, seconds, value)    # 获得token信息    def getToken(self, key):        data = self.r.get(key)        if data is None:            return None        return data.decode("utf-8")    # 删除token    def dropToken(self, key):        self.r.delete(key)# rm = RedisManager()# # rm.setToken("111111",3600,"1")# rm.getToken("111111")

rest_response.py代码:

# -*- coding: utf-8 -*-import timeimport json#响应信息处理class RestResponse(object):    def __init__(self):        self.errCode = 0        self.errMsg = ""        self.acceptAt = time.time() * 1000        self.duration = 0        self.data = {}    def success(self, data):        self.data = data    def error(self, error):        self.errCode = error[0]        self.errMsg = error[1]    def get_json(self):        self.duration = time.time() * 1000 - self.acceptAt        return json.dumps(            {"errCode": self.errCode, "errMsg": self.errMsg, "acceptAt": self.acceptAt, "duration": self.duration,             "data": self.data})

sqlite_conne.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"import sqlite3import traceback#用于需要回滚和提交的操作def alter(func):    def wrapper(self,*args,**kwargs):        try:            a = func(self,*args, **kwargs)        except Exception as e:            #如果函数出现错误,不管执行多少数据都全部回滚            self.conn.rollback()            print(traceback.format_exc())            print(str(e))            return traceback.format_exc()        else:            #只有整个函数执行结束没有错误才会提交数据            self.conn.commit()            return a        finally:            self.conn.close()    return wrapper#用于不需要回滚和提交的操作def find(func):    def wrapper(self,*args,**kwargs):        try:            return func(self,*args, **kwargs)        except Exception as e:            print(traceback.format_exc())            print(str(e))            return traceback.format_exc()        # 这里不关闭数据库,因为查询以后我们还需要插入数据        # finally:        #     self.conn.close()    return wrapperclass SqliteConne(object):    def __init__(self,path):        self.conn = sqlite3.connect(path)    def get_cursor(self):        if self.conn is not None:            ccr = self.conn.cursor()            return ccr        else:            print('conn不存在')            return False    @alter    def sql_adddelupdata(self,ccr,sql,t=()):        if ccr and sql:            s = ccr.execute(sql,t)            if s:                return True            else:                return False        else:            print('不存在 ccr 和 sql')            return False    @find    def sql_find(self,ccr,sql,t=()):        if ccr and sql:            s = ccr.execute(sql,t)            slist = ccr.fetchone()            return slist        else:            print('不存在 ccr 和 sql')            return []# sc = SqliteConne("/Users/zjl/Desktop/OssService/test.db")# ccr = sc.get_cursor()# 建表# sql = "create table catalog (id integer primary key ,imgsize integer not null,imgname varchar(50) UNIQUE not null,imgpath varchar(50) not null,imgurl varchar(50) UNIQUE not null,imgpassword varchar(50) UNIQUE not null)"# 新增# sql = "insert into catalog values (?,?,?,?,?,?)"# sqldata = (None,32,"namenamenamenamenamename222222","pathpathpathpathpathpath222222","http://urlurlurlurlurlurlurl222222","passwordpasswordpasswordpasswordpassword222222")# print(sc.sql_adddelupdata(ccr,sql))# 查询# sql = "select imgurl from catalog where imgsize=? and imgpassword=?"# sqldata = (32,"passwordpasswordpasswordpasswordpassword222222")# print(sc.sql_find(ccr,sql,sqldata))# sc.conn.close()

file_service.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"import uuidimport osfrom lib.redis_cache import RedisManager as rmfrom utils.url_utils import get_md5_value,get_sha1_value# from config import image_dbfrom common.rest_result import INVALID_OSS_CODE,INVALID_DATA_VALUE,UPLOAD_IMG_ERROR,GET_IMG_ERRORimport tracebackfrom config import t_path,w_path,d_path,sqlitepath,touxiang,wenti,huida,imghostfrom lib.sqlite_conne import SqliteConneimport sysreload(sys)sys.setdefaultencoding('utf-8')# 全局变量是图片目录路径,这里是相对路径,也可以是绝对路径# t_path = "ztouxiang"# w_path = "zwenti"# d_path = "zhuida"class FileService(object):    def __init__(self):        self.token = str(uuid.uuid1()).replace("-","")+str(uuid.uuid4()).replace("-","")        self.rm = rm()        # 地址标识是为了获取图片时判断去哪个目录下寻找,也是为了隐藏图片在服务器的位置        # 头像地址标识        self.touxiang = touxiang        # 问题地址标识        self.wenti = wenti        # 回答地址标识        self.huida = huida        # 图片服务器IP端口        self.host = imghost    # 上传图片,接收参数为token,文件名,文件内容    def Upload(self,token,filename,filedata,resp):        try:            sc = SqliteConne(sqlitepath)            ccr = sc.get_cursor()            findsql = "select imgurl from catalog where imgsize=? and imgpassword=?"            insertsql = "insert into catalog values (?,?,?,?,?,?)"            # 从redis中获取值,1是头像,2是问题,3是回答            type_x = self.rm.getToken(token)            if type_x==None:                return resp.error(INVALID_OSS_CODE)            type_x = int(type_x)            filesize = len(filedata)            filesha1 = get_sha1_value(filedata)            # 数据库查询值            findsqldata = (filesize,filesha1)            # 数据库查询            db_imgurl_list = sc.sql_find(ccr,findsql,findsqldata)            # 如果图片的大小和sha1值一致就当是同一张图,直接返回URL            if db_imgurl_list:                data = {"ossUrl":db_imgurl_list[0]}                return resp.success(data)            if type_x==1:                # 头像图片目录                image_path = t_path                # 头像图片名称是token加文件名md5加密产生                image_name = get_md5_value(token+filename)+".png"                # 写入图片                with open(image_path+os.sep+image_name,"wb") as of:                    of.write(filedata)                # 生成图片url                imgurl = self.host+"/image/"+self.touxiang+"/"+image_name                data = {                    "ossUrl":imgurl                }                # 插入数据                insertsqldata = (None, filesize,image_name,image_path+os.sep,imgurl,filesha1)                sc.sql_adddelupdata(ccr,insertsql,insertsqldata)            elif type_x==2:                image_path = w_path                image_name = get_md5_value(token + filename) + ".png"                imgurl = self.host + "/image/" + self.wenti + "/" + image_name                # 插入数据                insertsqldata = (None, filesize, image_name, image_path + os.sep, imgurl, filesha1)                sc.sql_adddelupdata(ccr, insertsql, insertsqldata)                with open(image_path + os.sep + image_name, "wb") as of:                    of.write(filedata)                data = {                    "ossUrl": imgurl                }            elif type_x==3:                image_path = d_path                image_name = get_md5_value(token + filename) + ".png"                imgurl = self.host + "/image/" + self.huida + "/" + image_name                # 插入数据                insertsqldata = (None, filesize, image_name, image_path + os.sep, imgurl, filesha1)                sc.sql_adddelupdata(ccr, insertsql, insertsqldata)                with open(image_path + os.sep + image_name, "wb") as of:                    of.write(filedata)                data = {                    "ossUrl": imgurl                }            else:                return resp.error(INVALID_DATA_VALUE)        except Exception as e:            print(traceback.print_exc())            return resp.error(UPLOAD_IMG_ERROR)        else:            return resp.success(data)    # 获得图片,参数为地址标识,图片名称    def get_image(self,path,imagename,resp):        try:            # 判断地址标识            if path==self.touxiang:                # 图片目录                path=t_path                # 读取图片                with open(path+os.sep+imagename,"rb") as of:                    imgae_data = of.read()            elif path==self.wenti:                path = w_path                with open(path + os.sep + imagename, "rb") as of:                    imgae_data = of.read()            elif path==self.huida:                path = d_path                with open(path + os.sep + imagename, "rb") as of:                    imgae_data = of.read()            else:                return resp.error(INVALID_DATA_VALUE)        except Exception as e:            print(traceback.print_exc())            return resp.error(GET_IMG_ERROR)        else:            return imgae_data

url_utils.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"from urlparse import urlparse# url_str = "http://127.0.0.1:5000/image/ztouxiang/753ee3a77989498ca088f7ca7d1fb26e.png"# 获得文件名和目录名def analysisUrl(url_str):     if url_str:          url = urlparse(url_str)          url_path = url.path          if url_path.count("/") >= 3:               image_path = url_path.split("/")[2]               image_name = url_path.split("/")[3]               data = {                   "image_path":image_path,                   "image_name":image_name,               }               return dataimport hashlib# md5加密def get_md5_value(src):    myMd5 = hashlib.md5()    myMd5.update(src)    myMd5_Digest = myMd5.hexdigest()    return myMd5_Digest# sha1加密def get_sha1_value(src):    return hashlib.sha1(src).hexdigest()

config.py代码:

# -*- coding: utf-8 -*-# 项目端口信息port = "8084"# redis 配置文件redis_db = 0token_db = 1image_db = 4redis_host = "127.0.0.1"redis_port = "6379"redis_password = ""image_tasks_br = "7"image_tasks_ba = "7"# 图片文件路径t_path = "ztouxiang"w_path = "zwenti"d_path = "zhuida"# 头像地址标识touxiang = "ztouxiang"# 问题地址标识wenti = "zwenti"# 回答地址标识huida = "zhuida"# 图片服务器IP端口imghost = "http://127.0.0.1:8084"# sqlite数据库文件路径sqlitepath = "/Users/zjl/Desktop/OssService/test.db"# 图片大小限制imgmaxseiz  = 5242880

tasks.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"from celery import Celeryfrom config import redis_password, redis_host, redis_port, image_tasks_br, image_tasks_baBROKER_URL = 'redis://:'+redis_password+'@'+redis_host+':'+redis_port+'/'+image_tasks_brBACKEND_URL = 'redis://:'+redis_password+'@'+redis_host+':'+redis_port+'/'+image_tasks_baapp = Celery('tasks',broker=BROKER_URL,backend=BACKEND_URL)@app.taskdef add(fun):    return fun

application.py代码:

# -*- coding: utf-8 -*-#__author__="ZJL"import tornado.httpserverimport tornado.ioloopimport tornado.webimport tornado.optionsfrom handler.file_handler import MainHandler,UploadHandler,GetImageimport tornado.wsgiimport wsgiref.simple_serversettings = dict(    # xsrf_cookies=False,  # 开启跨域安全    gzip=True,  # gzip输出    debug=True,  # 调试模式)handlers = [    (r"/", MainHandler),  # 测试    (r"/save",UploadHandler),# 上传文件    (r"/image/.*/.*",GetImage),# 获取图片    ]application = tornado.web.Application(handlers, **settings)def main():    http_server = tornado.httpserver.HTTPServer(application)    http_server.listen(8084)    tornado.ioloop.IOLoop.instance().start()if __name__ == "__main__":    # import os    # os.system("celery -A tasks worker --loglevel=info")    main()

使用方法:

redis的db4中存入token ,key是token,value是标记(1是头像,2是问题,3是回答)是为了判断图片存哪个目录


sqlite先建表,建表语句:

id integer primary key ,imgsize integer not null,imgname varchar(50) UNIQUE not null,imgpath varchar(50) not null,imgurl varchar(50) UNIQUE not null,imgpassword varchar(50) UNIQUE not null



浏览器输入http://127.0.0.1:8084进入测试页面

postman测试:





在这里的响应json中有获取图片的url


拿到图片


据说为了让图片文件夹不至于因为目录过大导致检索过慢会在目录下再创建上百个子目录,上传图片的时候随机存入子目录