def get(request, name, oid, key=None, speed=False): hmupName = str2Hump(name) try: redisData = r.get(key) if redisData and speed: res = pickle.loads(redisData) print('get by redis') else: res = query.get(hmupName, oid) print('get by db') if speed: r.set(key, pickle.dumps(res), ex=REDIS_SPEED_TIME) except: print('redis error; get by db') res = query.get(hmupName, oid) if isinstance(res, dict): return ok(res) elif res == 404: return fail('数据库中没有' + name + '这个表', 404, 404) elif res == 4042: return fail('没有这条数据', 404, 404) elif res == 4043: return fail(name + '数据库中没有数据', 404, 404) elif res == 503: return fail('数据查询失败', 503) else: return fail('服务器内部错误', 500, 500)
async def login(request): dat = request.json manageData = {'username': '******', 'password': '******'} res = fail('参数错误', 400) # 检查入参是否正确 if not checkParam(['account', 'password'], dat): del res.cookies['session'] return res # 从传参中解密密码 password = rsaDecrypt(PRIVATE_KEY_PATH, dat['password']) # 检查用户名密码是否正确 if dat['account'] != manageData['username'] \ or password != manageData['password']: res = fail('用户名或密码错误', 400) del res.cookies['session'] return res # 正常处理 token = makeToken(dat['account'], 'manage') res = ok(token) res.cookies['session'] = token res.cookies['session']['httponly'] = True return res
async def tree_channel(request): sourceData = rest.getList({ 'pagesize': -1, 'sort': '-sort,-id' }, 'channel', 'treeChannel', True) if sourceData == 1: return fail('服务器内部错误', 500, 500) if sourceData['total'] < 1: return fail('您当前还没有添加任何栏目') sourceList = sourceData['list'] def makeTree(pid, arr): res = [] for i in arr: if i['pid'] == pid: rep = makeTree(i['id'], arr) if len(rep) != 0: i['children'] = rep res.append(i) return res res = makeTree(0, sourceList) return ok(res)
async def put(request, oid): # 检查必填参数 checkParam(['account', 'old_password', 'new_password'], request) # 检查密码是否符合要求 oldPw = rsaDecrypt(KEY_PATH, request['old_password']) newPw = rsaDecrypt(KEY_PATH, request['new_password']) newPwLen = len(newPw) if oldPw == newPw: return fail('新密码不能与原密码相同') if newPwLen < 6 or newPwLen > 16: return fail('密码长度不能小于6位或大于16位') if not re.match(r'^[a-zA-Z0-9_]+$', newPw): return fail('密码只能是大小写字母加数字以及下划线的组合') # 检查原密码和数据库存储密码是否一致 saveManage = getItem('manages', oid) savePw = rsaDecrypt(KEY_PATH, saveManage['password']) if oldPw != savePw: return fail('原密码不正确') # 检查用户名是否被修改 account = request['account'] if account != saveManage['username']: return fail('用户名不能被修改') # 将参数整理为数据库所需字段 request['username'] = request.pop('account') request['password'] = request.pop('new_password') request.pop('old_password')
def delete(request, name, oid): hmupName = str2Hump(name) res = query.delete(hmupName, oid) if isinstance(res, dict): return ok(res) elif res == 400: return fail('您要删除的数据不存在', 400) elif res == 404: return fail('数据库中没有' + name + '这个表', 404, 404) elif res == 503: return fail('数据删除失败', 503) else: return fail('服务器内部错误', 500, 500)
def post(request, name): hmupName = str2Hump(name) res = query.post(hmupName, request) if isinstance(res, dict): return ok(res) elif res == 400: return fail('参数错误', 400) elif res == 404: return fail('数据库中没有' + name + '这个表', 404, 404) elif res == 503: return fail('数据添加失败', 503) else: return fail('服务器内部错误', 500, 500)
def ls(request, name): hmupName = str2Hump(name) res = query.ls(hmupName, request) if isinstance(res, dict): return ok(res['list'], res['total']) elif res == 400: return fail('参数错误', 400) elif res == 404: return fail('数据库中没有' + name + '这个表', 404) elif res == 503: return fail('数据查询失败', 503) else: return fail('服务器内部错误', 500, 500)
async def post(request): data = request['data'] for i in data: # 检查必填参数 if not checkParam(['account', 'password'], i): return fail('参数错误', 400) account = i['account'] # 检查添加的用户名在数据库中是否存在 saveData = getList({'username': account}, 'manages') if saveData != 1 and saveData['total'] >= 1: return fail('数据库已有用户名为' + account + '的管理员', 400) # 数据库存储字段名为 username 所以这里要改名 i['username'] = i.pop('account')
async def upload(request): # 字节码转16进制字符串 def bytes2hex(bytes): hexstr = u"" for i in range(10): t = u"%x" % bytes[i] if len(t) % 2: hexstr += u"0" hexstr += t return hexstr.lower() # 根据16进制字符串获取文件后缀 def getSuffix(hexStr): print(hexStr) for i in SUPPORT_TYPE: if i in hexStr: return SUPPORT_TYPE[i] return 400 # 判断参数是否正确 if not request.files and not request.files.get('file'): return fail('参数错误', 400) image = request.files.get('file').body # 判断文件是否支持 imageSuffix = getSuffix(bytes2hex(image)) if imageSuffix == 400: return fail('不支持的文件类型', 400) # 组织图片存储路径 m1 = hashlib.md5() m1.update(image) md5Name = m1.hexdigest() saveDir = UPLOAD_PATH + md5Name[0:2] + '/' savePath = saveDir + md5Name[2:] + '.' + imageSuffix resPath = '/' + md5Name[0:2] + '/' + md5Name[2:] + '.' + imageSuffix # 如果文件夹不存在,就创建文件夹 if not os.path.exists(saveDir): os.makedirs(saveDir) # 将文件写入到硬盘 tempFile = open(savePath, 'wb') tempFile.write(image) tempFile.close() # 给客户端返回结果 return ok({"path": resPath})
def get(request, name, oid): hmupName = str2Hump(name) res = query.get(hmupName, oid) if isinstance(res, dict): return ok(res) elif res == 404: return fail('数据库中没有' + name + '这个表', 404) elif res == 4042: return fail('没有这条数据', 404) elif res == 4043: return fail(name + '数据库中没有数据', 404) elif res == 503: return fail('数据查询失败', 503) else: return fail('服务器内部错误', 500, 500)
async def delete(request, oid): # 如果要删除的ID的长度,大于或数据库中所有管理员的条数,则不允许删除 # 因为必须保证系统中有至少一个管理员 idLen = len(oid.split(',')) saveData = getList({'pagesize': -1}, 'manages') if saveData != 1 and idLen >= saveData['total']: return fail('你不能删除所有管理员', 400)
async def post(request): for i in request['data']: # 判断必填字段 if not 'title' in i: return fail('标题不能为空', 400) elif not 'channel_id' in i: return fail('栏目ID不能为空', 400) elif not isInt(i['channel_id']): return fail('栏目ID只能是数字', 400) elif not 'edit_type' in i: return fail('编辑器类别不能为空', 400) elif i['edit_type'] != 'HTML' and i['edit_type'] != 'MD': return fail('编辑器类别指定错误', 400) # 根据编辑器类别,输出文章内容 if i['edit_type'] == 'MD': if not 'markdown' in i: return fail('文章内容不能为空', 400) # 转化markdown格式 i['content'] = markdown.markdown(i['markdown']) if i['edit_type'] == 'HTML': if not 'content' in i: return fail('文章内容不能为空', 400) # 如果没有传入简介信息,则根据文章内容截取 if i.get('description') == None or i.get('description') == '': i['description'] = filterHtml(i['content'])[0:200].replace( '\n', '') if i.get('hits') == None or i.get('hits') == '': i['hits'] = 0 if i.get('isdelete') == None or i.get('hits') == '': i['isdelete'] = 'NO'
async def logout(request): session = request.cookies.get('session') res = fail('退出失败', 401, 401) cs = checkSession(session) if cs == 0: clearSession(session) res = ok('退出成功') del res.cookies['session'] return res
async def post(self, request, name): data = request.json if data: if 'batch_additon' in data and isinstance(data['batch_additon'], list): data = {'data': data['batch_additon']} else: data = {'data': [data]} return await doProcess(app, name, request, data, 'post') else: return fail('数据不能为空', 400)
async def login(request): # 先检查参数是否正确 req = json.loads(request.body) checkParam(['account', 'password'], req) # 根据用户名,从数据库中读取管理员信息 accRes = json.loads(rest.ls({'username': req['account']}, 'manages').body) # 判断查询结果是否正常 if accRes['status'] == 0: # 判断是否查到该管理员 if len(accRes['data']['list']) >= 1: # 处理管路员密码 acc = accRes['data']['list'][0] accPw = rsaDecrypt(KEY_PATH, acc['password']) reqPw = rsaDecrypt(KEY_PATH, req['password']) reqPwLen = len(reqPw) if reqPwLen < 6 or reqPwLen > 16: res = fail('密码长度为 6-16 位') del res.cookies['session'] return res elif accPw != reqPw: res = fail('密码不正确') del res.cookies['session'] return res else: session = makeSession(req['account'], 'manage') res = ok('登录成功') res.cookies['session'] = session res.cookies['session']['httponly'] = True return res else: res = fail('用户名不正确') del res.cookies['session'] return res else: return fail('服务器内部错误', 503)
def middleHandle(request, prefix, authType = 'black', authList = {},\ checkLogin = True, anyList = {}): if prefix in request.url: urlArr = request.url.split(prefix)[1].split('?')[0].split('/') method = request.method apiName = urlArr[0].lower() # 检查请求路径是否合法 if len(urlArr) == 0 or len(urlArr) > 2: return fail('请求路径不合法', 404, 404) # 全局请求方法检查 if len(urlArr) == 1 and not method in ['GET', 'POST']: return fail('不被允许的请求方法', 405, 405) if len(urlArr) == 2 and not method in ['GET', 'PUT', 'DELETE']: return fail('不被允许的请求方法', 405, 405) # 检查请求方法自定义名单处理 for i in authList: m = 'LS' if len(urlArr) == 1 and method == 'GET' else method if apiName == i.lower(): # 黑名单处理 if authType == 'black' and m in authList[i]: return fail('该请求未被授权', 405, 405) # 白名单处理 if authType == 'white' and not m in authList[i]: return fail('该请求未被授权', 405, 405) # 检查接口是否在免登陆列表 noAnyApi = checkLogin if noAnyApi: for i in anyList: if apiName == i.lower(): noAnyApi = False # 校验是否登录 if noAnyApi: session = request.cookies.get('session') cs = checkSession(session) if cs == 1: return fail('没有权限', 401, 401) elif cs == 2: return fail('登录超时', 401, 401) elif cs == 4: return fail('请重新登录', 401, 401) elif cs == 0: updataSession(session)
async def check(request): # 根据请求路径,找到请求对应的接口前缀 prefix = None rep = None for i in c.PREFIX: if request.headers['host'] + c.PREFIX[i] in request.url: prefix = c.PREFIX[i] # 处理后台接口中间处理 if prefix == '/api/v1/be/': rep = middleHandle(request, prefix, 'black', c.BLACK_AUTH, True, c.ANONYMOUS_API) # 处理前台接口中间处理 elif prefix == '/api/v1/fe/': rep = middleHandle(request, prefix, 'white', c.WHITE_AUTH, False) # 处理非法请求地址 else: rep = fail('请求路径不合法', 404, 404) # 处理结果为非空,则直接return if rep: return rep
async def post(request): dat = request['data'] if len(dat) > 1: return fail('站点信息不能更新多条数据', 400, 400) return rest.put(dat[0], 'site', 'first')
async def put(request, oid): return fail('不被允许的请求方法', 405)
async def delete(request, oid): return fail('不被允许的请求方法', 405)
async def upimg(request): # 判断参数是否正确 if not request.files and not request.files.get('file'): return fail('request args is error', 412) image = request.files.get('file').body # 判断文件是否支持 imageSuffix = getSuffix(bytes2hex(image)) if imageSuffix == 'error': return fail('image type is error', 415) # 组织图片存储路径 m1 = hashlib.md5() m1.update(image) md5Name = m1.hexdigest() saveDir = STATIC_DIR + '/' + md5Name[0:2] + '/' savePath = saveDir + md5Name[2:] + '.' + imageSuffix resPath = md5Name + '.' + imageSuffix # 如果文件夹不存在,就创建文件夹 if not os.path.exists(saveDir): os.makedirs(saveDir) # 上传文件附带参数处理 if 'data' in request.form and imageSuffix != 'svg': # 检查参数是否是 json params_text = request.form['data'][0] if not isDictStr(params_text): return fail('form data must be Json String', 412) params = json.loads(params_text) # 读取原图信息 sImg = Image.open(BytesIO(image)) sw, sh = sImg.size # PNG 和 JPG 图片的处理 if imageSuffix != 'gif': oImg = sImg # 处理缩放图片 if 'zoom' in params: zoom = params['zoom'] if not isNum(zoom): return fail('zoom value must be Positive integer', 412) # 如果原图尺寸小于缩放尺寸,则直接保存 if sw <= zoom and sh <= zoom: saveImage(savePath, image) else: oImg = zoomImg(sImg, sw, sh, zoom) # 处理水印参数 mParam = calcMarkParams(params) if not isinstance(mParam, dict) and mParam != False: return mParam if isinstance(mParam, dict): oImg = makeMark(oImg, mParam) # 保存图片 oImg.save(savePath, checkSuffix(imageSuffix)) oImg.close() # 处理 gif 图片 else: oImg = sImg # 将 gif 拆解为一组照片 tempGIf = hackGif(sImg, savePath) tempImgPath = tempGIf[0] tempDirPath = tempGIf[1] gifDur = tempGIf[2] / 1000 tempImg = [] # 压缩每一张照片 for i in tempImgPath: tmp = Image.open(i) if 'zoom' in params: zoom = params['zoom'] if not isNum(zoom): return fail('zoom value must be Positive integer', 412) # 如果原图尺寸小于缩放尺寸,则直接保存 if sw > zoom and sh > zoom: tmp = zoomImg(tmp, sw, sh, zoom) # 处理水印参数 mParam = calcMarkParams(params) if not isinstance(mParam, dict) and mParam != False: return mParam if isinstance(mParam, dict): tmp = makeMark(tmp, mParam) tmp.save(i, 'PNG') tempImg.append(imageio.imread(i)) # 将拆解出来的图片重新组装为gif图片 imageio.mimsave(savePath, tempImg, 'GIF', duration=gifDur) # 删除临时文件夹 shutil.rmtree(tempDirPath) else: saveImage(savePath, image) # 给客户端返回结果 return ok({"path": resPath})
async def put(self, request, name, oid): data = request.json if data: return await doProcess(app, name, request, data, 'put', oid) else: return fail('数据不能为空', 400)
def returnNotFound(request, exception): return fail(request.url + '没有找到', 404, 404)
def calcMarkParams(params): if not 'mark' in params: return False # 确定是否包含水印参数以及水印的类型 mark = params['mark'] if not 'type' in mark: return fail('mark must has type field', 412) mType = mark['type'] if not mark['type'] in ['img', 'text']: return fail('mark type must be "img" or "text"', 412) # 默认水印位置为图片右下角 mParam = {'type': mType, 'location': ['right', 'bottom']} # 如果参数中包含水印位置参数,则重设水印位置参数 if 'location' in mark: mLoca = mark['location'] if not isinstance(mLoca, list): return fail('mark location must be array', 412) if len(mLoca) != 2: return fail('mark location array length must be 2', 412) if not mLoca[0] in ['left', 'right', 'center']: return fail( 'mark location[0] value must in "left", "right", "center"', 412) if not mLoca[1] in ['top', 'bottom', 'center']: return fail( 'mark location[1] value must in "top", "bottom", "center"', 412) mParam['location'] = mLoca # 图片水印参数处理 if mType == 'img': # 图片水印必须包含水印图片名称 if not 'imgName' in mark: return fail('image mark must has "imgName" field', 412) # 根据水印图片名称组织水印图片路径并判断水印是否存在 mImgName = mark['imgName'] markPath = STATIC_DIR + '/' + mImgName[0:2] + '/' + mImgName[2:] if not hasFile(markPath): return fail('image mark was not found', 412) mParam['imgPath'] = markPath # 读取水印图片并获取其宽高设为默认水印大小 mImg = Image.open(markPath) mW, mH = mImg.size mParam['imgSize'] = [mW, mH] # 若果包含水印大小参数则重设水印大小 if 'imgSize' in mark: imgSize = mark['imgSize'] if not isinstance(imgSize, list): return fail('mark imgSize must be array', 412) if len(imgSize) != 2: return fail('mark imgSize array length must be 2', 412) for i in imgSize: if not isNum(i): return fail( 'mark imgSize array value must be Positive integer', 412) mParam['imgSize'] = imgSize # 文字水印参数处理 if mType == 'text': # 文字水印必须包含文字信息 if not 'text' in mark: return fail('Text mark must has text field', 412) mText = mark['text'] if not isinstance(mText, str): return fail('Text mark text field must be String', 412) if len(mText) > 20: return fail('Text mark text field max length is 20', 412) mParam['text'] = mText # 处理颜色参数,默认为白色 mParam['color'] = '#FFFFFF' if 'color' in mark: if not isColor(mark['color']): return fail('mark color value must be a color String', 412) mParam['color'] = mark['color'] # 处理文字大小参数,默认为20,最小值为18 mParam['size'] = 20 if 'size' in mark: mSize = mark['size'] if not isNum(mark['size']): return fail('mark font size value must be Positive integer', 412) if mSize > 18: mParam['size'] = mSize else: mParam['size'] = 18 # 处理文字字体参数 if 'ttf' in mark: mTtf = mark['ttf'] if not isinstance(mTtf, str): return fail('Text mark ttf field must be String', 412) mParam['ttf'] = findTtfPath(mTtf) return mParam