Browse Source

脚底板增加进程池

master
dongchangxi 11 months ago
parent
commit
e11272ddee
  1. 504
      blender/fill_dm_code.py

504
blender/fill_dm_code.py

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import os, sys, bpy, math, time, platform, cairosvg, ppf.datamatrix, shutil, requests, json, redis, oss2, cv2,qrcode
from retrying import retry
import subprocess
import multiprocessing
import random
import numpy as np
import matplotlib.pyplot as plt
@ -403,238 +404,212 @@ def remove_gray_and_sharpening(jpg_path): @@ -403,238 +404,212 @@ def remove_gray_and_sharpening(jpg_path):
# show_histogram(input_image, img_id, low_x_thresh, high_x_thresh)
cv2.imwrite(jpg_path, high_output_image, [cv2.IMWRITE_JPEG_QUALITY, 95]) # 保存图片的质量是原图的 95%
def main(workdir, r, print_id):
print('脚底板二维码程序开始运行...')
only_one = False
while True:
#随机休眠 1- 9
time.sleep(random.uniform(1, 9))
if print_id == '0':
try:
if r.llen('model:foot') == 0:
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '队列为空,5秒后重试')
time.sleep(5)
continue
except Exception as e:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), 'redis连接异常,5秒后重试')
print(e)
time.sleep(5)
r = redis.Redis(host='106.14.158.208', password='kcV2000', port=6379, db=6)
# r = redis.Redis(host='172.31.1.254', password='', port=6379, db=6)
# continue
# 打印队列里面的全部内容
print(f'当前model:foot队列长度:{r.llen("model:foot")}')
for i in r.lrange('model:foot', 0, -1):
print(i)
print_id = r.lpop('model:foot')
if print_id is None:
print_id = '0'
continue
#判断是否存在相同的值
isHaveAlready = 0
for i in r.lrange('model:foot', 0, -1):
if i == print_id:
isHaveAlready = 1
if isHaveAlready == 1:
print_id = '0'
continue
print_id = print_id.decode('utf-8')
else:
print(f'接收到运行一个{print_id}任务')
only_one = True
res = requests.get(f'{get_pid_by_printid_url}?print_id={print_id}')
print('获取pid:', f'{get_pid_by_printid_url}?print_id={print_id}', res.text)
resCode = json.loads(res.text)['code']
#该笔订单的获取信息有误,可能信息还没有成功
if int(resCode) != 1000:
tempMesg = json.loads(res.text)['message']
#判断是否包含 不存在
if tempMesg == "打印订单不存在":
print(f"打印ID{print_id}打印订单不存在,跳过")
# res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
os.system(f'blender -b -P fill_dm_code.py')
return
print("获取打印任务信息有问题,重新任务队列,打印id-",print_id,"重新执行脚底板任务,等待20s")
time.sleep(20)
#将pid 重新扔进队列
r.lpush("model:foot", print_id)
#重新调用脚底板程序
#获取该笔订单的信息,判断是否存在,不存在就跳过
def getInfoByPrintId(print_id):
res = requests.get(f'{get_pid_by_printid_url}?print_id={print_id}')
print('获取pid:', f'{get_pid_by_printid_url}?print_id={print_id}', res.text)
resCode = json.loads(res.text)['code']
#该笔订单的获取信息有误,可能信息还没有成功
if int(resCode) != 1000:
tempMesg = json.loads(res.text)['message']
#判断是否包含 不存在
if tempMesg == "打印订单不存在":
print(f"打印ID{print_id}打印订单不存在,跳过")
# res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
#os.system(f'blender -b -P fill_dm_code.py')
continue
use_foot_type = json.loads(res.text)['data']['use_foot_type']
pid = json.loads(res.text)['data']['pid']
order_id = json.loads(res.text)['data']['order_id']
filename = os.path.join(workdir, f'{pid}_{order_id}', find_obj(pid, order_id))
print('导入obj文件:', filename)
# return
if only_one:
print(f'接收到运行一个{print_id}任务,强制调用cal_foot_position.py计算并上传qr_position')
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}')
print("延时20s,等待计算脚底板坐标")
time.sleep(20)
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
print('从云端获取的qr_position1:', res.text)
codeTemp = json.loads(res.text)['code']
if str(codeTemp) == "-1":
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
os.system(f'blender -b -P fill_dm_code.py')
return
qr_position = json.loads(res.text)['data']['position_data']
else:
#从云端获取qr_position,如果获取为空,调用cal_foot_position.py计算并上传qr_position,再重新读取qr_position.txt
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
print('从云端获取的qr_position2:', res.text)
return False
codeTemp = json.loads(res.text)['code']
if str(codeTemp) == "-1":
continue
qr_position = json.loads(res.text)['data']['position_data']
print("云端获取的坐标数据",qr_position)
if qr_position == '':
time.sleep(3)
print('qr_position为空,调用cal_foot_position.py计算并上传qr_position')
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}')
print("延时20s,等待计算脚底板坐标")
time.sleep(20)
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
print('从云端获取的qr_position3:', res.text)
qr_position = json.loads(res.text)['data']['position_data']
else:
qr_position = json.loads(qr_position)
if qr_position == "":
print("获取脚底坐标数据为空,重新任务队列,打印id-",print_id,"重新执行脚底板任务")
#将pid 重新扔进队列
r.lpush("model:foot", print_id)
#重新调用脚底板程序
#os.system(f'blender -b -P fill_dm_code.py')
continue
print("获取打印任务信息有问题,重新任务队列,打印id-",print_id,"重新执行脚底板任务,等待20s")
time.sleep(20)
#将pid 重新扔进队列
r.lpush("model:foot", print_id)
return False
return res.text
#获取订单的坐标信息,判断是否有,没有的话就重新生成坐标信息
def getQrPosition(print_id):
#从云端获取qr_position,如果获取为空,调用cal_foot_position.py计算并上传qr_position,再重新读取qr_position.txt
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
print('从云端获取的qr_position1:', res.text)
codeTemp = json.loads(res.text)['code']
if str(codeTemp) == "-1":
print(f"获取打印ID{print_id}的脚底板坐标失败,进行下一笔订单的处理")
return False
qr_position = json.loads(res.text)['data']['position_data']
if qr_position == '':
print(f"pid={pid},打印id={print_id} order_id={order_id},云端获取的坐标数据为空,进行重新计算")
time.sleep(3)
print('qr_position为空,调用cal_foot_position.py计算并上传qr_position')
os.system(f'blender -b -P cal_foot_position.py -- {pid}_{order_id}_{print_id}')
print("延时20s,等待计算脚底板坐标")
time.sleep(20)
res = requests.get(f'{get_qr_position_url}?print_id={print_id}')
print(f"pid={pid},打印id={print_id} order_id={order_id},重新获取qr_position",res.text)
print('从云端获取的qr_position3:', res.text)
if str(json.loads(res.text)['code']) == "-1":
print(f"pid={pid},打印id={print_id} order_id={order_id},重新获取qr_position 失败",res.text)
return False
qr_position = json.loads(res.text)['data']['position_data']
if qr_position == '':
#跳过该笔订单
print(f"pid={pid},打印id={print_id} order_id={order_id},重新获取qr_position 为空,跳过脚底板的处理,执行下一笔任务")
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
return False
else:
qr_position = json.loads(qr_position)
if type(qr_position) == str: qr_position = json.loads(qr_position)
print(f'type of qr_position:{type(qr_position)}')
print(f'qr_position:{qr_position}')
if qr_position == "":
print("获取脚底坐标数据为空,重新任务队列,打印id-",print_id,"重新执行脚底板任务")
#将pid 重新扔进队列
r.lpush("model:foot", print_id)
return False
return qr_position
qr_position['location'][2] = -0.1
def main(workdir, r, print_id):
resText = getInfoByPrintId(print_id)
print(f"根据打印ID{print_id}获取订单信息-内容{resText}")
if resText == False:
print(f"根据打印ID{print_id}获取订单信息失败")
return
arrResText = json.loads(resText)
#订单的相关信息
use_foot_type = arrResText['data']['use_foot_type'] #构建类型
pid = arrResText['data']['pid'] #pid
order_id = arrResText['data']['order_id'] # order_id
#文件路劲
filename = os.path.join(workdir, f'{pid}_{order_id}', find_obj(pid, order_id))
print('导入obj文件:', filename)
#获取该笔订单的坐标
qr_position = getQrPosition(print_id)
if qr_position == False:
return
if type(qr_position) == str: qr_position = json.loads(qr_position)
print(f'type of qr_position:{type(qr_position)}')
print(f'qr_position:{qr_position}')
qr_position['location'][2] = -0.1
temp_foot_data = print_id
# 根据print_id生成qr码
qr_path = os.path.join(workdir, f'{pid}_{order_id}' ,'qr.png')
# if use_foot_type == "short_url":
# #调用接口获取短网址信息
# short_url = get_short_url(print_id)
# if short_url == False:
# return
# temp_foot_data = short_url
# print(f'temp_foot_data---{temp_foot_data}')
gen_data_matrix(print_id,pid, qr_path)
#休眠1秒
time.sleep(2)
#判断是否存在 qr_path,不存在就执行下一个任务
if not os.path.exists(qr_path):
#移除该脚底板的面积处理
print(f"pid={pid},打印id={print_id} order_id={order_id},生成的二维码为空,跳过脚底板的处理,执行下一笔任务")
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
#os.system(f'blender -b -P fill_dm_code.py')
return
#在blender中贴上脚底板二维码的信息
# 导入obj文件,重置到标准单位
bpy.ops.wm.read_homefile()
bpy.context.preferences.view.language = 'en_US'
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.context.scene.unit_settings.scale_length = 0.001
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
bpy.ops.import_scene.obj(filepath=filename)
#bpy.ops.wm.obj_import(filepath=filename)
obj = bpy.context.selected_objects[0]
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
temp_foot_data = print_id
# 根据print_id生成qr码
qr_path = os.path.join(workdir, f'{pid}_{order_id}' ,'qr.png')
# if use_foot_type == "short_url":
# #调用接口获取短网址信息
# short_url = get_short_url(print_id)
# if short_url == False:
# return
# temp_foot_data = short_url
# print(f'temp_foot_data---{temp_foot_data}')
gen_data_matrix(print_id,pid, qr_path)
#休眠1秒
time.sleep(1)
#判断是否存在 qr_path,不存在就执行下一个任务
if not os.path.exists(qr_path):
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
os.system(f'blender -b -P fill_dm_code.py')
return
pid_objname = find_pid_objname(pid)
scale = 90 / obj.dimensions.y
obj.scale = (scale, scale, scale)
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Z'})
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
obj.location[0] = 0
obj.location[1] = 0
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
# 导入obj文件,重置到标准单位
bpy.ops.wm.read_homefile()
bpy.context.preferences.view.language = 'en_US'
bpy.ops.object.delete(use_global=False, confirm=False)
bpy.context.scene.unit_settings.scale_length = 0.001
bpy.context.scene.unit_settings.length_unit = 'CENTIMETERS'
bpy.context.scene.unit_settings.mass_unit = 'GRAMS'
bpy.ops.import_scene.obj(filepath=filename)
#bpy.ops.wm.obj_import(filepath=filename)
obj = bpy.context.selected_objects[0]
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
pid_objname = find_pid_objname(pid)
scale = 90 / obj.dimensions.y
obj.scale = (scale, scale, scale)
bpy.ops.object.align(align_mode='OPT_1', relative_to='OPT_1', align_axis={'Z'})
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS', center='MEDIAN')
obj.location[0] = 0
obj.location[1] = 0
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
print(f'qr_position:{qr_position}')
print(f'qr_position_type:{type(qr_position)}')
# 根据qr_position的值,恢复qr的位置和尺寸,重新生成贴图
bpy.ops.object.load_reference_image(filepath=os.path.join(workdir, f'{pid}_{order_id}', 'qr.png'))
bpy.context.object.rotation_euler = (math.radians(-180), math.radians(0), qr_position['rotation'])
bpy.ops.transform.translate(value=qr_position['location'], orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements={'INCREMENT'}, use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False, release_confirm=True)
bpy.context.object.empty_display_size = qr_position['dimensions'][0]
qr_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}_{order_id}Tex1_qr.png")
jpg_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg")
#判断是否存在 jpg_path,不存在就执行下一个任务
if not os.path.exists(jpg_path):
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
os.system(f'blender -b -P fill_dm_code.py')
return
jpg_img = Image.open(jpg_path)
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_noqr.jpg"))
bpy.context.scene.eyek.res_x = jpg_img.width
bpy.context.scene.eyek.res_y = jpg_img.height
bpy.context.scene.eyek.path_export_image = qr_path
bpy.data.objects[pid_objname].select_set(True)
bpy.data.objects['Empty'].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects[pid_objname]
bpy.ops.eyek.exe()
print(f'qr_position:{qr_position}')
print(f'qr_position_type:{type(qr_position)}')
# 根据qr_position的值,恢复qr的位置和尺寸,重新生成贴图
bpy.ops.object.load_reference_image(filepath=os.path.join(workdir, f'{pid}_{order_id}', 'qr.png'))
bpy.context.object.rotation_euler = (math.radians(-180), math.radians(0), qr_position['rotation'])
bpy.ops.transform.translate(value=qr_position['location'], orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, use_proportional_connected=False, use_proportional_projected=False, snap=False, snap_elements={'INCREMENT'}, use_snap_project=False, snap_target='CLOSEST', use_snap_self=True, use_snap_edit=True, use_snap_nonedit=True, use_snap_selectable=False, release_confirm=True)
bpy.context.object.empty_display_size = qr_position['dimensions'][0]
qr_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}_{order_id}Tex1_qr.png")
jpg_path = os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1.jpg")
#判断是否存在 jpg_path,不存在就执行下一个任务
if not os.path.exists(jpg_path):
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
#os.system(f'blender -b -P fill_dm_code.py')
return
#判断qr_path是否存在,不存在就执行下一个任务
if not os.path.exists(qr_path):
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
os.system(f'blender -b -P fill_dm_code.py')
return
jpg_img = Image.open(jpg_path)
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_noqr.jpg"))
bpy.context.scene.eyek.res_x = jpg_img.width
bpy.context.scene.eyek.res_y = jpg_img.height
bpy.context.scene.eyek.path_export_image = qr_path
bpy.data.objects[pid_objname].select_set(True)
bpy.data.objects['Empty'].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects[pid_objname]
bpy.ops.eyek.exe()
#判断qr_path是否存在,不存在就执行下一个任务
if not os.path.exists(qr_path):
#移除该脚底板的面积处理
res = requests.get(f'https://mp.api.suwa3d.com/api/footCode/deleteByPid?pid={pid}')
#os.system(f'blender -b -P fill_dm_code.py')
return
qr_img = Image.open(qr_path)
jpg_img.paste(qr_img, (0, 0), qr_img)
jpg_img.save(jpg_path, quality=90)
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_qr.jpg"))
qr_img = Image.open(qr_path)
jpg_img.paste(qr_img, (0, 0), qr_img)
jpg_img.save(jpg_path, quality=90)
shutil.copyfile(jpg_path, os.path.join(workdir,f'{pid}_{order_id}', f"{pid}Tex1_qr.jpg"))
# 加入去灰、锐化
remove_gray_and_sharpening(jpg_path)
# 加入去灰、锐化
remove_gray_and_sharpening(jpg_path)
#上传脚底板文件 -》 修改为移动脚底板文件
upload_jpg_mtl(pid, order_id, print_id)
#上传脚底板文件 -》 修改为移动脚底板文件
upload_jpg_mtl(pid, order_id, print_id)
# plt.axis('equal')
# plt.show()
# plt.axis('equal')
# plt.show()
# 保存blend文件
# bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}_{order_id}/{pid}_qr_end.blend')
bpy.ops.wm.quit_blender()
# 保存blend文件
# bpy.ops.wm.save_as_mainfile(filepath=f'{workdir}{pid}_{order_id}/{pid}_qr_end.blend')
bpy.ops.wm.quit_blender()
# 删除临时文件
print("workdirworkdir",workdir)
shutil.rmtree(os.path.join(workdir, f'{pid}_{order_id}'))
if only_one:
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,退出程序')
break
else:
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,继续运行下一个任务')
print_id = '0'
continue
restart_current_process("blender -b -P fill_dm_code.py")
# 删除临时文件
print("workdirworkdir",workdir)
shutil.rmtree(os.path.join(workdir, f'{pid}_{order_id}'))
if only_one:
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,退出程序')
break
else:
print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())} 运行{print_id}任务完成,继续运行下一个任务')
print_id = '0'
continue
# restart_current_process("blender -b -P fill_dm_code.py")
#根据print_id 获取 短网址
def get_short_url(print_id):
@ -681,8 +656,51 @@ def upload_jpg_mtl(pid, order_id, print_id): @@ -681,8 +656,51 @@ def upload_jpg_mtl(pid, order_id, print_id):
except Exception as e:
print("迁移文件出 yichang ")
def worker(workdir, r, print_id):
"""每个子进程的工作函数,用于处理单个 print_id。"""
try:
main(workdir, r, print_id)
except Exception as e:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f"处理 print_id {print_id} 时出错: {e}")
def process_print_ids(workdir, r, pool_size):
"""监控 Redis 队列并并行处理 print_id,同时控制进程池的大小。"""
# 创建进程池
pool = multiprocessing.Pool(processes=pool_size)
while True:
try:
# 如果队列为空,等待10秒后重试
if r.llen('model:foot') == 0:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '队列为空,10秒后重试')
time.sleep(10)
continue
# 从队列中取出一个 print_id
print_id = r.lpop('model:foot')
if print_id is None:
continue
# 判断该 print_id 是否已经存在,避免重复处理
isHaveAlready = 0
for i in r.lrange('model:foot', 0, -1):
if i == print_id:
isHaveAlready = 1
break
if isHaveAlready == 1:
continue
# 如果 print_id 是唯一的,则将任务提交给进程池处理
print(f"正在处理 print_id: {print_id}")
pool.apply_async(worker, args=(workdir, r, print_id)) # 使用 apply_async 异步执行任务
except Exception as e:
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), f"出现异常错误: {e}")
r = create_redis_connection()
continue
if __name__ == '__main__':
atexit.register(common.notify,"打印工厂-本地虚拟木脚底板处理程序已停止一个")
low_y_limit = 25000
@ -714,17 +732,51 @@ if __name__ == '__main__': @@ -714,17 +732,51 @@ if __name__ == '__main__':
sourceFilePath = '/data/datasets/print'
#脚底板最终保存的路径
resFilePath = '/data/datasets/complate/objs'
print("Usage: blender -b -P fill_dm_code.py")
# 控制进程池的大小
pool_size = 2 # 设置最大并发进程数为4(根据需求调整)
# 创建一个进程池
pool = multiprocessing.Pool(processes=pool_size)
# 如果传递了参数,则处理特定的 print_ids
if len(sys.argv) == 5:
print_ids = sys.argv[-1]
for print_id in print_ids.split(','):
main(workdir, r, print_id)
else:
print_ids = '0'
print(f"print_ids--{print_ids}")
for print_id in print_ids.split(','):
main(workdir, r, print_id)
# 启动进程池,监控队列并并行处理 print_id
process_print_ids(workdir, r, pool_size)
# if len(sys.argv) == 5:
# print_ids = sys.argv[-1]
# for print_id in print_ids.split(','):
# main(workdir, r, print_id)
# else:
# while True:
# try:
# #判断队列是否为空
# if r.llen('model:foot') == 0:
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '队列为空,10秒后重试')
# time.sleep(10)
# continue
# #不为空,取出队列的第一个值
# print_id = r.lpop('model:foot')
# if print_id is None:
# continue
# #判断是否存在相同的值
# isHaveAlready = 0
# for i in r.lrange('model:foot', 0, -1):
# if i == print_id:
# isHaveAlready = 1
# break
# if isHaveAlready == 1:
# continue
# #如果是唯一值, 则调用main函数
# main(workdir, r, print_id)
# except Exception as e:
# print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), '出现异常错误,5秒后重试',e)
# r = create_redis_connection()
# continue

Loading…
Cancel
Save