addproject

This commit is contained in:
qiaoxinjiu
2026-01-22 19:10:37 +08:00
commit 6994b185a3
184 changed files with 21039 additions and 0 deletions

17
base_framework/.gitignore vendored Normal file
View File

@@ -0,0 +1,17 @@
/.idea/workspace.xml
**/pz_all_server_ip.ini
**/*run.log*
**/.__run.lock
*/.idea
**/.idea/*
**/__pycache__/*
**/*.pyc
**/results/*
**/db_config.ini/*
*.pyc
**/interface.txt
**/case_result/*
*.project
*.txt
*.json
**/user.txt

View File

View File

@@ -0,0 +1,18 @@
# coding=utf-8
# QA环境
[QA]
zhyy = https://smart-management-api-dev.best-envision.com/admin-api
zhyy_login = https://smart-management-api-dev.best-envision.com/admin-api/system/auth/login
ZZYY = https://smart-management-api-dev.best-envision.com/admin-api
# uat环境
[UAT]
zhyy = https://smart-management-api-pre.best-envision.com/admin-api/
zhyy_login = https://smart-management-api-pre.best-envision.com/admin-api/system/auth/login
[team_user_list]
ASTWB_owner = 18202810506
谯新久 = 18202810506
ASTWB_to_dd = 2079cb3e311ab6a37138a6d4671181661d211c12a1532c2012ae7e6b397996c3

View File

@@ -0,0 +1,33 @@
# coding=utf-8
[PostgreSQL]
db_test_host = 39.170.26.156
db_test_port = 8566
db_test_dbname = smart_management_st
db_test_user = sm_test_user
db_test_password = Test@736141
db_charset = utf8
db_min_cached = 10
db_max_cached = 20
db_max_shared = 20
db_max_connecyions = 100
db_blocking = True
db_max_usage = 5
db_set_session = None
[jwadmin]
tenant = 境外租户空间
show_username = 超级管理员
username = jwadmin
password = 1qaz@WSX
[purchase]
tenant = 境内组织
show_username = 孙丽萍
username = sunliping01
password = 1qaz@WSX
[supply]
tenant = 境内组织
show_username = 东莞艾斯保健用品有限公司
username = SU00014
password = 1qaz#WSX

View File

@@ -0,0 +1,19 @@
# coding=utf-8
[jwadmin]
tenant = 境外租户空间
show_username = 超级管理员
username = jwadmin
password = 1qaz@WSX
[purchase]
tenant = 境内组织
show_username = 孙丽萍
username = sunliping01
password = 1qaz@WSX
[supply]
tenant = 境内组织
show_username = 东莞艾斯保健用品有限公司
username = SU00014
password = 1qaz#WSX

View File

@@ -0,0 +1,31 @@
# coding=utf-8
import os
import configparser
# 配置文件路径
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT_PATH = os.path.abspath(os.path.join(HERE, '../../'))
ASTWB_PATH = os.path.abspath(os.path.join(HERE, '../../ASTWB/library/Config/'))
env_choose_path = os.path.join(HERE, 'env_choose.ini')
config_choose_path = os.path.join(HERE, 'config.ini')
la_config_path = os.path.join(HERE, '../platform_tools/jira_tools/la_config.ini')
config_evn_path = os.path.join(HERE, 'current_pth.py')
server_ip_path = os.path.join(HERE, 'server_ip.ini')
log_path = os.path.join(ROOT_PATH, 'Log')
# 根据启动参数来加载对应的配置文件
cof = configparser.ConfigParser()
cof.read(env_choose_path)
current_business = cof['run_evn_name']['current_business'].lower()
current_evn = cof['run_evn_name']['current_evn'].lower()
config_file_path = os.path.join(HERE, 'config_{0}_{1}.ini'.format(current_business, current_evn))
db_config_path = config_file_path
pz_all_server_ip_path = os.path.join(HERE, 'pz_all_server_ip.ini')
swagger_choose_path = os.path.join(HERE, 'swagger_url.ini')
astwb_config = os.path.join(ASTWB_PATH, 'team_config.ini')
uat_config_path = os.path.abspath(os.path.join(HERE, 'uat_config'))
if __name__ == '__main__':
print(config_file_path)

View File

@@ -0,0 +1,45 @@
[Mysql]
db_test_host = mysql.qa.huohua.cn
db_all_school_test_host = qa-my-asc.mysql.rds.aliyuncs.com
db_hhi_test_host = qa-my-hhi.mysql.rds.aliyuncs.com
db_test_port = 3306
db_test_dbname = crmthirdparty
db_all_school_test_dbname = hulk_content_audit
db_hhi_test_dbname = hhi_account
db_test_user = qa-dev
db_test_password = jaeg3SCQt0
db_charset = utf8
db_min_cached = 10
db_max_cached = 20
db_max_shared = 20
db_max_connecyions = 100
db_blocking = True
db_max_usage = 5
db_set_session = None
[MONGODB]
mongo_host = 10.250.100.106
mongo_port = 20007
mongo_user = hulk_teach_marketing_rw
mongo_passowrd = MmEzZmqatest
[PgSql]
db_test_host = mysql.qa.huohua.cn
db_all_school_test_host = qa-my-asc.mysql.rds.aliyuncs.com
db_hhi_test_host = qa-my-hhi.mysql.rds.aliyuncs.com
db_test_port = 3306
db_test_dbname = crmthirdparty
db_all_school_test_dbname = hulk_content_audit
db_hhi_test_dbname = hhi_account
db_test_user = qa-dev
db_test_password = jaeg3SCQt0
db_charset = utf8
db_min_cached = 10
db_max_cached = 20
db_max_shared = 20
db_max_connecyions = 100
db_blocking = True
db_max_usage = 5
db_set_session = None

View File

@@ -0,0 +1,14 @@
[run_evn_name]
current_business = hh
current_evn = qa
current_team = ZZYY
[run_jira_id]
huohua-podenv = qa
[is_ip_from_ini]
is_ip_from_ini = false
[run_user_name]
default_user = jwadmin

View File

@@ -0,0 +1,2 @@
[ZZYY]
zhyy = http://39.170.26.156:8380/swagger-ui/index.html#/

View File

@@ -0,0 +1 @@
2026-01-22 11:35:43,802 - database - ERROR - <20><><EFBFBD>ݿ<EFBFBD><DDBF><EFBFBD><EFBFBD>ӳس<D3B3>ʼ<EFBFBD><CABC>ʧ<EFBFBD><CAA7>: δ<>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD>·<EFBFBD><C2B7>: ['config/database.ini', '../config/database.ini', '../../config/database.ini', WindowsPath('C:/Users/hasee/.config/database.ini')]

33
base_framework/main.py Normal file
View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from robotremoteserver import RobotRemoteServer
from library import KwLibrary
import platform
import os
import signal
def main():
RobotRemoteServer(library=KwLibrary(), host='0.0.0.0', port=9999)
def kill_pid():
"""
功能:杀掉旧的进程
"""
sys = platform.system()
if sys == "Windows":
with os.popen('netstat -aon|findstr "9999"') as res:
res = res.read().split('\n')
result = []
for line in res:
temp = [i for i in line.split(' ') if i != '']
if len(temp) > 4 and temp[1] == "0.0.0.0:9999":
result.append(
{'pid': temp[4], 'address': temp[1], 'state': temp[3]})
os.popen("taskkill -pid {} -f".format(temp[4]))
else:
pid = os.popen(
"sudo -i netstat -nlp | grep :9999 | awk '{print $7}' | awk -F '/' '{ print $1 }'",
'r',
1).read().strip('\n')
if pid != "":
os.kill(int(pid), signal.SIGKILL)

View File

@@ -0,0 +1,55 @@
# -*- coding:utf-8 -*-
# 检查jenkins上的用例构建结果并根据用例失败数量将结果设置为通过或失败
import sys
import os
HERE = os.path.dirname(os.path.abspath(__file__))
WORKSPACE = os.path.abspath(os.path.join(HERE, '../../../'))
sys.path.append(WORKSPACE)
from base_framework.public_tools.xml_file_api import XmlFileApi
PROJECT_NAME = sys.argv[0]
# 指定 Robot Framework 输出文件的路径
HERE = os.path.dirname(os.path.abspath(__file__))
output_file = os.path.abspath(os.path.join(HERE, '../../../Report/out/output.xml'))
# 解析输出文件, 获取失败用例数量
xf = XmlFileApi(file_path=output_file)
result = xf.get_contain_by_tag_names(tag_names='statistics/total/stat')
failed_count = int(result[0]['attrib']['fail'])
# 按失败用例对应的qa人员标签,
if failed_count > 0:
result = xf.get_contain_by_tag_names(tag_names='statistics/tag/stat')
qa_info = {}
qa_failed_case_number = 0
for item in result:
if 'qa-' in item['text'].lower():
fail_case = int(item['attrib']['fail'])
if fail_case > 0:
qa_info[item['text'][3:]] = fail_case
qa_failed_case_number += fail_case
if len(qa_info) > 0:
print("失败用例对应的QA人员及失败用例数量:")
for key, value in qa_info.items():
print(f"{key}: {value}")
if failed_count > qa_failed_case_number:
print(f"无qa标签{failed_count - qa_failed_case_number}")
# 根据失败用例数量设置构建结果
if "-IT" in PROJECT_NAME: # it用例失败用例大于5个就标红
if failed_count > 5:
print(f"测试用例执行失败,共有 {failed_count} 个失败的用例。")
sys.exit(1) # 退出码为1表示构建失败构建结果将展示为红色
elif failed_count > 0:
print("测试用例小范围失败,但是不影响整体结果。")
sys.exit(0) # 退出码为0表示构建成功但此时受jenkins构建后步骤限制将展示为黄色
else:
print("测试用例执行成功,所有用例都通过了。")
sys.exit(0) # 退出码为0表示构建成功构建结果将展示为绿色
else: # st和smoking有失败用例就标红
if failed_count > 0:
print(f"测试用例执行失败,共有 {failed_count} 个失败的用例。")
sys.exit(1) # 退出码为1表示构建失败构建结果将展示为红色
else:
print("测试用例执行成功,所有用例都通过了。")
sys.exit(0) # 退出码为0表示构建成功构建结果将展示为绿色

View File

@@ -0,0 +1,68 @@
# coding: utf-8
from base_framework.public_tools.selenium_api import SeleniumWebUI
import time
class JenkinsAPI(SeleniumWebUI):
def __init__(self):
SeleniumWebUI.__init__(self)
self.project_info = {}
def jenkins_login(self, u_name=None, u_pwd=None):
""" 登陆jenkins """
if not u_name:
u_name = "admin"
u_pwd = "admin"
login_url = "http://39.170.26.156:8256/jenkins/login?from=%2Fjenkins%2F"
self.web_open_url(url=login_url)
self.web_send_keys(locator="//*[@id=\"j_username\"]", text=u_name)
self.web_send_keys(locator="//*[@name=\"j_password\"]", text=u_pwd)
self.web_click_element(locator="//*[@name=\"Submit\"]")
time.sleep(1) # 切换到构建墙
self.web_click_element(locator="//*[text()='1-用户产品组']")
time.sleep(1) # 显示用例数量
self.web_click_element(locator="//*[@title=\"Configure Build Monitor Settings\"]")
time.sleep(1)
self.web_click_checkbox(locator="//*[@id=\"settings-show-test-result\"]")
self.web_click_checkbox(locator="//button[text()=\"Done\"]")
def jenkins_find_all_case_number(self):
""" 从构建墙上读取用例数量 """
elements = self.web_find_elements(locator="//li[@ng-repeat=\"project in jobs track by project.hashCode\"]")
for element in elements:
item = element.text.split('\n')
case_number = item[1].split('/')
self.project_info[item[0]] = case_number[1]
def jenkins_edit_project_config(self, **kwargs):
""" 编辑工程配置信息 """
for jp in self.project_info:
case_number = int(self.project_info[jp])
if jp == "TO-IT":
jp = "TO-IT-TEST"
elif jp == "TO-SMOKING":
jp = "TO-SMOKING_NEW"
project_url = "http://10.250.200.1:8080/jenkins/view/1-%E7%94%A8%E6%88%B7%E4%BA%A7%E5%93%81%E7%BB%84/" \
"job/{}/configure".format(jp)
if "SMOKING" in jp or "-ST" in jp:
threshold = 99.999999 # 冒烟和ST用例需全部构建成功
else:
threshold = round((1-5.5/case_number)*100, 4) # 阈值
self.web_open_url(url=project_url) # 进入设置页面
self.web_send_keys(locator="//input[@name=\"_.unstableThreshold\"]", text=str(threshold))
self.web_click_checkbox(locator="//button[text()=\"保存\"]")
print("{}设置阈值{}完毕....".format(jp, threshold))
time.sleep(1)
if __name__ == '__main__':
ja = JenkinsAPI()
ja.jenkins_login()
time.sleep(3)
ja.jenkins_find_all_case_number()
ja.jenkins_edit_project_config()
time.sleep(3)
ja.web_close_browser()

View File

@@ -0,0 +1 @@
special_team = ["EN", "PZ", "ASTOP", "ASOPE", "ASTWB", "ASORG", "UBRD", "UBJ", "ES", "ASTEA", 'GUE', "TMO", "PB"]

View File

@@ -0,0 +1,684 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import importlib.util as i_util
import inspect
import json
import os
import re
import sys
import traceback
from base_framework.platform_tools.Keywords_service import special_team
token_false_server = ["scm-server", "scm-biz-server", "peppa-qi-api", "lggzt", "cti-manage", "ccwx-api", "peppa-la-core-server"]
add_api_list = ["peppa-teach-api"]
host_servers = ["HULK-ORG-API", "HULK-TEACHER-API", "PEPPA-LA-CORE-SERVER"]
host_servers_except_api = ["HULK-CONTENT-AUDIT-SERVER"]
header_servers = ["hulk-operation-api-server", "hulk-teach-supply-cli-api", "hulk-teach-backend-api"]
uid_server = ['SPARKEDU-API', 'SPARKEDU-SITE-API', 'HUOHUA-SERVICE-API']
eid_server = ['SPARKEDU-SITE-MANAGE', 'SPARKEDU-SITE-SCHEDULER']
def check_server_host(servers, host):
"""
检查不需要token的server是否存在url中
"""
for server in servers:
if server in host:
return True
else:
return False
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.sqlhelper import MySqLHelper
db = MySqLHelper()
log = get_logger()
def get_classes(module):
classes = []
cls_members = inspect.getmembers(module, inspect.isclass)
for (name, _) in cls_members:
classes.append(name)
return classes
def get_module_by_file_path(file_path):
spec = importlib.util.spec_from_file_location("module_name", file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class SetAttr(object):
def __init__(self, paras):
for key, values in paras.items():
self._add_attr(key, values)
def _add_attr(self, key, value):
self.__setattr__(key, value)
class SwaggerDBInfo(object):
def __init__(self):
pass
@staticmethod
def get_team_swagger_info_by_team(teamname):
sql = "SELECT * FROM sparkatp.swagger_info where team = '%s'" % teamname
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_team_swagger_info_by_id(swagger_id):
sql = "SELECT * FROM sparkatp.swagger_info where id = '%s'" % swagger_id
return db.select_one(sql, choose_db="huohua")
@staticmethod
def get_interface_info_by_url_and_type(request_url, request_type, interface_id):
sql = '''select * from sparkatp.interface_info where in_url = "%s" and type = "%s" and
id = %s''' % (request_url, request_type, interface_id)
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_interface_parameters_by_interface_id(interface_id):
sql = """select * from sparkatp.request_parameters a right join sparkatp.parameters_relation b on
a.id = b.parameter_id where a.interface_id = {} and a.is_need != 2 and b.type = 'request'""".format(
interface_id)
return db.select_all(sql=sql, choose_db="huohua")
@staticmethod
def get_interface_request_demo_info_by_interface_id(interface_id):
sql = 'SELECT * FROM sparkatp.parameters_demo where in_id = "%s" order by id DESC' % interface_id
return db.select_all(sql, choose_db="huohua")
class KWFileOperation(object):
"""
操作xx_interface.py文件
"""
def __init__(self, team):
self.kw_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
self.all_keywords = []
self.all_url = {}
self.kw_name_with_url = {}
self.runner = self._get_obj_runner()
self.team = team
self.kw_class = self._get_interface_class_name(self.kw_file_path)
def _clean_demo_to_less_key(self, demo):
demo_result = copy.deepcopy(demo)
for key, value in demo.items():
if not value:
demo_result.pop(key)
lenth = len(demo_result)
paras = "kwargs"
if lenth == 1 and isinstance(list(demo_result.values())[0], list):
paras = "args"
return lenth, demo_result, paras
def _get_interface_class_name(self, file_path):
module = get_module_by_file_path(file_path)
classes = get_classes(module)
for class_ in classes:
if "interface" in class_.lower() and class_.lower().startswith(self.team.lower()):
return class_
def _get_obj_runner(self):
spec = i_util.spec_from_file_location("module_name", self.kw_file_path)
module = i_util.module_from_spec(spec)
spec.loader.exec_module(module)
runner = inspect.getmembers(module)
for module in runner:
if module[0] == "obj_runner":
return module[1]
return runner
def _get_file_content(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as f:
for line in f:
yield line.strip("\n")
def re_write_all_keyword_to_py_file(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
index = len(all_content)
with open(self.kw_file_path, "r", encoding="UTF-8") as file1:
for line in file1:
if "if __name__" in line and "__main__" in line:
index = all_content.index(line)
break
for i in range(index - 1, 0, -1):
s = all_content[i]
if all_content[i] != "\n":
index = i
all_content.insert(i + 1, "\n")
break
a = all_content[i + 1]
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
file.writelines(all_content[:index + 1])
def get_all_exist_keywords(self):
for item in self._get_file_content():
kw_match = re.search(r"def\s+(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def get_all_exist_keywords_info(self):
for item in self._get_file_content():
kw_match = re.search(r"(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def write_content_to_interface_file(self, content, file_path=None):
if not file_path:
file_path = self.kw_file_path
with open(file_path, "a+", encoding="UTF-8") as f:
f.write(content)
def generate_all_keyword_info(self):
"""
获取interface文件内已有关键字的url请求地址和请求方法
"""
temp_dict = {}
all_content = self._get_file_content()
server = ""
for line in all_content:
line_new = line
if "def kw_in_" in line:
kw_name = re.search("(kw_in_[\w+_]+)\(", line).group(1)
if "tools.get_container_ip_from_eureka" in line or "tool.get_container_ip_from_eureka" in line:
server = list(re.search("\([\"\']?([\w\-_]+)[\"\']?|\=[\"\']?([\w\-_]+)[\"\']?", line).groups())
server.remove(None)
server = server[0]
if "self." in line and "=" in line:
temp_dict[line.split("=")[0].strip()] = line.split("=")[1].strip().strip("\"")
if " url =" in line or "url=" in line:
log.info(line)
try:
# urls = list(re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line).groups())
match = re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line)
urls = list(match.groups()) if match else []
except Exception as err:
traceback.print_exc()
continue
# urls.remove(None)
urls = [url for url in urls if url is not None]
if not urls:
continue
url = urls[0]
if "\\" in line:
line_new = all_content.__next__()
try:
host = re.search("obj_runner\.([\w\-_]+)", line_new).group(1).strip().strip(")").strip(",")
except:
if "self." in line:
if self.team.upper() in ["EN", "ASTWB", "ASOPE"]:
host_header = "https://"
else:
host_header = "http://"
server = re.search("self\.([\w\-_]+)", line_new).group(1).strip().strip(")").strip(",")
server = server.replace("_", "-").lower()
if self.team.upper() == 'TMO':
host = host_header + "%s" % 'swagger' + ".qa.huohua.cn"
elif server.upper() in host_servers:
if server.upper() == "HULK-ORG-API":
host = host_header + "%s" % server + ".qa.huohua.cn"
else:
host = host_header + "%s" % "api" + ".qa.allschool.com"
else:
if server.upper() in host_servers_except_api:
host = host_header + "%s" % server + ".qa.allschool.com"
else:
host = host_header + "%s" % server + ".qa.huohua.cn"
if self.team.upper() == "TO":
host = "teach_opt_host"
if re.search("\{\}/\{\}", line) and "self." in line:
para = re.search("(self\.[\w_]+)", line).group(1).strip().strip(")").strip(",")
url = temp_dict[para] + urls[0]
if "?" in url:
url = url.split("?")[0]
# if url.endswith("/"):
# url = url[:len(url) - 1]
if server:
if self.team.upper() in special_team and self.team.upper() != 'TMO':
if url.startswith("/"):
url = host + "%s" % url
else:
url = host + "/%s" % url
elif self.team.upper() == 'TMO':
if url.startswith("/"):
url = '%s/%s%s' % (host, server, url)
else:
url = '%s/%s/%s' % (host, server, url)
else:
try:
if url.startswith("/"):
url = eval("self.runner.%s" % host) + "/%s" % server + url
else:
url = eval("self.runner.%s" % host) + "/%s/" % server + url
except Exception as err:
url = "empty_url"
else:
try:
if url.startswith("/"):
url1 = eval("self.runner.%s" % host)
url = url1 + url
else:
url = eval("self.runner.%s" % host) + url
except Exception as err:
url = "empty_url"
if "req_type=" in line:
method = re.search('req_type="(\w+)"|req_type=\'(\w+)\'', line.replace("\'", "\"")).group(1)
self.all_url[url] = [method, kw_name]
server = ""
return self.all_url
class KWOperation(object):
"""
根据数据库内容生成对应的keywords
"""
def __init__(self, team, kw_instance, server=None):
self.team = team
self.doc_string_table_content_temp = " |{}|{}|{}|{}|{}|"
self.function_name = ""
self.help_doc = None
self.function_content = None
self.has_kw_list = []
self.function_list = []
self.file = kw_instance
self.all_keywords = self.file.get_all_exist_keywords()
self.all_url = self.file.generate_all_keyword_info()
self.kw_name = None
self.kw_string = None
self.kw_array = None
self.server = server
def get_interface_demo(self, interface_id):
return SwaggerDBInfo.get_interface_request_demo_info_by_interface_id(interface_id)
def get_interface_parameters(self, interface_id):
return SwaggerDBInfo.get_interface_parameters_by_interface_id(interface_id)
def get_swagger_info_by_id(self, swagger_id):
return SwaggerDBInfo.get_team_swagger_info_by_id(swagger_id)
def _check_kw_exist(self, keywordname):
if keywordname in self.all_keywords:
return False
else:
return True
def generate_kw_name(self, interface_info, demo):
"""
生成对应关键字方法名称如kw_in_to_get_leads_get(**kwargs)
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
_, name_list, _, _ = self._generate_all_url_paramaters(interface_info.in_url)
if "" in name_list:
name_list.remove("")
name_list.append(interface_info.name)
for item in add_api_list:
if item in interface_info.in_url:
name_list[-1] = 'api_' + name_list[-1]
count = -1
self.kw_name = "kw_in_{}_{}_{}".format(self.team.lower(), name_list[-1], interface_info.type.lower())
while abs(count) <= len(name_list):
if self._check_kw_exist(self.kw_name):
if lenth > 1:
self.function_name = " def {kw_name}(self, **kwargs):".format(kw_name=self.kw_name)
else:
if self.team.upper() in ["TMO"]:
self.function_name = " def {kw_name}(self, **kwargs):".format(kw_name=self.kw_name)
else:
if demo_result:
if isinstance(list(demo_result.values())[0], list):
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, **kwargs):".format(
kw_name=self.kw_name)
self.has_kw_list.append(interface_info.id)
self.all_keywords.append(self.kw_name)
return True
else:
count -= 1
self.kw_name += "_" + name_list[count]
else:
self.has_kw_list.append(interface_info.id)
return False
def generate_kw_doc(self, interface_info, parameters_list):
"""
根据接口参数信息生成对应的帮助文档
"""
def set_lenth(para):
return " " + str(para).strip() + " "
def get_normal_value(normal_values):
return normal_values.split(",")[0] if normal_values else 0
doc = []
doc_string_head = " \"\"\""
doc_string_table_head = " | {} | {} | {} | {} | {} |".format("请求参数名".ljust(30), "说明".ljust(15),
"类型".ljust(12), "是否必填".ljust(8),
"如无要求时的值".ljust(8))
doc.append(doc_string_head)
doc.append(" | 功能说明:| {} |".format(interface_info.interface_describe))
doc.append(doc_string_table_head)
# if self.team.upper() not in ["TMO"]:
# doc.append(self.doc_string_table_content_temp.format(set_lenth("is_check"),
# set_lenth("is_check默认空不校验返回有值就校验返回"),
# set_lenth("string"),
# set_lenth("业务case的时候需要传入值"),
# set_lenth("False")))
for parameter in parameters_list:
attr = SetAttr(parameter)
if int(attr.in_body) != 0 and int(attr.is_need) != 2:
doc.append(self.doc_string_table_content_temp.format(set_lenth(attr.name), set_lenth(attr.note),
set_lenth(attr.type), set_lenth(attr.is_need),
set_lenth(get_normal_value(attr.normal_values))))
doc.append(doc_string_head)
return "\n".join(doc)
def _generate_all_url_paramaters(self, url):
"""
拆解url生成host, 生成keyword名的相关名称和url中带{}的参数
"""
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
if self.team.upper() in ["TMO"]:
search.pop(0)
url_lasr_temp = url_last.split("/")
url_lasr_temp.pop(1)
url_last = '/'.join(url_lasr_temp)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return host, head_list, para_list, url_last
def _generate_url_host(self, host):
"""
根据host生成对应的url后缀比如teach_opt_host
如果需要将host替换成IP则生成对应的IP
"""
url_host = ""
if 'peppa-cc-manage' in host:
url_host = "cc_host"
elif 'crmv2' in host or 'smm' in host or 'xxljob' in host:
url_host = "crm_host"
elif 'scm' in host:
url_host = "scm_host"
elif "la-gate" in host:
url_host = "insights_host"
elif 'la-ai-api-bg' in host or 'la-api-ai' in host:
url_host = "spark_land_host"
elif 'manage' in host:
url_host = "manage_host"
elif 'teach' in host:
url_host = "teach_host"
elif 'teach-opt-api' in host or 'sparkle-manage' in host:
url_host = "teach_opt_host"
elif 'opengalaxy' in host:
url_host = "opengalaxy_host"
elif 'employee-manage' in host:
url_host = "ehr_host"
elif 'peppa-agent-manage' in host:
url_host = "hhr_host"
elif 'peppa-agent-api' in host:
url_host = "hhr_api_host"
elif "teach-message-api" in host:
url_host = "teacher_message_host"
elif "swagger" in host:
url_host = "swagger_host"
elif "peppa-qi-api" in host:
url_host = "qi_host"
elif "scm-server" in host:
url_host = "scm_server_host"
elif "scm-biz-server" in host:
url_host = "scm_biz_server_host"
elif "cti-manage" in host:
url_host = "cti_host"
elif "peppa-conversion-api" in host:
url_host = "uc_host"
elif "la-api" in host:
url_host = "la_api_host"
elif "hulk_content_audit_server" in host:
url_host = "hulk_content_audit"
elif "ccwx-api" in host:
url_host = "cc_weixin_host"
return url_host
def generate_function_url(self, interface_info, demo):
"""
生成对应的url
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
url_list = []
user_kwargs = " user, kwargs = get_user(kwargs)"
url_list.append(user_kwargs)
check_json = " kwargs = convert_json(kwargs)"
if paras_d == "args":
check_json = " args = convert_json(args)"
url_list.append(check_json)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
host = re.findall("[\w+-]+", host)
url_host = self._generate_url_host(host)
server = ""
# if self.team.upper() in ["TMO"]:
# url_list.append(" try:\n kwargs = eval(args[0])\n except:")
# url_list.append(" try:\n if args[0] and isinstance(args[0], dict):")
# url_list.append("kwargs = args[0]".rjust(20 + len("kwargs = args[0]"), " "))
# url_list.append("except:".rjust(12 + len("except:"), " "))
# url_list.append("kwargs = kwargs".rjust(16 + len("kwargs = kwargs"), " "))
# url_last_tmp = url_last.split("/")
# server = url_last_tmp.pop(1)
# url_last = "/".join(url_last_tmp)
# ip = " ip = tools.get_container_ip_from_eureka(\"%s\", need_jira_id=True)" % server
# url_list.append(ip)
# host = " obj_runner." + url_host + " = \"http://\" + " + "ip[\"container_ip\"]" + " + \":8080\""
# url_list.append(host)
if self.team.upper() in special_team:
if self.server.upper() in host_servers or self.team.upper() in ["TMO"]:
host_server = self.server.lower().replace("-", "_")
else:
host_server = host[1].replace("-", "_")
url_host = "self.%s" % host_server
else:
if self.server.upper() in host_servers or self.team.upper() in ["TMO"]:
host_server = self.server.lower().replace("-", "_")
url_host = "self.%s" % host_server
# if self.team.upper() == 'LALIVE' and 'la-api' in host:
# url_last = url_last.replace("smart", "api/smart")
if "PEPPA-QI-API".lower() in host:
ip = " obj_runner.%s = \"http://{}:8080\".format(self.get_container_ip('PEPPA-QI-API'))" % url_host
url_list.append(ip)
if parameters:
if lenth > 1:
temp = 'kwargs.get("u")'
else:
temp = 'kwargs'
url = " url = \"%s" + url_last + "\".format("
for item in parameters:
url += "%s=%s, " % (item, "%s.get(\"%s\", \"\")" % (temp, item))
url += ") % "
else:
url = " url = \"%s" + url_last + "\" % "
if self.team.upper() in special_team or self.server.upper() in host_servers:
url += url_host
else:
url += "obj_runner.%s" % url_host
url_list.append(url)
return "\n".join(url_list), server
def generate_function_body(self, interface_info, demo, parameters_all):
"""
生成关键字的body内容
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
func_body = []
if re.search(r"\*(\w+)", self.function_name):
paras = re.search(r"\*(\w+)", self.function_name).group(1)
if self.team.upper() in ["TMO"]:
paras = "kwargs"
content = ' obj_log.info("your input:{0}".format(%s))' % paras
func_body.append(content)
if self.kw_string:
func_body.append(" kwargs = kwargs.get('%s', '')" % parameters_all[0].get("name"))
if self.kw_array:
func_body.append(" kwargs = eval(kwargs.pop('%s'))" % parameters_all[0].get("name"))
else:
paras = None
if lenth > 1:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", ' % interface_info.type
if "d" in demo_result:
resp = resp + 'json=kwargs.get("d"), '
if "p" in demo_result:
resp = resp + 'params=kwargs.get("p"), '
if "h" in demo_result:
resp = resp + 'headers=kwargs.get("h"), '
resp = resp + 'user=user)'
else:
if demo_result:
if "d" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", json=%s, user=user)' % (
interface_info.type, paras)
elif "h" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", headers=%s, user=user)' % (
interface_info.type, paras)
elif "p" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", params=%s, user=user)' % (
interface_info.type, paras)
elif "u" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
else:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
if self.team.upper() in special_team or check_server_host(token_false_server, host) or \
self.team.upper() in ["TMO"]:
resp = resp.replace("user=user", "token=False")
try:
if self.server.upper() in host_servers:
resp = resp.replace("token=False", "token=False, user=user")
if self.server.upper() == "HULK-ORG-API":
resp = resp.replace("user=user", "user=user, as_login_type=2")
if self.server.upper() in host_servers_except_api:
resp = resp.replace("token=False", "")
except:
pass
if str(self.server).lower() in header_servers:
header = " header = {'debug-param': 'huangliye@huohua.cn'}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'huangliye@huohua.cn'})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in eid_server:
header = " header = {'debug-param': 'eid:%s' % self.eid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'eid:%s' % self.eid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in uid_server:
header = " header = {'debug-param': 'uid:%s' % self.uid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'uid:%s' % self.uid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
func_body.append(resp)
# if self.team.upper() not in ["TMO"] and paras_d == "kwargs":
# func_body.append(" check_resp(is_check, resp)")
func_body.append(" return resp\n")
return "\n".join(func_body)
def get_has_keyword_id_in_db(self):
"""
获取所有已有
"""
keyword_list = []
for key in self.all_url.keys():
interface_name = key.split("/")[-1]
sql = "select * from sparkatp.interface_info where name = '%s' and type = '%s'" % (
interface_name, self.all_url[key][0])
interface_infos = SwaggerDBInfo.get_db_info_by_sql(sql=sql)
for interface_info in interface_infos:
interface_attr = SetAttr(interface_info)
if key.lower() in interface_attr.in_url.lower():
keyword_list.append(interface_attr.id)
return keyword_list
def run_keyword_generage(result_path, interface, demo_info, parameters, keyword):
if demo_info.request_demo:
demo_info = json.loads(demo_info.request_demo)
else:
demo_info = {}
if keyword.all_url.get(interface.in_url) and keyword.all_url.get(interface.in_url)[
0].lower() == interface.type.lower():
msg = "接口url%s,%s,已存在关键字,继续生成用例!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return True
else:
exist = keyword.generate_kw_name(interface, demo_info)
if exist:
keyword.function_list = []
doc = keyword.generate_kw_doc(interface, parameters)
url, temp = keyword.generate_function_url(interface, demo_info)
content = keyword.generate_function_body(interface, demo_info, parameters)
keyword.function_list.append("\n".join(("", keyword.function_name, doc, url, content)))
keyword.file.re_write_all_keyword_to_py_file()
keyword.file.write_content_to_interface_file("\n".join(keyword.function_list))
keyword.file.all_url[interface.in_url] = [interface.type, keyword.kw_name]
return True
else:
msg = "接口url%s,%s,无法生成关键字,请检查!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return False

View File

@@ -0,0 +1,666 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import importlib.util as i_util
import inspect
import json
import os
import re
import sys
import traceback
from base_framework.platform_tools.Keywords_service import special_team
token_false_server = ["scm-server", "scm-biz-server", "peppa-qi-api", "lggzt", "cti-manage", "ccwx-api"]
add_api_list = ["peppa-teach-api"]
host_servers = ["HULK-ORG-API", "HULK-TEACHER-API"]
host_servers_except_api = ["HULK-CONTENT-AUDIT-SERVER"]
header_servers = ["hulk-operation-api-server", "hulk-teach-supply-cli-api", "hulk-teach-backend-api"]
uid_server = ['SPARKEDU-API', 'SPARKEDU-SITE-API']
eid_server = ['SPARKEDU-SITE-MANAGE', 'SPARKEDU-SITE-SCHEDULER']
def check_server_host(servers, host):
"""
检查不需要token的server是否存在url中
"""
for server in servers:
if server in host:
return True
else:
return False
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.sqlhelper import MySqLHelper
db = MySqLHelper()
log = get_logger()
def get_classes(module):
classes = []
cls_members = inspect.getmembers(module, inspect.isclass)
for (name, _) in cls_members:
classes.append(name)
return classes
def get_module_by_file_path(file_path):
spec = importlib.util.spec_from_file_location("module_name", file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class SetAttr(object):
def __init__(self, paras):
for key, values in paras.items():
self._add_attr(key, values)
def _add_attr(self, key, value):
self.__setattr__(key, value)
class SwaggerDBInfo(object):
def __init__(self):
pass
@staticmethod
def get_team_swagger_info_by_team(teamname):
sql = "SELECT * FROM sparkatp.swagger_info where team = '%s'" % teamname
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_team_swagger_info_by_id(swagger_id):
sql = "SELECT * FROM sparkatp.swagger_info where id = '%s'" % swagger_id
return db.select_one(sql, choose_db="huohua")
@staticmethod
def get_interface_info_by_url_and_type(request_url, request_type, team):
sql = 'select * from sparkatp.interface_info where in_url = "%s" and type = "%s" and swagger_id in (select id from sparkatp.swagger_info where team = "%s")' % (request_url, request_type, team)
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_interface_parameters_by_interface_id(interface_id):
sql = "select * from sparkatp.request_parameters a right join sparkatp.parameters_relation b on a.id = b.parameter_id where a.interface_id = {} and b.type = 'request' and a.offline=0".format(
interface_id)
return db.select_all(sql=sql, choose_db="huohua")
@staticmethod
def get_interface_request_demo_info_by_interface_id(interface_id):
sql = 'SELECT * FROM sparkatp.parameters_demo where in_id = "%s" order by id DESC' % interface_id
return db.select_all(sql, choose_db="huohua")
@staticmethod
def get_swagger_url_by_team_and_server(team, server):
sql = "select * from sparkatp.swagger_info where team='%s' and server_name = '%s';" % (team, server)
return db.select_one(sql, choose_db="huohua")
class KWFileOperation(object):
"""
操作xx_interface.py文件
"""
def __init__(self, team, server):
self.kw_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
self.team = team
self.server = server
self.all_url = {}
self.all_keywords = []
self.runner = self._get_obj_runner()
self.file_content = self.re_write_all_keyword_to_py_file()
self.check_server_and_rewrite_server()
self.get_all_exist_keywords()
self.generate_all_keyword_info()
self.kw_class = self._get_interface_class_name(self.kw_file_path)
def check_server_and_rewrite_server(self):
"""
检查server是否在interface.py文件中配置获取IP
"""
start_index = 0
empty_index = 0
server_host = self.server.lower().replace('-', '_')
server_upper = self.server.upper()
if "self.%s =" % server_host in ''.join(self.file_content) or '':
return
for line in self.file_content:
if "def __init__(self):" in line:
start_index = self.file_content.index(line) + 1
log.info(start_index)
if start_index == 0:
continue
if " def " in line and "__init__" not in line:
end_index = self.file_content.index(line)
log.info(end_index)
break
if "pass" in self.file_content[start_index]:
self.file_content[
start_index] = ' self.need_jira_id = True if ReadConfig(env_choose_path).get_value("run_jira_id", "huohua-podenv") else False\n'
elif 'self.need_jira_id = True if ' not in self.file_content[start_index] and "pass" in self.file_content[
start_index]:
self.file_content.insert(start_index,
' self.need_jira_id = True if ReadConfig(env_choose_path).get_value("run_jira_id", "huohua-podenv") else False\n')
for item in range(start_index, end_index):
if self.file_content[item] == "\n":
self.file_content.insert(item,
" self.%s_ip = obj_tool.get_container_ip_from_eureka('%s', need_jira_id=self.need_jira_id)\n" % (
server_host, server_upper))
self.file_content.insert(item + 1,
' self.%s = "http://" + self.%s_ip["container_ip"] + ":8080"\n' % (
server_host, server_host))
break
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
for line1 in self.file_content:
file.writelines(line1)
def _clean_demo_to_less_key(self, demo):
demo_result = copy.deepcopy(demo)
for key, value in demo.items():
if not value:
demo_result.pop(key)
lenth = len(demo_result)
paras = "kwargs"
if lenth == 1 and isinstance(list(demo_result.values())[0], list):
paras = "args"
return lenth, demo_result, paras
def _get_interface_class_name(self, file_path):
module = get_module_by_file_path(file_path)
classes = get_classes(module)
for class_ in classes:
if "interface" in class_.lower():
return class_
def _get_obj_runner(self):
spec = i_util.spec_from_file_location("module_name", self.kw_file_path)
module = i_util.module_from_spec(spec)
spec.loader.exec_module(module)
runner = inspect.getmembers(module)
for module in runner:
if module[0] == "obj_runner":
return module[1]
return runner
def _get_file_content(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
return all_content
def re_write_all_keyword_to_py_file(self):
with open(self.kw_file_path, "r", encoding="UTF-8") as file:
all_content = file.readlines()
index = len(all_content)
with open(self.kw_file_path, "r", encoding="UTF-8") as file1:
for line in file1:
if "if __name__" in line and "__main__" in line:
index = all_content.index(line)
break
for i in range(index - 1, 0, -1):
s = all_content[i]
if all_content[i] != "\n":
index = i
all_content.insert(i + 1, "\n")
break
a = all_content[i + 1]
with open(self.kw_file_path, "w", encoding="UTF-8") as file:
file.writelines(all_content[:index + 1])
return all_content[:index + 1]
def get_all_exist_keywords(self):
for item in self.file_content:
kw_match = re.search(r"def\s+(kw_in{1}_\w+)\(", item)
if kw_match:
self.all_keywords.append(kw_match.group(1))
return self.all_keywords
def write_content_to_interface_file(self, content, file_path=None):
if not file_path:
file_path = self.kw_file_path
with open(file_path, "a+", encoding="UTF-8") as f:
f.write(content)
def generate_all_keyword_info(self):
"""
获取interface文件内已有关键字的url请求地址和请求方法
"""
temp_dict = {}
all_content = self.file_content
server = False
kw_line = False
url_line = False
host = False
for line in all_content:
if "def kw_in_" in line:
kw_line = True
kw_name = re.search("(kw_in_[\w+_]+)\(", line).group(1)
if kw_line:
try:
if "get_container_ip" in line:
server = list(re.search(r'get_container_ip.*\([\'\"]([\w\-_]+)[\'\"]', line).groups())
server = server[0]
except Exception as err:
pass
if "self." in line and "=" in line:
temp_dict[line.split("=")[0].strip()] = line.split("=")[1].strip().strip("\"")
if (" url =" in line or "url=" in line) and 'url +' not in line:
log.info(line)
try:
urls = list(re.search(r"%s(/[\w+-_\}\{]+/?)\??|\}(/[\w+-_\}\{]+/?)\??", line).groups())
urls.remove(None)
except Exception as err:
log.info("error: %s" % line)
url_line = True
if url_line:
if 'url = "%s/api/customer/{customerId}/give_up"' in line:
print()
if not host:
try:
host_key = re.search("obj_runner\.([\w\-_]+)", line).group(1).strip().strip(")").strip(",")
host = eval("self.runner.%s" % host_key)
except:
if not server:
if "self." in line:
server = re.search("self\.([\w\-_]+)", line).group(1).strip().strip(")").strip(",")
server = server.replace("_", "-").lower()
if server:
try:
host_temp = SwaggerDBInfo.get_swagger_url_by_team_and_server(self.team, server)['sw_url']
host = host_temp.split("/v2")[0]
except Exception as err:
host = ""
if host:
url = host + urls[0]
if "req_type=" in line:
method = re.search('req_type="(\w+)"|req_type=\'(\w+)\'', line.replace("\'", "\"")).group(1)
self.all_url[url] = [method, kw_name]
server = False
kw_line = False
url_line = False
host = False
return self.all_url
class KWOperation(object):
"""
根据数据库内容生成对应的keywords
"""
def __init__(self, team, kw_instance):
self.team = team
self.doc_string_table_content_temp = " |{}|{}|{}|{}|{}|"
self.function_name = ""
self.help_doc = None
self.function_content = None
self.has_kw_list = []
self.function_list = []
self.file = kw_instance
self.all_keywords = self.file.all_keywords
self.all_url = self.file.all_url
self.kw_name = None
self.kw_string = None
self.server = kw_instance.server
def get_interface_demo(self, interface_id):
return SwaggerDBInfo.get_interface_request_demo_info_by_interface_id(interface_id)
def get_interface_parameters(self, interface_id):
return SwaggerDBInfo.get_interface_parameters_by_interface_id(interface_id)
def get_swagger_info_by_id(self, swagger_id):
return SwaggerDBInfo.get_team_swagger_info_by_id(swagger_id)
def _check_kw_exist(self, keywordname):
if keywordname in self.all_keywords:
return False
else:
return True
def generate_kw_name(self, interface_info, demo):
"""
生成对应关键字方法名称如kw_in_to_get_leads_get(is_check='', **kwargs)
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
_, name_list, _, _ = self._generate_all_url_paramaters(interface_info.in_url)
if "" in name_list:
name_list.remove("")
name_list.append(interface_info.name)
for item in add_api_list:
if item in interface_info.in_url:
name_list[-1] = 'api_' + name_list[-1]
count = -1
self.kw_name = "kw_in_{}_{}_{}".format(self.team.lower(), name_list[-1], interface_info.type.lower())
while abs(count) <= len(name_list):
if self._check_kw_exist(self.kw_name):
if lenth > 1:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(kw_name=self.kw_name)
else:
if self.team.upper() in ["TMO"]:
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(kw_name=self.kw_name)
else:
if demo_result:
if isinstance(list(demo_result.values())[0], list):
self.function_name = " def {kw_name}(self, *args, **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(
kw_name=self.kw_name)
else:
self.function_name = " def {kw_name}(self, is_check='', **kwargs):".format(
kw_name=self.kw_name)
self.has_kw_list.append(interface_info.id)
self.all_keywords.append(self.kw_name)
return True
else:
count -= 1
self.kw_name += "_" + name_list[count]
else:
self.has_kw_list.append(interface_info.id)
return False
def generate_kw_doc(self, interface_info, parameters_list):
"""
根据接口参数信息生成对应的帮助文档
"""
def set_lenth(para):
return " " + str(para).strip() + " "
doc = []
doc_string_head = " \"\"\""
doc_string_table_head = " | {}| {}| {}| {}| {}|".format("请求参数名".ljust(30), "说明".ljust(15),
"类型".ljust(12), "条件".ljust(10),
"是否必填".ljust(8))
doc.append(doc_string_head)
doc.append(" {} + {} + interface id: {}".format(interface_info.interface_describe, interface_info.type,
interface_info.id))
doc.append(" url: " + interface_info.in_url)
doc.append(doc_string_table_head)
if self.team.upper() not in ["TMO"]:
doc.append(self.doc_string_table_content_temp.format(set_lenth("is_check"),
set_lenth("is_check默认空不校验返回有值就校验返回"),
set_lenth("string"),
set_lenth("业务case的时候需要传入值"),
set_lenth("False")))
for parameter in parameters_list:
attr = SetAttr(parameter)
if int(attr.in_body) != 0 and int(attr.is_need) != 2:
doc.append(self.doc_string_table_content_temp.format(set_lenth(attr.name), set_lenth(attr.note),
set_lenth(attr.type), set_lenth(attr.p_condition),
set_lenth(attr.is_need)))
doc.append(doc_string_head)
return "\n".join(doc)
def _generate_all_url_paramaters(self, url):
"""
拆解url生成host, 生成keyword名的相关名称和url中带{}的参数
"""
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return host, head_list, para_list, url_last
def _generate_url_host(self):
"""
根据host生成对应的url后缀比如teach_opt_host
如果需要将host替换成IP则生成对应的IP
"""
server_temp = self.server.lower().replace('-', '_')
url_host = 'self.%s' % server_temp
# if self.server:
# url_host = ""
# url_host = ""
# if 'peppa-cc-manage' in host:
# url_host = "cc_host"
# elif 'crmv2' in host or 'smm' in host or 'xxljob' in host:
# url_host = "crm_host"
# elif 'scm' in host:
# url_host = "scm_host"
# elif "la-gate" in host:
# url_host = "insights_host"
# elif 'la-ai-api-bg' in host or 'la-api-ai' in host:
# url_host = "spark_land_host"
# elif 'manage' in host:
# url_host = "manage_host"
# elif 'teach' in host:
# url_host = "teach_host"
# elif 'teach-opt-api' in host or 'sparkle-manage' in host:
# url_host = "teach_opt_host"
# elif 'opengalaxy' in host:
# url_host = "opengalaxy_host"
# elif 'employee-manage' in host:
# url_host = "ehr_host"
# elif 'peppa-agent-manage' in host:
# url_host = "hhr_host"
# elif 'peppa-agent-api' in host:
# url_host = "hhr_api_host"
# elif "teach-message-api" in host:
# url_host = "teacher_message_host"
# elif "swagger" in host:
# url_host = "swagger_host"
# elif "peppa-qi-api" in host:
# url_host = "qi_host"
# elif "scm-server" in host:
# url_host = "scm_server_host"
# elif "scm-biz-server" in host:
# url_host = "scm_biz_server_host"
# elif "cti-manage" in host:
# url_host = "cti_host"
# elif "peppa-conversion-api" in host:
# url_host = "uc_host"
# elif "la-api" in host:
# url_host = "la_api_host"
# elif "hulk_content_audit_server" in host:
# url_host = "hulk_content_audit"
return url_host
def generate_function_url(self, interface_info, demo):
"""
生成对应的url
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
url_list = []
user_kwargs = " user, kwargs = get_user(kwargs)"
url_list.append(user_kwargs)
check_json = " kwargs = convert_json(kwargs)"
if paras_d == "args":
check_json = " args = convert_json(args)"
url_list.append(check_json)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
host = re.findall("[\w+-]+", host)
url_host = self._generate_url_host()
server = ""
if self.team.upper() in ["TMO"]:
url_list.append(" try:\n kwargs = eval(args[0])\n except:")
url_list.append(" try:\n if args[0] and isinstance(args[0], dict):")
url_list.append("kwargs = args[0]".rjust(20 + len("kwargs = args[0]"), " "))
url_list.append("except:".rjust(12 + len("except:"), " "))
url_list.append("kwargs = kwargs".rjust(16 + len("kwargs = kwargs"), " "))
url_last_tmp = url_last.split("/")
server = url_last_tmp.pop(1)
url_last = "/".join(url_last_tmp)
if parameters:
if lenth > 1:
temp = 'kwargs.get("u")'
else:
temp = 'kwargs'
url = " url = \"%s" + url_last + "\".format("
for item in parameters:
url += "%s=%s, " % (item, "%s.get(\"%s\", \"\")" % (temp, item))
url += ") % "
else:
url = " url = \"%s" + url_last + "\" % "
url += url_host
url_list.append(url)
return "\n".join(url_list), server
def generate_function_body(self, interface_info, demo, parameters_all):
"""
生成关键字的body内容
"""
lenth, demo_result, paras_d = self.file._clean_demo_to_less_key(demo)
host, _, parameters, url_last = self._generate_all_url_paramaters(interface_info.in_url)
func_body = []
if re.search(r"\*(\w+)", self.function_name):
paras = re.search(r"\*(\w+)", self.function_name).group(1)
if self.team.upper() in ["TMO"]:
paras = "kwargs"
content = ' obj_log.info("your input:{0}".format(%s))' % paras
func_body.append(content)
if self.kw_string:
func_body.append(" kwargs = kwargs.get('%s', '')" % parameters_all[0].get("name"))
else:
paras = None
if lenth > 1:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", ' % interface_info.type
if "d" in demo_result:
resp = resp + 'json=kwargs.get("d"), '
if "p" in demo_result:
resp = resp + 'params=kwargs.get("p"), '
if "h" in demo_result:
resp = resp + 'headers=kwargs.get("h"), '
resp = resp + 'user=user)'
else:
if demo_result:
if "d" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", json=%s, user=user)' % (
interface_info.type, paras)
elif "h" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", headers=%s, user=user)' % (
interface_info.type, paras)
elif "p" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", params=%s, user=user)' % (
interface_info.type, paras)
elif "u" in demo_result:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
else:
resp = ' resp = obj_runner.call_rest_api(API_URL=url, req_type="%s", user=user)' % (
interface_info.type)
if self.team.upper() in special_team or check_server_host(token_false_server, host) or self.team.upper() in [
"TMO"]:
resp = resp.replace("user=user", "token=False")
try:
if self.server.upper() in host_servers:
resp = resp.replace("token=False", "token=False, user=user")
if self.server.upper() == "HULK-ORG-API":
resp = resp.replace("user=user", "user=user, as_login_type=2")
if self.server.upper() in host_servers_except_api:
resp = resp.replace("token=False", "")
except:
pass
if str(self.server).lower() in header_servers:
header = " header = {'debug-param': 'huangliye@huohua.cn'}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'huangliye@huohua.cn'})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in eid_server:
header = " header = {'debug-param': 'eid:%s' % self.eid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'eid:%s' % self.eid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
if str(self.server).upper() in uid_server:
header = " header = {'debug-param': 'uid:%s' % self.uid}"
if "h" in demo_result:
resp = resp.replace(' headers=kwargs.get("h"),', "")
header = " header = kwargs.get('h')\n header.update({'debug-param': 'uid:%s' % self.uid})"
func_body.append(header)
resp = resp.replace("token=False", "token=False, headers=header")
func_body.append(resp)
if self.team.upper() not in ["TMO"] and paras_d == "kwargs":
func_body.append(" check_resp(is_check, resp)")
func_body.append(" return resp\n")
return "\n".join(func_body)
def get_has_keyword_id_in_db(self):
"""
获取所有已有
"""
keyword_list = []
for key in self.all_url.keys():
interface_name = key.split("/")[-1]
sql = "select * from sparkatp.interface_info where name = '%s' and type = '%s'" % (
interface_name, self.all_url[key][0])
interface_infos = SwaggerDBInfo.get_db_info_by_sql(sql=sql)
for interface_info in interface_infos:
interface_attr = SetAttr(interface_info)
if key.lower() in interface_attr.in_url.lower():
keyword_list.append(interface_attr.id)
return keyword_list
def run_keyword_generage(result_path, interface, demo_info, parameters, keyword):
if demo_info.request_demo:
demo_info = json.loads(demo_info.request_demo)
else:
demo_info = {}
if keyword.all_url.get(interface.in_url) and keyword.all_url.get(interface.in_url)[
0].lower() == interface.type.lower():
msg = "接口url%s,%s,已存在关键字,继续生成用例!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return True
else:
exist = keyword.generate_kw_name(interface, demo_info)
if exist:
keyword.function_list = []
doc = keyword.generate_kw_doc(interface, parameters)
url, temp = keyword.generate_function_url(interface, demo_info)
content = keyword.generate_function_body(interface, demo_info, parameters)
keyword.function_list.append("\n".join(("", keyword.function_name, doc, url, content)))
keyword.file.re_write_all_keyword_to_py_file()
keyword.file.write_content_to_interface_file("\n".join(keyword.function_list))
keyword.file.all_url[interface.in_url] = [interface.type, keyword.kw_name]
return True
else:
msg = "接口url%s,%s,无法生成关键字,请检查!" % (interface.type, interface.in_url)
log.warning(msg)
keyword.file.write_content_to_interface_file(msg + "\n", result_path)
return False
if __name__ == "__main__":
kw = KWFileOperation("CC", "peppa-qi-api")
kw.check_server_and_rewrite_server()

View File

@@ -0,0 +1,3 @@
目录结构说明:
使用者:王刚
用途存放自动生成py文件关键字的脚本

View File

@@ -0,0 +1,141 @@
# -*- coding:utf-8 -*-
"""
功能:发送飞书消息接口
"""
import requests
import json
import logging
import time
import urllib3
urllib3.disable_warnings()
import os
from base_framework.public_tools.read_config import ReadConfig
try:
JSONDecodeError = json.decoder.JSONDecodeError
except AttributeError:
JSONDecodeError = ValueError
HERE = os.path.dirname(os.path.abspath(__file__))
msg_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'msg_config.ini')
read_config = ReadConfig(filename=msg_config_path)
def get_feishu_config_options():
"""
功能:获取飞书配置文件信息
option_key:配置文件中的key
"""
return read_config.get_options('feishu_user_list')
def get_feishu_config_value(option_key):
"""
功能:获取飞书配置文件信息
option_key:配置文件中的key
"""
return read_config.get_value(sections='feishu_user_list', options=option_key)
def get_user_name_by_email_prefix(email_prefix):
"""
功能:获取飞书配置文件中的邮箱前缀与中文姓名的对应关系
email_prefix:邮箱前缀
返回:对应的中文姓名
"""
return read_config.get_value(sections='user_name', options=email_prefix)
class FeiShuMessage(object):
def __init__(self, team='TO', secret=None, pc_slide=False, fail_notice=False):
"""
机器人初始化
:param team: 业务组名用于从msg_config配置文件中读取对应群组的webhook地址
:param secret: 机器人安全设置页面勾选“加签”时需要传入的密钥
:param pc_slide: 消息链接打开方式默认False为浏览器打开设置为True时为PC端侧边栏打开
:param fail_notice: 消息发送失败提醒默认为False不提醒开发者可以根据返回的消息发送结果自行判断和处理
"""
super(FeiShuMessage, self).__init__()
self.headers = {'Content-Type': 'application/json; charset=utf-8'}
team_name = team.lower() + '_webhook_token'
if team_name in get_feishu_config_options():
token = get_feishu_config_value(option_key=team_name)
self.webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/{0}".format(token)
else:
self.webhook = None
logging.error("Team: {} not in msg_config.ini".format(team))
return
self.secret = secret
self.pc_slide = pc_slide
self.fail_notice = fail_notice
def send_text(self, msg):
"""
消息类型为text类型
:param msg: 消息内容
:return: 返回消息发送结果
"""
data = {"msg_type": "text"}
if msg and self.webhook: # 传入msg非空
data["content"] = {"text": msg}
if "全部通过" in msg:
# 不用发飞书消息
logging.info("+++++++++ 全部构建成功,不发消息 ++++++++")
return False
return self.post(data)
def post(self, data):
"""
发送消息内容UTF-8编码
:param data: 消息数据(字典)
:return: 返回消息发送结果
"""
try:
post_data = json.dumps(data)
response = requests.post(self.webhook, headers=self.headers, data=post_data, verify=False)
except requests.exceptions.HTTPError as exc:
logging.error("消息发送失败, HTTP error: %d, reason: %s" % (exc.response.status_code, exc.response.reason))
raise
except requests.exceptions.ConnectionError:
logging.error("消息发送失败HTTP connection error!")
raise
except requests.exceptions.Timeout:
logging.error("消息发送失败Timeout error!")
raise
except requests.exceptions.RequestException:
logging.error("消息发送失败, Request Exception!")
raise
else:
try:
result = response.json()
except JSONDecodeError:
logging.error("服务器响应异常,状态码:%s,响应内容:%s" % (response.status_code, response.text))
return {'errcode': 500, 'errmsg': '服务器响应异常'}
else:
logging.debug('发送结果:%s' % result)
# 消息发送失败提醒errcode 不为 0表示消息发送异常默认不提醒开发者可以根据返回的消息发送结果自行判断和处理
if self.fail_notice and result.get('errcode', True):
time_now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
error_data = {
"msgtype": "text",
"text": {
"content": "[注意-自动通知]飞书机器人消息发送失败,时间:%s,原因:%s,请及时跟进,谢谢!" % (
time_now, result['errmsg'] if result.get('errmsg', False) else '未知异常')
},
"at": {
"isAtAll": False
}
}
logging.error("消息发送失败,自动通知:%s" % error_data)
requests.post(self.webhook, headers=self.headers, data=json.dumps(error_data))
return result
if __name__ == '__main__':
fs = FeiShuMessage(team='TO')
吴勇刚 = "<at user_id='ou_94f57439f58bde9376189a3cabb0b11a'>吴勇刚</at>"
刘明浩 = "<at user_id='ou_94f57439f58bde9376189a3cabb0b11b'>刘明浩</at>"
陈慧宗 = "<at user_id='ou_bc2b266b05a428959bfcff3378af2d36'>陈慧宗</at>"
message = "【TO-SMOKING_NEW】 第【1314】次构建结果失败【5】个 \n|--{}: 1个\n|--{}: 2个\n|--{}: 3个" \
"构建报告:...........test..........".format(吴勇刚, 刘明浩, 陈慧宗)
resp = fs.send_text(msg=message)
print(resp)

View File

@@ -0,0 +1,101 @@
# encoding: utf-8
# @Time : 2021/12/20 18:14
# @Author : yk
# @Site :
# @File : jira_message_by_ding.py
from jira import JIRA
import json
import requests
import datetime
import urllib.parse
class JiraObj:
def __init__(self, user, pwd, jira_addr):
if not hasattr(JiraObj, 'jira'):
JiraObj.jira_obj(user, pwd, jira_addr)
@staticmethod
def jira_obj(user, pwd, jira_addr):
JiraObj.jira = JIRA(auth=(user, pwd), options={'server': jira_addr})
@staticmethod
def search_by_jql(jql, fields=None):
return JiraObj.jira.search_issues(jql, fields=fields, maxResults=-1, json_result='true')
def get_delay_sub_task_creator(jira_obj, now_date):
name_list="chengpu,fengtian,dengzhenbo,guosongchao,handongtang,huanghaifeng,lidaijun,wanglushun,wangyujie02,yangwenlei01,yangwenlei01,zhanghaodong,zhengxin01,zhuzipeng"
# name_list="zhanghaodong"
jql = 'issuetype = 缺陷 and createdDate < \'{} 18:00\' AND status not in (QA测试,SIM验证,Done,关闭,待测试) and assignee in ({})'.format(
now_date,name_list)
fields = 'assignee'
print(jql)
issue_list = jira_obj.search_by_jql(jql, fields).get('issues')
print(issue_list)
if issue_list:
return list(map(lambda x: x.get('fields').get('assignee').get('name'), issue_list))
else:
return None
def send_message_by_feishu(name, message):
data={"list":[name]}
a=requests.post(url="http://sqe.qc.huohua.cn:8082/feishu/getUserIds",data=data)
print(a.text)
open_id=a.text.split("open_id")[1].split('"')[2].split("\\")[0]
data={
"app_id": "cli_a2d926427f39900d",
"app_secret": "CvETCGh3rHu6CtcnxzaWK7rMnVgcSLED"
}
a=requests.post(url="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",json=data)
tenant_access_token=eval(a.text)["tenant_access_token"]
data ={"msg_type": "text","content": { "text": "{}".format(message) }, "open_ids": [open_id]}
header={"Authorization":"Bearer {}".format(tenant_access_token),"Content-Type":"application/json; charset=utf-8"}
a=requests.post(url="https://open.feishu.cn/open-apis/message/v4/batch_send/", json=data,headers=header)
print(a.text)
if __name__ == '__main__':
now_date = datetime.datetime.now().strftime('%Y-%m-%d')
jira_obj = JiraObj('yaokun', 'Cyjayk1314', 'https://jira.bg.huohua.cn')
creator = get_delay_sub_task_creator(jira_obj, now_date)
if creator:
creator=list(set(creator))
message="你还有未解决的bug请及时解决若已解决请及时更新jira状态"
for i in creator:
jql = 'issuetype = 缺陷 and createdDate < \'{} 18:00\' AND status not in (QA测试,SIM验证,Done,关闭,待测试) and assignee in ({})'.format(
now_date, i)
print(jql)
i=i+"@sparkedu.com"
a=jira_obj.search_by_jql(jql, "assignee").get('issues')
for j in a:
print(j["key"])
send_message_by_feishu(i,"你有未修复的bug请及时修复https://jira.bg.huohua.cn/browse/{}".format(j["key"]))
# send_message_by_feishu("yaokun@sparkedu.com","https://jira.bg.huohua.cn/browse/HHC-50790")
# data={"list":["yaokun@sparkedu.com"]}
# a=requests.post(url="http://sqe.qc.huohua.cn:8082/feishu/getUserIds",data=data)
#
# print(a.text.split("open_id")[1].split('"')[2].split("\\")[0])
# open_id="ou_95feb664191332a7916bb3b0d886f553"
# print(a.text)
# data={
# "app_id": "cli_a2d926427f39900d",
# "app_secret": "CvETCGh3rHu6CtcnxzaWK7rMnVgcSLED"
# }
# a=requests.post(url="https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",json=data)
# print(a.text)
#
# data ={"msg_type": "text","content": { "text": "https://jira.bg.huohua.cn/browse/HHC-50790" }, "open_ids": [open_id]}
#
# header={"Authorization":"Bearer t-719d260f474e6a1a6b4a5d990a6bccec6fc3b17f","Content-Type":"application/json; charset=utf-8"}
# print(data)
# a = requests.post(url="https://open.feishu.cn/open-apis/message/v4/batch_send/", json=data,headers=header)
# print(a.text)

View File

@@ -0,0 +1 @@
# to be add 。。。。

View File

@@ -0,0 +1,236 @@
# 此文档中配置钉钉,飞书群组和群用户信息
[feishu_user_list]
JENKINS_webhook_token = 6de2e886-10de-4a85-aa92-2d7446d0c315
CDQA_webhook_token = 30e400ed-b802-4afb-b38c-2a0690c1a2a6
TO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
TMO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
HHI_TO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
HHI_TMO_webhook_token = 2109acc2-6ffe-4038-8bf1-4bfcfcd4cb01
GUE_webhook_token = a46a610c-4c52-4abf-bcad-5fde5cd545e4
LALIVE_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
H2R_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
CC_webhook_token = 3f9379c7-ebcd-4844-9c61-3f2642f64df4
ASTWB_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ASOPE_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ASTOP_webhook_token = 31ab2e27-df7c-46d6-8294-afb98a68b6f0
ES_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
SCM_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
UBRD_webhook_token = a46a610c-4c52-4abf-bcad-5fde5cd545e4
DBSYNC_webhook_token = 054f4c3c-b38b-46f8-9cc4-d9660e205fc7
ODS_webhook_token = 054f4c3c-b38b-46f8-9cc4-d9660e205fc7
AUTOMATION_webhook_token = ceb41bf3-f3ce-433f-bbea-2960bf46a819
; TO-RD_webhook_token = d8d9ac4b-e5f9-47a6-8810-0ae6b621a7cb
TO-RD_webhook_token = 2387b345-3675-49dd-971a-2f81b98e3ed1
TO-FE_webhook_token = 1d0dac58-dd68-412d-a2eb-b445927339a
GUE-RD_webhook_token = 8951949c-e300-4c96-a725-53346309851a
USER-FE_webhook_token = 4f946e2b-0b58-4eb5-aa22-3d3516aee1ba
SCM-RD_webhook_token = 74ceedc8-510f-4efd-bf64-4c5e6e87dc0f
SCM-monitor_webhook_token = b821c1e5-63b0-42c9-b7ff-ab89c6dcd076
PB_webhook_token = 84b165e1-160f-4ebd-a36d-d1873cca0d20
INFO_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
CODING_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
OFFLINE_webhook_token = 20114e9a-9f09-44f9-bfbe-0da382745cde
陈林 = ou_69254b2555a2c6257d5ca45d885f785e
吴勇刚 = ou_94f57439f58bde9376189a3cabb0b11a
陈慧宗 = ou_bc2b266b05a428959bfcff3378af2d36
胥雯筠 = ou_04e4a3ccf680acc17f1039dd57349a00
陈江 = ou_02ce6ebcb8a510c0b4102e15aaf59d05
谯新久 = ou_c3c6f7ac7997000dcb23e634a830851d
罗洪 = ou_f6981cb788d066accae770fda5fa7ee5
刘睿权 = ou_218303e87f4c45ff81ed16a1e1d0b1ee
陈典模 = ou_0fe84f418974af89e89148e2380427dd
李聪 = ou_711ebaac08461ae9a2726fcbe393d7de
左其灵 = ou_cc27134156714d88da23e159ba8390e1
宋飞飞 = ou_ccafa6ebe0194e42ea55f8804734e6d4
付文龙 = ou_589b7151ac583ca3d94f0fb2d579f282
赵晓放 = ou_a62ef7960b9c57e1946f55a6bb5d8b35
张浩东 = ou_04ba397251c63036e9d75096f3c28704
王玉杰 = ou_e3198cbf59327bac7dd686a94af30c2a
冯天 = ou_3522b9c4015645ea2d4c36aa6c260e90
郭松超 = ou_6a8e6ccaceb51167c4745b347085edd9
张雄 = ou_1ab263e2e7386fd5c5ecd67c1c6f2b6d
刘学刚 = ou_a17ade671a3030f12ae0fa13cc4f82bc
朱亮 = ou_0cc7a4c1a97bb58dd188b6aac6c7569b
高志军 = ou_5df6937cc109565f92868f0cdca4906c
马锦程 = ou_d35f2668f960300ee2d9a7768c7de9ce
刘英杰 = ou_d62d587ce3960add08214726897c228d
张晓刚 = ou_320976aaeb1e50976932f2eb446918c7
赵飞 = ou_17fdcc65d985a21a225034db318308bf
刘信林 = ou_dec1cf51c8b3e63823e7262928b20dbb
叶飞 = ou_3559e23c390a60a59f4b10e7053e5551
向明 = ou_4c26b3066017f57f6f2658b49e16b8a5
姜浩 = ou_8014499bb65adda6399ebc82e1a46724
姜灵敏 = ou_e4e113c845e88a43125c8a994710ff94
徐长乐 = ou_54c5d0f98d26ab7500a5af64d9022b1b
卿晨 = ou_3e517c22dbcdfce4b4e75af20a7e5d8c
李柏成 = ou_e40510cf340bc5ac0f860ac01844f1a1
苟宇恒 = ou_d82652fb433a470d7ac72a542292cd3b
董吉祥 = ou_4d9d1b8cf8c743f75313863a3ac336ad
白杨 = ou_ac886a59ffd1a4a0da4cb3d06c1e7b03
李明泽 = ou_0fc5af64989a1677fa4c34b7542e4c2c
沈佳坤 = ou_36ed516d812a1bacc3f978179e32910d
朱乾元 = ou_31a10ecba1074d45df6c6cbb13ec16e9
顾洋 = ou_543fe7b50e1cadf6974e93ac8663b09d
吴优 = ou_117b8d43a8122021d290ef89cbcb9c88
王亚超 = ou_649286a2b30bfe81bb4041a2b617fd80
徐佳林 = ou_f382da52d42932870a0ea122753f795a
彭霞 = ou_9ef2d9e4a5c098ab27933208dd95ff8a
通用 = ou_abcdefghijklmnopqrstuvwxyz123456
韩东堂 = ou_4ff52d71cb3cbe7cd1f26b2ac7da5eb0
邓振博 = ou_994af7ca097ea1b63a5bec13b0b9a42c
朱子朋 = ou_8d13121890e1de3851c0616b8392cb1f
李代军 = ou_279bbc6d368cffa743c959d7461d8481
黄海峰 = ou_8a4cf96dfc173dbed704d44fdc8fc5c1
杨文磊 = ou_885d10cea3c3ae72c380971a7082da06
卢纪霖 = ou_eaaf66ef5c53d3977a0bd3961376beae
杨远宁 = ou_8693a6f29fe174b4d18155e6d4da0396
崔瑞 = ou_7bbb7b678b07e74fd1c245bafc6877f9
李振宇 = ou_82a619af1d592dfb994ee28389815ee9
王同刚 = ou_ca8ea6ff6bf6258f49932dc4b0c98fbc
张瑞涛 = ou_ca355f3fad90fe934087d225bb5794b4
左钊 = ou_ea9a364cffa235aec88d37d075e300bb
田翔 = ou_75796572af14c996f853f015ecb788e0
赵加会 = ou_33ed201f4e7f0f8919bc7021130c066e
刘欣畅 = ou_6247db29c46ac536d2aa10fab9711a82
田文昌 = ou_60e01001facaadd78b89ef05281e0763
郑宇翔 = ou_4e8d677a2389a8f8a5517961f2423970
王国静 = ou_862b663bac7be9762eb78d71a9e6defb
李其 = ou_cfb094d4d15f7da729eea8b1491ffd13
尚万中 = ou_53e6edbf2ef8a1620cbadb4b7efeb966
张景峰 = ou_48c88a2ff129e5d23215d245c6b82fc0
[user_name]
fuwenlong = 付文龙
fengtian = 冯天
guosongchao = 郭松超
wangyujie02 = 王玉杰
zhanghaodong = 张浩东
zhuliang = 朱亮
liuxuegang = 刘学刚
gaozhijun = 高志军
majincheng = 马锦程
zhaoxiaofang = 赵晓放
liuyingjie = 刘英杰
zhangxiaogang = 张晓刚
zhaofei = 赵飞
liuxinlin = 刘信林
yefei = 叶飞
xiangming = 向明
jianghao = 姜浩
jianglingmin = 姜灵敏
xuchangle = 徐长乐
qingchen = 卿晨
libaicheng = 李柏成
gouyuheng = 苟宇恒
jixiang.dong = 董吉祥
baiyang01 = 白杨
wuyonggang = 吴勇刚
xuwenjun = 胥雯筠
songfeifei = 宋飞飞
wanggang02 = 王刚
lichao04 = 李超
xiexiangyi = 谢祥益
denghaiou = 邓海鸥
yaokun = 姚坤
chenhuizong = 陈慧宗
yuanzhengqi = 袁正旗
luohong = 罗洪
chendianmo = 陈典模
licong = 李聪
zuoqiling = 左其灵
zhengxin01 = 郑新
handongtang = 韩东堂
dengzhenbo = 邓振博
zhuzipeng = 朱子朋
lidaijun = 李代军
huanghaifeng =黄海峰
yangwenlei01 =杨文磊
lujilin = 卢纪霖
yangyuanning = 杨远宁
cuirui = 崔瑞
lizhenyu = 李振宇
wangtonggang = 王同刚
zhangruitao01 = 张瑞涛
zuozhao = 左钊
tianxiang = 田翔
zhaojiahui = 赵加会
liuxinchang = 刘欣畅
tianwenchang = 田文昌
zhengyuxiang = 郑宇翔
zhangxiong = 张雄
wangguojing = 王国静
liqi03 = 李其
shangwanzhong = 尚万中
zhangjingfeng = 张景峰
limingze = 李明泽
zhuqianyuan = 朱乾元
guyang = 顾洋
shenjiakun = 沈佳坤
wuyou = 吴优
[dingding_user_list]
SCM_owner = 13350956802
景虎成 = 13350956802
文妮 = 17302811505
罗洪 = 13550629276
TO_owner = 18215530124
李超 = 18011454607
吴勇刚 = 13540133074
王刚 = 19141999584
刘明浩 = 18380450039
谢祥益 = 18010623985
唐其麟 = 18011454607
肖淇迈 = 19141999584
唐浩 = 18380450039
TMO_owner = 18215530124
姚坤 = 18215530124
罗志鹏 = 18582482272
刘涛婷 = 18328504751
陈江 = 13458500234
ASTWB_owner = 18202810506
谯新久 = 18202810506
刘鹏 = 13547858402
杨雨 = 13608006659
华学敏 = 13708231975
ASORG_owner = 18202810506
PZ_owner = 18202810506
肖亮 = 18108096240
ASTOP_owner = 18780106567
ASOPE_owner = 18180956201
蒋安龙 = 18180956201
杨中莲 = 13882105134
CC_owner = 18215530124
林于棚 = 18782019436
黄业宏 = 18603267203
EN_owner = 18180956201
LALIVE_owner = 18215530124
H2R_owner = 18380448416
胥雯筠 = 18780106567
周仁华 = 18281029023
袁正旗 = 18030895120
刘睿权 = 15681935823
文青 = 18584851102
刘德全 = 13402829590
包利 = 18380448416
ASTOP_to_dd = 495fc7e0171e829acda91ffa08eaf37307e123fafc090249b633eb651c31dd79
ASTWB_to_dd = 2079cb3e311ab6a37138a6d4671181661d211c12a1532c2012ae7e6b397996c3
ASOPE_to_dd = 147617cea7e1b480e54cecb6dfd33edb5a9ed872e1bab52007d488673ad8ab0f
ASORG_to_dd = 2079cb3e311ab6a37138a6d4671181661d211c12a1532c2012ae7e6b397996c3
CC_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
H2R_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
LALIVE_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
TMO_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
TO_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
UBRD_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
ULS_to_dd = ccceb513dbc7120dc301fa38a2f634b861009ec54d424d293653dc15c6e4963f
SCM_to_dd = 38fc76120cb204e2c1da53ec9921805670466da64c37aed8bc6fba0513f5688b

View File

@@ -0,0 +1,851 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import json
import os
import random
import re
import sys
import string
import time
import traceback
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
sys.path.append(Project_Path)
from base_framework.platform_tools.Keywords_service.keyword_service import SetAttr, log
def load_module_from_file(file_path, team):
module_file = re.search(r"\\(%s\\.*)\.py" % team, file_path)
# if not module_file:
# module_file = re.search(r"\\(%s\\.*)\.py" % team.lower(), file_path)
temp = module_file.group(1)
temp = temp.replace(os.sep, ".")
# try:
# module = importlib.import_module(temp)
# except Exception as err:
# temp = re.sub(r"%s\." % team, "%s." % team.lower(), temp)
# module = importlib.import_module(temp)
return importlib.import_module(temp)
class ITCaseFileOperation(object):
def __init__(self, team, kw_instance):
self.kw = kw_instance
self.all_url = kw_instance.all_url
self.kw_class_name = kw_instance.kw_class
self.case_dir = os.path.abspath(os.path.join(Project_Path, team, "test_case", "TestCase", "1.接口"))
self.env_path = os.path.abspath(
os.path.join(Project_Path, team, "test_case", "Resource", "AdapterKws", "env.robot"))
def _check_case_file_exist(self, file_path):
if os.path.exists(file_path) and os.path.isfile(file_path):
return True
else:
return False
def check_case_exist(self, case_path, case_name):
if self._check_case_file_exist(case_path):
with open(case_path, "r", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith(case_name):
return True
else:
return False
else:
return False
def generate_case_file(self, file_path):
dirname = file_path[:-len(os.path.basename(file_path))]
if not self._check_case_file_exist(file_path):
if not os.path.exists(dirname):
os.makedirs(dirname)
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
else:
with open(file_path, "r", encoding="utf-8") as f:
content = f.readlines()
if content:
if "\n" != content[-1]:
self.write_content_to_case_file(file_path, "\n")
else:
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
def generate_env_releate_path(self, file_path):
return "*** Settings ***\nResource " + os.path.relpath(self.env_path,
file_path).replace("\\",
"/") + "\n\n*** Test Cases ***\n"
def _check_case_file_resource(self, file_path):
"""
确定case文件中是否有关键字
"""
with open(file_path, "a+", encoding="utf-8") as f:
content_list = f.readlines()
if "*** Keywords ***\n" not in content_list:
return True
else:
kw_index = content_list.rindex("*** Keywords ***\n")
for index in range(kw_index, len(content_list) + 1):
if "*** Test Cases ***\n" == content_list[index]:
status = True
if "*** Keywords ***\n" == content_list[index]:
status = False
return status
def write_content_to_case_file(self, file_path, content):
status = self._check_case_file_resource(file_path)
with open(file_path, "a+", encoding="utf-8") as f:
if status:
f.write(content)
else:
content = "*** Test Cases ***\n" + content
f.write(content)
class ITCaseContentOperation(object):
def __init__(self, team, kw_instance):
self.team = team
self.file = ITCaseFileOperation(team, kw_instance)
self.doc_content_row_temp = " ... | {} | {} | {} |"
self.normal_1001 = None
self.un_expect_list = None
self.case_string = None
def _generate_case_name(self, interface_name, case_type, case_des):
return "-".join([interface_name, case_type, case_des])
def _check_interface_confirm(self, interface_info):
if not interface_info.confirmed:
raise Exception("接口%s未确认,请先确认接口参数。" % interface_info.name)
def generate_case_body_all_1001_array(self, request_demo, parameters):
temp_list = list()
demo_copy = copy.deepcopy(request_demo)
for key, value in request_demo.items():
if not value:
demo_copy.pop(key)
if len(demo_copy.keys()) == 1:
demo_copy = demo_copy.pop(list(demo_copy.keys())[0])
dict_para, self.un_expect_list, _, _ = self._analysis_case_parameters_to_dict(parameters)
demo_add_values = self._analysis_case_parameters_to_value(demo_copy, dict_para)
data_list = self._analysis_dict_to_body_value(demo_copy, demo_add_values)
if isinstance(demo_copy, list):
for item in data_list:
temp_list.append([item])
data_list = temp_list
return data_list
def _generate_parameter_to_dict(self, parameters):
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def _analysis_case_parameters_to_value_empty_dict(self, request_demo, parameters, type=None):
"""
循环生成缺少某个参数的值,其它正确的参数组合列表
"""
parameter_expect_list = list()
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for key, parameter_attr in parameters.items():
if parameter_attr.normal_values:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
parameter_expect_dict[parameter_attr.name] = [
self._generate_body_expect_type_value(parameter_attr.type)]
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
parameter_un_expect_dict[parameter_attr.name] = [
self._generate_body_un_expect_type_value(parameter_attr.type)]
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
for key in parameter_expect_dict.keys():
if key not in json.dumps(request_demo):
continue
value = copy.copy(parameter_expect_dict)
value[key] = ['NULL']
if parameters.get(key).is_need == 1:
status = "FAIL"
else:
status = "PASS"
if type == "1102":
name = "缺少参数%s" % key
else:
name = "缺少参数%s的值" % key
parameter_expect_list.append([value, status, name])
return parameter_expect_list
def _distinguish_parameters_by_is_needed(self, parameters):
"""
将必填参数和非必须参数分开成2个列表
"""
need_parameters = list()
no_need_parameters = list()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.is_need != 0:
need_parameters.append(parameter_attr)
else:
no_need_parameters.append(parameter_attr)
return need_parameters, no_need_parameters
def _replace_null_value_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s[\[\{]" % parameter_attr.name
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, really_body)
return really_body
def generate_case_body_all_1101_array(self, request_demo, parameters):
"""
生成1101用例对应的所有请求参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_value_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数为空"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search("\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
log.info("1101%s" % name)
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": []" % parameter_attr.name
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, demo_json_copy)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def _clean_all_none_json_char(self, really_body):
"""
清除1102场景删除缺少参数后不规则的字符串内容
"""
really_body = re.sub(",\s,", ",", really_body)
really_body = re.sub("\[\s?,", "[", really_body)
really_body = re.sub("\{\s?,", "{", really_body)
really_body = re.sub(",\s\]", "]", really_body)
really_body = re.sub(",\s\}", "}", really_body)
really_body = re.sub("\s,", "", really_body)
return really_body
def _replace_null_all_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = ""
really_body = re.sub(patten1, replace1, really_body)
really_body = self._clean_all_none_json_char(really_body)
return really_body
def generate_case_body_all_1102_array(self, request_demo, parameters):
"""
生成1102用例对应的所有参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_all_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数均缺失"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
log.info("1102%s" % name)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = ""
really_body = re.sub(patten1, replace1, demo_json_copy)
really_body = self._clean_all_none_json_char(really_body)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def generate_case_body_all_1201_array(self, request_demo, parameters):
"""
生成1201用例对应的所有参数组合
"""
body_array = list()
demo_json = json.dumps(request_demo, ensure_ascii=False)
for item in parameters:
parameter_attr = SetAttr(item)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json):
continue
log.info("参数%s开始替换异常值" % parameter_attr.name)
for un_expect_item in self.un_expect_list.get(parameter_attr.name, []):
name = "参数%s使用异常值%s" % (parameter_attr.name, un_expect_item)
demo_json_copy = copy.copy(demo_json)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = '"%s": ["%s"]' % (parameter_attr.name, un_expect_item)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = '"%s": "%s"' % (parameter_attr.name, un_expect_item)
really_body = re.sub(patten1, replace1, demo_json_copy)
status = "FAIL"
body_array.append([json.loads(really_body), status, name])
return body_array
def create_case_content(self, result_path, interface_info, parameters, demo, case_type, tags=None):
"""
生成用例的所有内容
"""
self._check_interface_confirm(interface_info)
body_content = list()
doc = self.generate_case_doc(interface_info, tags=tags, demo=demo)
case_file, _ = self.generate_case_file_path(interface_info)
interface_name = interface_info.name
if not parameters:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, None, status="PASS")
else:
try:
body_array = self.generate_case_body_all_1001_array(demo, parameters)
self.normal_1001 = body_array[0]
except TypeError:
log.warning(traceback.print_exc())
body_array = []
self.normal_1001 = {}
if case_type == "1001":
try:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, body_array, status="PASS")
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
elif case_type == "1101" or case_type == "1102" or case_type == "1201":
body_temp_list = []
# parameters_temp = self._generate_parameter_to_dict(parameters)
try:
body_array = eval(
"self.generate_case_body_all_%s_array(self.normal_1001, parameters)" % case_type)
if case_type == "1101":
name = "缺少参数值"
elif case_type == "1102":
name = "缺少参数key和值"
elif case_type == "1201":
name = "参数使用异常值"
else:
name = ""
body_tmp = self.generate_case_body_content(interface_info.in_url, doc, interface_name,
case_type,
name, body_array, status="PASS")
body_temp_list.append(body_tmp)
body = "".join(body_temp_list)
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
raise err
body_content.append(body)
self.file.write_content_to_case_file(case_file, "\n".join(body_content))
msg = "接口url%s,%s,生成case%s成功!路径为:%s" % (interface_info.type, interface_info.in_url, case_type, case_file)
log.info(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
return msg
def generate_case_body_content(self, url, doc, interface_name, case_type, description, body_para, status="PASS"):
body_content = list()
case_name = self._generate_case_name(interface_name, case_type, description)
body_content.append(case_name)
body_content.append(doc)
# 如何需要自动生成1001类型用例的请求body数据请将304/305行放开并把306~310注释
request = self._generate_case_body_content(url, body_content=body_para, status=status)
body_content.append(request)
# if case_type != "1001":
# request = self._generate_case_body_content(url, body_content=body_para, status=status)
# body_content.append(request)
# else:
# body_content.append(" #请自行添加用例请求数据!\n")
return "\n".join(body_content)
def _generate_case_body_content(self, url, body_content=None, status="PASS"):
body_list = []
if body_content is None:
try:
body_request = " ${resp} %s" % (self.file.all_url.get(url)[1])
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_request)
body_list.append(" Log ${resp}")
if self.team.upper() in ["TMO"]:
body_validate_success = " should be equal as json ${resp} %s" % '请自行添加检查结果'
else:
body_validate_code = " Should Be Equal ${resp['code']} ${200}"
body_validate_success = " Should Be True ${resp['success']}"
body_list.append(body_validate_code)
body_list.append(body_validate_success)
else:
if not body_content:
body_create = " #request_demo无内容或者parameters在数据表中不全请先检查再生成 \n ${body} Evaluate %s" % body_content
body_list.append(body_create)
count = 0
for real_body in body_content:
body_error = None
result = {"message": "", "code": 200, "success": True}
temp = copy.copy(real_body)
if len(real_body) == 3 and isinstance(real_body, list):
if real_body[1] == "PASS" or real_body[1] == "FAIL":
real_body = temp[0]
status = temp[1]
case_description = temp[2]
body_list.append(" #%s" % case_description)
try:
module = load_module_from_file(self.file.kw.kw_file_path, self.team)
importlib.reload(module)
if isinstance(real_body, list):
temp_type = "*"
else:
temp_type = "**"
result = eval(
"%s.%s().%s(%sreal_body)" % (
"module", self.file.kw_class_name, self.file.all_url.get(url)[1], temp_type))
except:
msg = "#url: %s 无法通过参数得到请求结果!" % url
log.warning(real_body)
log.warning(msg)
log.warning(traceback.print_exc())
if "${time}" in str(real_body):
body_list.append(" ${time} get time epoch")
body_create = " ${body_%s} Evaluate %s" % (count, real_body)
try:
if isinstance(real_body, list):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "@{body_%s}" % count)
elif isinstance(real_body, dict):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "&{body_%s}" % count)
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_create)
body_list.append(body_request)
body_list.append(" Log ${resp_%s}" % count)
if self.team.upper() in ["TMO"]:
body_validate_resp = " Should Be Equal as json ${resp_%s} %s" % (count, result)
else:
try:
body_validate_code = " Should Be Equal as strings ${resp_%s['code']} ${%s}"
# body_validate_success = " %s ${resp_%s['success']}"
body_validate_success = " %s ${resp_%s['success']} %s"
body_validate_resp = " %s ${resp_%s[\"message\"]} %s"
if isinstance(result, str):
body_validate_code = " #特殊接口返回!"
body_validate_success = " #返回为bytes类型的字符码。仅检查结果是否相等"
body_validate_resp = " Should Be Equal as strings ${resp_%s} %s" % (count, result)
elif status == "PASS":
body_validate_code = body_validate_code % (count, result['code'])
body_validate_success = body_validate_success % ("Should Be True", count, "")
try:
if result["message"]:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
else:
try:
body_validate_code = body_validate_code % (count, result['code'])
except Exception as e:
if "status" in result:
body_validate_code = body_validate_code % (count, result['status'])
body_validate_code = body_validate_code.replace("code", "status")
else:
raise KeyError("接口不中无code返回请与开发确认")
try:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["success"])
except Exception as err:
if "error" in result:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["error"])
body_validate_success = body_validate_success.replace("success", "error")
else:
raise KeyError("接口不中无success返回请与开发确认")
try:
if result.get("message"):
if re.search("[a-zA-Z0-9]{32}", result.get("message", "")):
body_validate_resp = body_validate_resp % ("should contain", count,
re.sub("[a-zA-Z0-9]{32}", "", result["message"]))
else:
body_validate_resp = body_validate_resp % (
"should be equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
except Exception as error:
log.error(error)
body_validate_code = None
body_validate_success = None
body_validate_resp = None
body_error = " #无法正确生成异常用例返回值,请检查并手动生成!"
if body_validate_code:
body_list.append(body_validate_code)
if body_validate_success:
body_list.append(body_validate_success)
if body_validate_resp:
body_list.append(body_validate_resp)
if body_error:
body_list.append(body_error)
# body_list.append(" #请自行添加data校验")
count += 1
body_list.append("\n")
return "\n".join(body_list)
def _generate_body_expect_type_value(self, type):
if type.lower() == 'integer':
value = random.randint(0, 100)
elif type.lower() == "string":
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "date":
value = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
elif type.lower() == "boolean":
value = True
elif type.lower() == "array":
value = [1]
else:
value = "123"
return value
def _generate_body_un_expect_type_value(self, type):
value = ""
if type.lower() == 'integer':
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "string":
value = random.randint(0, 10000)
else:
value = False
return value
def _analysis_dict_to_body_value(self, dict_none, dict_data):
"""
生成所有请求参数的组合列表
"""
data_list = []
if isinstance(dict_none, list):
if dict_none:
for index in range(len(dict_none)):
data = self._analysis_dict_to_body_value(dict_none[index], dict_data[index])
data_list.extend(data)
else:
data_list = dict_data
elif isinstance(dict_none, dict):
try:
len_num = self._get_nums(dict_data)
except Exception as e:
# log.error(e)
log.error("数据表request_parameters中参数不全请使用interface_hunter补全参数")
raise e
for i in range(0, len_num):
copy_dict = copy.copy(dict_none)
for key, value in copy_dict.items():
data = self._analysis_dict_to_body_value(copy_dict[key], dict_data[key])
if not data:
copy_dict[key] = None
elif i >= len(data):
copy_dict[key] = data[0]
if isinstance(value, list):
copy_dict[key] = [data[0]]
else:
copy_dict[key] = data[i]
if isinstance(value, list):
copy_dict[key] = [data[i]]
data_list.append(copy_dict)
else:
data_list = dict_data
return data_list
def _get_nums(self, dict_data):
"""
获取所有参数对应值的最大长度
"""
nums_list = [0]
if isinstance(dict_data, list):
for item in dict_data:
if isinstance(item, dict):
data = self._get_nums(dict_data[0])
nums_list.append(data)
else:
nums_list.append(len(dict_data))
elif isinstance(dict_data, dict):
for value in dict_data.values():
data = self._get_nums(value)
nums_list.append(data)
elif dict_data == None:
nums_list.append(0)
else:
nums_list.append(len(dict_data))
return max(nums_list)
def _analysis_case_parameters_to_value(self, interface_demo, parameters):
"""
根据demo将每个参数的对应值的列表写回demo
"""
body = copy.copy(interface_demo)
if isinstance(body, list):
for index in range(len(body)):
item = body[index]
body[index] = self._analysis_case_parameters_to_value(item, parameters)
elif isinstance(body, dict):
for key, value in body.items():
if isinstance(value, dict) or isinstance(value, list):
if value:
body[key] = self._analysis_case_parameters_to_value(body.get(key), parameters)
else:
body[key] = parameters.get(key)
else:
body[key] = parameters.get(key)
return body
def _analysis_case_parameters_to_dict(self, parameters):
"""
根据所有参数生成正常值 和异常值的列表
"""
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.normal_values:
if parameter_attr.normal_values.startswith("["):
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
if parameter_attr.type == "array":
parameter_expect_dict[parameter_attr.name] = [[x] for x in parameter_attr.normal_values.split(",")]
else:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
continue
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
# raise Exception("接口参数%s无异常值请先确认接口参数。" % parameter_attr.name)
parameter_un_expect_dict[parameter_attr.name] = []
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
return parameter_expect_dict, parameter_un_expect_dict, expect_lenth, un_expect_lenth
def generate_case_doc(self, interface_info, tags=None, demo=None):
if demo:
lenth, demo_json, paras_d = self.file.kw._clean_demo_to_less_key(demo)
doc_list = list()
doc_list.append(
" [Documentation] {} 接口路径:{}, {}".format(interface_info.interface_describe,
interface_info.type, interface_info.in_url))
# doc_list.append(self.doc_content_row_temp.format("输入参数名", "参数类型", "说明"))
# for parameters_info in parameters:
# sub_doc = self._generate_case_doc(parameters_info)
# doc_list.append(sub_doc)
if tags:
tag_list = tags.split(",")
tag = " [Tags]"
for tag_ in tag_list:
tag += " %s" % tag_
doc_list.append(tag)
if lenth > 1:
doc_list.append(
" # 请求参数说明 h:放header里的参数 u:放url路径节点中的参数 d:不放在url中在请求参数 p放在url问号后在key-value结构参数")
return "\n".join(doc_list)
def _generate_case_doc(self, request_parameter):
doc_content = list()
parameter_attr = SetAttr(request_parameter)
row = self.doc_content_row_temp.format(parameter_attr.name, parameter_attr.type, parameter_attr.note)
doc_content.append(row)
return "\n".join(doc_content)
def generate_case_file_path(self, interface_info):
path_cell_list = self._generate_case_file_path_info(interface_info.in_url)
path_cell_list.insert(0, self.file.case_dir)
return os.path.abspath(os.path.join(*path_cell_list[:-1]) + ".robot"), path_cell_list[-1]
def _generate_case_file_path_info(self, url):
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
path_first = re.search("//([\w+\-]+)\.", host).group(1)
head_list.append(path_first)
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return head_list
def run_interface_case_generate(result_path, interface, demo_info, parameters, case_instance, tags=None, normal=None):
case_type_list = ["1001", "1101", "1102", "1201"]
not_exist_case_type = list()
exist_case_name = list()
case_path, _ = case_instance.generate_case_file_path(interface)
case_name_head = interface.name
if parameters:
for case_type in case_type_list:
case_name = "-".join([case_name_head, case_type])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append(case_type)
else:
exist_case_name.append(case_name)
else:
case_name = "-".join([case_name_head, "1001"])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append("1001")
else:
exist_case_name.append(case_name)
for item in exist_case_name:
msg = "接口url%s,%s,已存在用例%s,请确认!" % (interface.type, interface.in_url, item)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")
if not_exist_case_type:
if normal:
not_exist_case_type = ["1001"]
case_instance.file.generate_case_file(case_path)
try:
demo = json.loads(demo_info.request_demo)
except Exception as err:
log.error(traceback.print_exc())
log.warning("the demo of interface(%s) is empty,please check." % interface.id)
demo = dict()
try:
result = list()
result.append("")
result.append("-" * 60)
result.append("用例生成成功!")
for case_type in not_exist_case_type:
body = case_instance.create_case_content(result_path, interface, parameters, demo, case_type, tags=tags)
# result.append(body)
result.append(body.split("")[1])
log.info("\n".join(result))
except:
log.error("could not generate case for interface id: %s, url: %s" % (interface.id, interface.in_url))
log.error(traceback.print_exc())
else:
msg = "接口url%s,%s,已存在用例,请确认!" % (interface.type, interface.in_url)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")

View File

@@ -0,0 +1,833 @@
# -*- coding:utf-8 -*-
import copy
import importlib
import json
import os
import random
import re
import sys
import string
import time
import traceback
def get_project_root_path(path="base_framework"):
"""
获取项目目录
"""
o_path = os.getcwd()
try:
project_path = re.search(r"(.*%s)" % path, o_path).group(1)
return os.path.abspath(os.path.join(project_path, os.path.pardir))
except:
pass
Project_Path = get_project_root_path()
sys.path.append(Project_Path)
from base_framework.platform_tools.Keywords_service.keyword_service1 import SetAttr, log
def load_module_from_file(file_path, team):
module_file = re.search(r"(%s\\.*)\.py" % team, file_path)
if not module_file:
module_file = re.search(r"(%s.*)\.py" % team.lower(), file_path)
temp = module_file.group(1)
temp = temp.replace(os.sep, ".")
# try:
# module = importlib.import_module(temp)
# except Exception as err:
# temp = re.sub(r"%s\." % team, "%s." % team.lower(), temp)
# module = importlib.import_module(temp)
return importlib.import_module(temp)
class ITCaseFileOperation(object):
def __init__(self, team, kw_instance):
self.kw = kw_instance
self.all_url = kw_instance.all_url
self.kw_class_name = kw_instance.kw_class
self.case_dir = os.path.abspath(os.path.join(Project_Path, team, "test_case", "TestCase", "1.接口"))
self.env_path = os.path.abspath(
os.path.join(Project_Path, team, "test_case", "Resource", "AdapterKws", "env.robot"))
def _check_case_file_exist(self, file_path):
if os.path.exists(file_path) and os.path.isfile(file_path):
return True
else:
return False
def check_case_exist(self, case_path, case_name):
if self._check_case_file_exist(case_path):
with open(case_path, "r", encoding="utf-8") as f:
for line in f.readlines():
if line.startswith(case_name):
return True
else:
return False
else:
return False
def generate_case_file(self, file_path):
if not self._check_case_file_exist(file_path):
dirname = file_path[:-len(os.path.basename(file_path))]
if not os.path.exists(dirname):
os.makedirs(dirname)
self.write_content_to_case_file(file_path, self.generate_env_releate_path(dirname))
else:
with open(file_path, "r", encoding="utf-8") as f:
if f.readlines():
if "\n" != f.readlines()[-1]:
self.write_content_to_case_file(file_path, "\n")
def generate_env_releate_path(self, file_path):
return "*** Settings ***\nResource " + os.path.relpath(self.env_path,
file_path).replace("\\",
"/") + "\n\n*** Test Cases ***\n"
def _check_case_file_resource(self, file_path):
"""
确定case文件中是否有关键字
"""
with open(file_path, "a+", encoding="utf-8") as f:
content_list = f.readlines()
if "*** Keywords ***\n" not in content_list:
return True
else:
kw_index = content_list.rindex("*** Keywords ***\n")
for index in range(kw_index, len(content_list) + 1):
if "*** Test Cases ***\n" == content_list[index]:
status = True
if "*** Keywords ***\n" == content_list[index]:
status = False
return status
def write_content_to_case_file(self, file_path, content):
status = self._check_case_file_resource(file_path)
with open(file_path, "a+", encoding="utf-8") as f:
if status:
f.write(content)
else:
content = "*** Test Cases ***\n" + content
f.write(content)
class ITCaseContentOperation(object):
def __init__(self, team, kw_instance):
self.team = team
self.file = ITCaseFileOperation(team, kw_instance)
self.doc_content_row_temp = " ... | {} | {} | {} |"
self.normal_1001 = None
self.un_expect_list = None
self.case_string = None
def _generate_case_name(self, interface_name, case_type, case_des):
return "-".join([interface_name, case_type, case_des])
def _check_interface_confirm(self, interface_info):
if not interface_info.confirmed:
raise Exception("接口%s未确认,请先确认接口参数。" % interface_info.name)
def generate_case_body_all_1001_array(self, request_demo, parameters):
temp_list = list()
demo_copy = copy.deepcopy(request_demo)
for key, value in request_demo.items():
if not value:
demo_copy.pop(key)
if len(demo_copy.keys()) == 1:
demo_copy = demo_copy.pop(list(demo_copy.keys())[0])
dict_para, self.un_expect_list, _, _ = self._analysis_case_parameters_to_dict(parameters)
demo_add_values = self._analysis_case_parameters_to_value(demo_copy, dict_para)
data_list = self._analysis_dict_to_body_value(demo_copy, demo_add_values)
if isinstance(demo_copy, list):
for item in data_list:
temp_list.append([item])
data_list = temp_list
return data_list
def _generate_parameter_to_dict(self, parameters):
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def _analysis_case_parameters_to_value_empty_dict(self, request_demo, parameters, type=None):
"""
循环生成缺少某个参数的值,其它正确的参数组合列表
"""
parameter_expect_list = list()
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for key, parameter_attr in parameters.items():
if parameter_attr.normal_values:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
parameter_expect_dict[parameter_attr.name] = [
self._generate_body_expect_type_value(parameter_attr.type)]
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
parameter_un_expect_dict[parameter_attr.name] = [
self._generate_body_un_expect_type_value(parameter_attr.type)]
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
for key in parameter_expect_dict.keys():
if key not in json.dumps(request_demo):
continue
value = copy.copy(parameter_expect_dict)
value[key] = ['NULL']
if parameters.get(key).is_need == 1:
status = "FAIL"
else:
status = "PASS"
if type == "1102":
name = "缺少参数%s" % key
else:
name = "缺少参数%s的值" % key
parameter_expect_list.append([value, status, name])
return parameter_expect_list
def _distinguish_parameters_by_is_needed(self, parameters):
"""
将必填参数和非必须参数分开成2个列表
"""
need_parameters = list()
no_need_parameters = list()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.is_need != 0:
need_parameters.append(parameter_attr)
else:
no_need_parameters.append(parameter_attr)
return need_parameters, no_need_parameters
def _replace_null_value_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s[\[\{]" % parameter_attr.name
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, really_body)
return really_body
def generate_case_body_all_1101_array(self, request_demo, parameters):
"""
生成1101用例对应的所有请求参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_value_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数为空"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search("\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = "\"%s\": []" % parameter_attr.name
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = "\"%s\": \"NULL\"" % parameter_attr.name
really_body = re.sub(patten1, replace1, demo_json_copy)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def _clean_all_none_json_char(self, really_body):
"""
清除1102场景删除缺少参数后不规则的字符串内容
"""
really_body = re.sub(",\s,", ",", really_body)
really_body = re.sub("\[\s?,", "[", really_body)
really_body = re.sub("\{\s?,", "{", really_body)
really_body = re.sub(",\s\]", "]", really_body)
really_body = re.sub(",\s\}", "}", really_body)
really_body = re.sub("\s,", "", really_body)
return really_body
def _replace_null_all_no_need_parameters(self, demo, no_need_parameter_attrs):
"""
将所有非必填参数置空
"""
really_body = copy.deepcopy(demo)
for parameter_attr in no_need_parameter_attrs:
if re.search(r"\"%s\"" % parameter_attr.name, demo):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo):
continue
try:
expect_value = parameter_attr.normal_values.split(",")[0]
except Exception as err:
log.error(err)
raise Exception("接口参数%s无正常值,请先确认接口参数。" % parameter_attr.name)
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
replace1 = ""
really_body = re.sub(patten1, replace1, really_body)
really_body = self._clean_all_none_json_char(really_body)
return really_body
def generate_case_body_all_1102_array(self, request_demo, parameters):
"""
生成1102用例对应的所有参数组合
"""
body_array = list()
need, no_need = self._distinguish_parameters_by_is_needed(parameters)
demo_json = json.dumps(request_demo, ensure_ascii=False)
if no_need:
no_need_demo_json = self._replace_null_all_no_need_parameters(demo_json, no_need)
body_array.append([json.loads(no_need_demo_json), "PASS", "所有非必填参数均缺失"])
for item in need:
parameter_attr = copy.copy(item)
demo_json_copy = copy.copy(demo_json)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json_copy):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json_copy):
continue
name = "缺少参数%s" % parameter_attr.name
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = ""
really_body = re.sub(patten1, replace1, demo_json_copy)
really_body = self._clean_all_none_json_char(really_body)
if parameter_attr.is_need == 1:
status = "FAIL"
else:
status = "PASS"
body_array.append([json.loads(really_body), status, name])
return body_array
def generate_case_body_all_1201_array(self, request_demo, parameters):
"""
生成1201用例对应的所有参数组合
"""
body_array = list()
demo_json = json.dumps(request_demo, ensure_ascii=False)
for item in parameters:
parameter_attr = SetAttr(item)
if re.search(r"\"%s\"" % parameter_attr.name, demo_json):
check_para = r"\"%s\":\s\{|\"%s\":\s\[\{" % (parameter_attr.name, parameter_attr.name)
if re.search(check_para, demo_json):
continue
for un_expect_item in self.un_expect_list.get(parameter_attr.name):
name = "参数%s使用异常值%s" % (parameter_attr.name, un_expect_item)
demo_json_copy = copy.copy(demo_json)
expect_value = parameter_attr.normal_values.split(",")[0]
if parameter_attr.type == "array":
patten1 = r"\"%s\":\s\[\"%s\"\]" % (parameter_attr.name, expect_value)
replace1 = '"%s": ["%s"]' % (parameter_attr.name, un_expect_item)
else:
patten1 = r"\"%s\":\s\"%s\"" % (parameter_attr.name, expect_value)
patten1 = patten1.replace("{", r"\{")
patten1 = patten1.replace("}", r"\}")
patten1 = patten1.replace("[", r"\[")
patten1 = patten1.replace("]", r"\]")
patten1 = patten1.replace("$", r"\$")
replace1 = '"%s": "%s"' % (parameter_attr.name, un_expect_item)
really_body = re.sub(patten1, replace1, demo_json_copy)
status = "FAIL"
body_array.append([json.loads(really_body), status, name])
return body_array
def create_case_content(self, result_path, interface_info, parameters, demo, case_type, tags=None):
"""
生成用例的所有内容
"""
self._check_interface_confirm(interface_info)
body_content = list()
doc = self.generate_case_doc(interface_info, tags=tags, demo=demo)
case_file, _ = self.generate_case_file_path(interface_info)
interface_name = interface_info.name
if not parameters:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, None, status="PASS")
else:
try:
body_array = self.generate_case_body_all_1001_array(demo, parameters)
self.normal_1001 = body_array[0]
except TypeError:
log.warning(traceback.print_exc())
body_array = []
self.normal_1001 = {}
if case_type == "1001":
try:
body = self.generate_case_body_content(interface_info.in_url, doc, interface_name, case_type,
interface_info.interface_describe, body_array, status="PASS")
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
elif case_type == "1101" or case_type == "1102" or case_type == "1201":
body_temp_list = []
# parameters_temp = self._generate_parameter_to_dict(parameters)
try:
body_array = eval(
"self.generate_case_body_all_%s_array(self.normal_1001, parameters)" % case_type)
if case_type == "1101":
name = "缺少参数值"
elif case_type == "1102":
name = "缺少参数key和值"
elif case_type == "1201":
name = "参数使用异常值"
else:
name = ""
body_tmp = self.generate_case_body_content(interface_info.in_url, doc, interface_name,
case_type,
name, body_array, status="PASS")
body_temp_list.append(body_tmp)
body = "".join(body_temp_list)
except Exception as err:
msg = "生成用例%s,%s失败原因如下:%s" % (interface_info.type, interface_info.in_url, traceback.print_exc())
log.error(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
raise err
body_content.append(body)
self.file.write_content_to_case_file(case_file, "\n".join(body_content))
msg = "接口url%s,%s,生成case%s成功!路径为:%s" % (interface_info.type, interface_info.in_url, case_type, case_file)
log.info(msg)
self.file.write_content_to_case_file(result_path, msg + "\n")
return msg
def generate_case_body_content(self, url, doc, interface_name, case_type, description, body_para, status="PASS"):
body_content = list()
case_name = self._generate_case_name(interface_name, case_type, description)
body_content.append(case_name)
body_content.append(doc)
# 如何需要自动生成1001类型用例的请求body数据请将304/305行放开并把306~310注释
request = self._generate_case_body_content(url, body_content=body_para, status=status)
body_content.append(request)
# if case_type != "1001":
# request = self._generate_case_body_content(url, body_content=body_para, status=status)
# body_content.append(request)
# else:
# body_content.append(" #请自行添加用例请求数据!\n")
return "\n".join(body_content)
def _generate_case_body_content(self, url, body_content=None, status="PASS"):
body_list = []
if body_content is None:
try:
body_request = " ${resp} %s" % (self.file.all_url.get(url)[1])
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_request)
body_list.append(" Log ${resp}")
if self.team.upper() in ["TMO"]:
body_validate_success = " check_rf ${resp} %s" % '请自行添加检查结果'
else:
body_validate_code = " Should Be Equal ${resp['code']} ${200}"
body_validate_success = " Should Be True ${resp['success']}"
body_list.append(body_validate_code)
body_list.append(body_validate_success)
else:
if not body_content:
body_create = " #request_demo无内容或者parameters在数据表中不全请先检查再生成 \n ${body} Evaluate %s" % body_content
body_list.append(body_create)
count = 0
for real_body in body_content:
result = {"message": "", "code": 200, "success": True}
temp = copy.copy(real_body)
if len(real_body) == 3 and isinstance(real_body, list):
if real_body[1] == "PASS" or real_body[1] == "FAIL":
real_body = temp[0]
status = temp[1]
case_description = temp[2]
body_list.append(" #%s" % case_description)
try:
module = load_module_from_file(self.file.kw.kw_file_path, self.team)
importlib.reload(module)
if isinstance(real_body, list):
temp_type = "*"
else:
temp_type = "**"
result = eval(
"%s.%s().%s(%sreal_body)" % (
"module", self.file.kw_class_name, self.file.all_url.get(url)[1], temp_type))
except:
msg = "#url: %s 无法通过参数得到请求结果!" % url
log.warning(real_body)
log.warning(msg)
log.warning(traceback.print_exc())
if "${time}" in str(real_body):
body_list.append(" ${time} get time epoch")
body_create = " ${body_%s} Evaluate %s" % (count, real_body)
try:
if isinstance(real_body, list):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "@{body_%s}" % count)
elif isinstance(real_body, dict):
body_request = " ${resp_%s} %s %s" % (
count, self.file.all_url.get(url)[1], "&{body_%s}" % count)
except Exception as err:
log.error(traceback.print_exc())
body_request = " #url: %s 无法找到对应的关键字,请检查并更新!" % url
body_list.append(body_create)
body_list.append(body_request)
body_list.append(" Log ${resp_%s}" % count)
if self.team.upper() in ["TMO"]:
body_validate_resp = " check_rf ${resp_%s} %s" % (count, result)
else:
body_validate_code = " Should Be Equal as strings ${resp_%s['code']} ${%s}"
# body_validate_success = " %s ${resp_%s['success']}"
body_validate_success = " %s ${resp_%s['success']} %s"
body_validate_resp = " %s ${resp_%s[\"message\"]} %s"
if isinstance(result, str):
body_validate_code = " #特殊接口返回!"
body_validate_success = " #返回为bytes类型的字符码。仅检查结果是否相等"
body_validate_resp = " Should Be Equal as strings ${resp_%s} %s" % (count, result)
elif status == "PASS":
body_validate_code = body_validate_code % (count, result['code'])
body_validate_success = body_validate_success % ("Should Be True", count, "")
try:
if result["message"]:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
else:
try:
body_validate_code = body_validate_code % (count, result['code'])
except Exception as e:
if "status" in result:
body_validate_code = body_validate_code % (count, result['status'])
body_validate_code = body_validate_code.replace("code", "status")
else:
raise KeyError("接口不中无code返回请与开发确认")
try:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["success"])
except Exception as err:
if "error" in result:
body_validate_success = body_validate_success % (
"Should Be Equal As Strings", count, result["error"])
body_validate_success = body_validate_success.replace("success", "error")
else:
raise KeyError("接口不中无success返回请与开发确认")
try:
if result.get("message"):
if re.search("[a-zA-Z0-9]{32}", result.get("message", "")):
body_validate_resp = body_validate_resp % ("should contain", count,
re.sub("[a-zA-Z0-9]{32}", "", result["message"]))
else:
body_validate_resp = body_validate_resp % (
"should be equal as strings", count, result["message"])
else:
body_validate_resp = body_validate_resp % (
"Should Be Equal as strings", count, '${EMPTY}')
except KeyError as err:
body_validate_resp = " #resp中没有message请后续自行添加"
log.warning(err)
log.warning("resp中没有message请后续自行添加")
body_list.append(body_validate_code)
body_list.append(body_validate_success)
body_list.append(body_validate_resp)
# body_list.append(" #请自行添加data校验")
count += 1
body_list.append("\n")
return "\n".join(body_list)
def _generate_body_expect_type_value(self, type):
if type.lower() == 'integer':
value = random.randint(0, 100)
elif type.lower() == "string":
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "date":
value = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
elif type.lower() == "boolean":
value = True
elif type.lower() == "array":
value = [1]
else:
value = "123"
return value
def _generate_body_un_expect_type_value(self, type):
value = ""
if type.lower() == 'integer':
value = "".join([random.choice(string.digits + string.ascii_letters) for i in range(5)])
elif type.lower() == "string":
value = random.randint(0, 10000)
else:
value = False
return value
def _analysis_dict_to_body_value(self, dict_none, dict_data):
"""
生成所有请求参数的组合列表
"""
data_list = []
if isinstance(dict_none, list):
if dict_none:
for index in range(len(dict_none)):
data = self._analysis_dict_to_body_value(dict_none[index], dict_data[index])
data_list.extend(data)
else:
data_list = dict_data
elif isinstance(dict_none, dict):
try:
len_num = self._get_nums(dict_data)
except Exception as e:
# log.error(e)
log.error("数据表request_parameters中参数不全请使用interface_hunter补全参数")
raise e
for i in range(0, len_num):
copy_dict = copy.copy(dict_none)
for key, value in copy_dict.items():
data = self._analysis_dict_to_body_value(copy_dict[key], dict_data[key])
if not data:
copy_dict[key] = None
elif i >= len(data):
copy_dict[key] = data[0]
if isinstance(value, list):
copy_dict[key] = [data[0]]
else:
copy_dict[key] = data[i]
if isinstance(value, list):
copy_dict[key] = [data[i]]
data_list.append(copy_dict)
else:
data_list = dict_data
return data_list
def _get_nums(self, dict_data):
"""
获取所有参数对应值的最大长度
"""
nums_list = [0]
if isinstance(dict_data, list):
for item in dict_data:
if isinstance(item, dict):
data = self._get_nums(dict_data[0])
nums_list.append(data)
else:
nums_list.append(len(dict_data))
elif isinstance(dict_data, dict):
for value in dict_data.values():
data = self._get_nums(value)
nums_list.append(data)
elif dict_data == None:
nums_list.append(0)
else:
nums_list.append(len(dict_data))
return max(nums_list)
def _analysis_case_parameters_to_value(self, interface_demo, parameters):
"""
根据demo将每个参数的对应值的列表写回demo
"""
body = copy.copy(interface_demo)
if isinstance(body, list):
for index in range(len(body)):
item = body[index]
body[index] = self._analysis_case_parameters_to_value(item, parameters)
elif isinstance(body, dict):
for key, value in body.items():
if isinstance(value, dict) or isinstance(value, list):
if value:
body[key] = self._analysis_case_parameters_to_value(body.get(key), parameters)
else:
body[key] = parameters.get(key)
else:
body[key] = parameters.get(key)
return body
def _analysis_case_parameters_to_dict(self, parameters):
"""
根据所有参数生成正常值 和异常值的列表
"""
expect_lenth = 0
un_expect_lenth = 0
parameter_expect_dict = dict()
parameter_un_expect_dict = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
if parameter_attr.normal_values:
if parameter_attr.normal_values.startswith("["):
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
if parameter_attr.type == "array":
parameter_expect_dict[parameter_attr.name] = [[x] for x in parameter_attr.normal_values.split(",")]
else:
parameter_expect_dict[parameter_attr.name] = parameter_attr.normal_values.split(",")
else:
continue
if len(parameter_expect_dict[parameter_attr.name]) > expect_lenth:
expect_lenth = len(parameter_expect_dict[parameter_attr.name])
if parameter_attr.exception_values:
parameter_un_expect_dict[parameter_attr.name] = parameter_attr.exception_values.split(",")
else:
# raise Exception("接口参数%s无异常值请先确认接口参数。" % parameter_attr.name)
parameter_un_expect_dict[parameter_attr.name] = []
if len(parameter_un_expect_dict[parameter_attr.name]) > un_expect_lenth:
un_expect_lenth = len(parameter_un_expect_dict[parameter_attr.name])
return parameter_expect_dict, parameter_un_expect_dict, expect_lenth, un_expect_lenth
def generate_case_doc(self, interface_info, tags=None, demo=None):
if demo:
lenth, demo_json, paras_d = self.file.kw._clean_demo_to_less_key(demo)
doc_list = list()
doc_list.append(
" [Documentation] {} 接口路径:{}, {}".format(interface_info.interface_describe,
interface_info.type, interface_info.in_url))
# doc_list.append(self.doc_content_row_temp.format("输入参数名", "参数类型", "说明"))
# for parameters_info in parameters:
# sub_doc = self._generate_case_doc(parameters_info)
# doc_list.append(sub_doc)
if tags:
tag_list = tags.split(",")
tag = " [Tags]"
for tag_ in tag_list:
tag += " %s" % tag_
doc_list.append(tag)
if lenth > 1:
doc_list.append(
" # 请求参数说明 h:放header里的参数 u:放url路径节点中的参数 d:不放在url中在请求参数 p放在url问号后在key-value结构参数")
return "\n".join(doc_list)
def _generate_case_doc(self, request_parameter):
doc_content = list()
parameter_attr = SetAttr(request_parameter)
row = self.doc_content_row_temp.format(parameter_attr.name, parameter_attr.type, parameter_attr.note)
doc_content.append(row)
return "\n".join(doc_content)
def generate_case_file_path(self, interface_info):
path_cell_list = self._generate_case_file_path_info(interface_info.in_url)
path_cell_list.insert(0, self.file.case_dir)
return os.path.abspath(os.path.join(*path_cell_list[:-1]) + ".robot"), path_cell_list[-1]
def _generate_case_file_path_info(self, url):
head_list = []
para_list = []
host, url_last = re.search(r"(\S+\.cn|\S+\.com)(\S+)", url).groups()
path_first = re.search("//([\w+\-]+)\.", host).group(1)
head_list.append(path_first)
search = re.findall("[\w+\-_]+|[\{\w+\-_\}]+", url_last)
for item in search:
if "{" in item:
temp = item.replace("{", "").replace("}", "")
para_list.append(temp)
else:
head_list.append(item)
return head_list
def run_interface_case_generate(result_path, interface, demo_info, parameters, case_instance, tags=None, normal=None):
case_type_list = ["1001", "1101", "1102", "1201"]
not_exist_case_type = list()
exist_case_name = list()
case_path, _ = case_instance.generate_case_file_path(interface)
case_name_head = interface.name
if parameters:
for case_type in case_type_list:
case_name = "-".join([case_name_head, case_type])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append(case_type)
else:
exist_case_name.append(case_name)
else:
case_name = "-".join([case_name_head, "1001"])
if not case_instance.file.check_case_exist(case_path, case_name):
not_exist_case_type.append("1001")
else:
exist_case_name.append(case_name)
for item in exist_case_name:
msg = "接口url%s,%s,已存在用例%s,请确认!" % (interface.type, interface.in_url, item)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")
if not_exist_case_type:
if normal:
not_exist_case_type = ["1001"]
case_instance.file.generate_case_file(case_path)
try:
demo = json.loads(demo_info.request_demo)
except Exception as err:
log.error(traceback.print_exc())
log.warning("the demo of interface(%s) is empty,please check." % interface.id)
demo = dict()
try:
result = list()
result.append("")
result.append("-" * 60)
result.append("用例生成成功!")
for case_type in not_exist_case_type:
body = case_instance.create_case_content(result_path, interface, parameters, demo, case_type, tags=tags)
# result.append(body)
result.append(body.split("")[1])
log.info("\n".join(result))
except:
log.error("could not generate case for interface id: %s, url: %s" % (interface.id, interface.in_url))
log.error(traceback.print_exc())
else:
msg = "接口url%s,%s,已存在用例,请确认!" % (interface.type, interface.in_url)
log.warning(msg)
case_instance.file.write_content_to_case_file(result_path, msg + "\n")

View File

@@ -0,0 +1,3 @@
目录结构说明:
使用者:王刚
用途存放自动生成RF层用例的脚本

View File

@@ -0,0 +1,7 @@
# -*- coding:utf-8 -*-
"""
Author: linyupeng
Email: linyupeng@huohua.cn
Create Data: 2021/4/9 19:08
"""

View File

@@ -0,0 +1,266 @@
# -*- coding:utf-8 -*-
import argparse
import copy
import datetime
import json
import os
import sys
import time
from base_framework.platform_tools.Keywords_service.keyword_service import SetAttr, SwaggerDBInfo, \
KWFileOperation, log, KWOperation, run_keyword_generage, Project_Path
# from base_framework.platform_tools.Swagger_scanner.scanner import SwaggerOnlineDebug
from base_framework.platform_tools.Testcase_service.case_service import ITCaseContentOperation, \
run_interface_case_generate, load_module_from_file
from base_framework.base_config.current_pth import *
from base_framework.public_tools.read_config import ReadConfig
from base_framework.platform_tools.Swagger_scanner.scanner_add import GenerateBody
dir_name = os.path.dirname(__file__)
interface_path = os.path.join(dir_name, "Swagger_scanner/interface.txt")
token_header = ["accesstoken", "user-token"]
def get_all_interface_info_from_file(path):
with open(path, encoding="UTF-8") as f:
content = f.readlines()
return content[1:]
def write_interface_info_to_file(content, path=interface_path):
with open(path, mode="r", encoding="UTF-8") as f:
line = f.readline().strip("\n")
with open(path, mode="w", encoding="UTF-8") as f1:
f1.write(line + "\n" + content)
def generate_result_file():
file_name = "reslut_%s.txt" % time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
result_dir = os.path.join(dir_name, "case_result")
result_file = os.path.abspath(os.path.join(result_dir, file_name))
if not os.path.exists(result_dir):
os.makedirs(result_dir)
with open(result_file, "w+", encoding="UTF-8") as f:
f.write("开始自动生成keyword和case! 开始时间:%s\n" % time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()))
return result_file
def write_case_result_to_case_file(file_path, content):
with open(file_path, "a+", encoding="UTF-8") as f:
lines = f.readlines()
text = content + "\n"
if lines:
if lines[-1] != "\n":
text = "\n" + "\n" + content + "\n"
f.write(text)
def clean_the_interface_info(result_file, interface_info):
interface = tuple(interface_info.strip("\n").split(","))
interface_infos = SwaggerDBInfo.get_interface_info_by_url_and_type(interface[1], interface[0], interface[2])
if len(interface_infos) > 1:
msg = "接口url%s,%s,存在多条数据,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif len(interface_infos) == 0:
msg = "接口url%s,%s,在数据库中不存在,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
interface_info_attr = SetAttr(interface_infos[0])
if interface_info_attr.is_used == 0:
msg = "接口url%s,%s,%s,已不在使用,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif interface_info_attr.at_numbers > 0:
msg = "接口url%s,%s,%s,已存在用例,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
msg = "接口url%s,%s,%s,开始生成接口关键字和用例!" % interface
log.info(msg)
write_case_result_to_case_file(result_file, msg)
return interface_info_attr
return msg
def generate_swagger_url_and_option(swagger_info):
swagger_info_attr = SetAttr(swagger_info)
url = swagger_info_attr.sw_url
temp_list = url.split("v2")
swagger_url = temp_list[0] + 'swagger-ui.html'
option = '/v2' + temp_list[1]
return swagger_url, option, temp_list[0]
def get_demo_by_interface_info(interface_id):
body_demo = GenerateBody(interface_id)
request_data = body_demo.combine_request_parameters()
if request_data["d"]:
request_demo = body_demo.combine_request_data(request_data["d"][0], request_data["d"][1], key=0)
request_data["d"] = request_demo
return request_data
def clear_request_parameters(parameters):
"""
清除参数中作为token的参数
"""
parameters_result = copy.deepcopy(parameters)
for item in parameters:
if item.get("name").lower() in token_header:
parameters_result.remove(item)
return parameters_result
def clear_header_token_demo(demo_info):
"""
清除请求头中带有token这种无用参数
"""
demo_copy = json.loads(copy.deepcopy(demo_info.request_demo))
header = copy.deepcopy(demo_copy.get('h'))
for key, _ in demo_copy.get('h').items():
if key in token_header:
header.pop(key)
if not header:
demo_copy['h'] = {}
else:
demo_copy['h'] = header
demo_info.request_demo = json.dumps(demo_copy)
return demo_info
def generate_parameter_to_dict(parameters):
"""
将数据库查询到的参数以参数名组成字典
"""
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def clean_un_use_parameter(demo, parameters):
"""
删除不再使用的参数
"""
if isinstance(demo, SetAttr):
demo_item = json.loads(demo.request_demo)
demo_copy = copy.deepcopy(demo_item)
else:
demo_item = copy.deepcopy(demo)
demo_copy = copy.deepcopy(demo)
parameters_dict = generate_parameter_to_dict(parameters)
if isinstance(demo, list):
demo_copy.clear()
for index in range(len(demo_item)):
demo_copy.append(clean_un_use_parameter(demo_item[index], parameters))
elif isinstance(demo_item, dict):
for key, values in demo_item.items():
if parameters_dict.get(key, None) and int(parameters_dict.get(key).is_need) == 2:
demo_copy.pop(key)
continue
demo_copy[key] = clean_un_use_parameter(values, parameters)
else:
demo_copy = demo_copy
return demo_copy
def confirm_demo_value_string(kw, case, demo_string, parameters):
"""
确认demo中的请求参数是不是纯值并非json说格式
"""
demo = copy.deepcopy(demo_string.request_demo)
demo_value = json.loads(demo)
if (not demo_value["d"]) and (demo_value["d"] != 0):
return demo_value
if (isinstance(demo_value["d"], str) or isinstance(demo_value["d"], int)):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif isinstance(demo_value["d"], list):
if not isinstance(demo_value["d"][0], dict):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif 'empty' in demo_value['d']:
kw.kw_array = demo_value['d'].pop('empty')
return demo_value
def confirm_correct_team_case(team):
interface_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
try:
module = load_module_from_file(interface_file_path, team.upper())
real_team = team.upper()
except Exception as err:
real_team = team.lower()
return real_team
def run_interface_case(team, tags=None, platform=None, server=None, driver=None, normal=None):
team = confirm_correct_team_case(team)
kw_instance = KWFileOperation(team)
result_file = generate_result_file()
interface_contents = get_all_interface_info_from_file(interface_path)
keyword = KWOperation(team, kw_instance, server)
case = ITCaseContentOperation(team, kw_instance)
for interface_temp in interface_contents:
if interface_temp == "\n" or interface_temp.startswith("#"):
continue
interface = clean_the_interface_info(result_file, interface_temp)
if not isinstance(interface, str):
parameters = keyword.get_interface_parameters(interface.id)
parameters = clear_request_parameters(parameters)
if parameters:
demo = get_demo_by_interface_info(interface.id)
else:
demo = {"d": {}, "h": {}, "p": {}, "u": {}}
demo = [{"request_demo": json.dumps(demo)}]
demo_info = SetAttr(demo[0])
demo_info = clear_header_token_demo(demo_info)
demo_info.request_demo = json.dumps(clean_un_use_parameter(demo_info, parameters))
demo_info.request_demo = json.dumps(confirm_demo_value_string(keyword, case, demo_info, parameters))
kw_status = run_keyword_generage(result_file, interface, demo_info, parameters, keyword)
if kw_status:
msg = "接口url%s,%s,生成keyword成功接下来继续生成用例!" % (interface.type, interface.in_url)
log.info(msg)
write_case_result_to_case_file(result_file, msg)
run_interface_case_generate(result_file, interface, demo_info, parameters, case, tags, normal)
return True
else:
return interface
def print_usage():
parser_inst.print_usage()
parser_inst.exit(1, "生成用例失败,请根据使用帮助传入正确参数。")
def write_platform_to_running_env(platform, team):
if not platform:
platform = ""
cfg_opt = ReadConfig(env_choose_path)
cfg_opt.set_section("run_jira_id", "huohua-podenv", platform)
cfg_opt.set_section("run_evn_name", "current_team", team)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="请输入组名!")
parser.add_argument("-team", dest='team', help="TO or TMO")
parser.add_argument("-tag", dest='tags', help="标签", default=None)
parser.add_argument("-p", dest='platform', help="独立环境", default=None)
parser.add_argument("-s", dest='server', help="服务名", default=None)
parser.add_argument("-d", dest='driver', help="驱动路径", default=None)
parser.add_argument("-n", dest='normal', help="是否只生成正常用例", default=None)
args = parser.parse_args()
global parser_inst
parser_inst = parser
if not args.team:
print_usage()
else:
write_platform_to_running_env(args.platform, args.team)
run_interface_case(args.team, args.tags, args.platform, args.server, args.driver, args.normal)

View File

@@ -0,0 +1,264 @@
# -*- coding:utf-8 -*-
import argparse
import copy
import datetime
import json
import os
import sys
import time
from base_framework.platform_tools.Keywords_service.keyword_service1 import SetAttr, SwaggerDBInfo, \
KWFileOperation, log, KWOperation, run_keyword_generage, Project_Path
# from base_framework.platform_tools.Swagger_scanner.scanner import SwaggerOnlineDebug
from base_framework.platform_tools.Testcase_service.case_service1 import ITCaseContentOperation, \
run_interface_case_generate, load_module_from_file
from base_framework.base_config.current_pth import *
from base_framework.public_tools.read_config import ReadConfig
from base_framework.platform_tools.Swagger_scanner.scanner_add import GenerateBody
dir_name = os.path.dirname(__file__)
interface_path = os.path.join(dir_name, "Swagger_scanner/interface.txt")
token_header = ["accesstoken", "user-token"]
def get_all_interface_info_from_file(path):
with open(path, encoding="UTF-8") as f:
content = f.readlines()
return content[1:]
def write_interface_info_to_file(content, path=interface_path):
with open(path, mode="r", encoding="UTF-8") as f:
line = f.readline().strip("\n")
with open(path, mode="w", encoding="UTF-8") as f1:
f1.write(line + "\n" + content)
def generate_result_file():
file_name = "reslut_%s.txt" % time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime())
result_dir = os.path.join(dir_name, "case_result")
result_file = os.path.abspath(os.path.join(result_dir, file_name))
if not os.path.exists(result_dir):
os.makedirs(result_dir)
with open(result_file, "w+", encoding="UTF-8") as f:
f.write("开始自动生成keyword和case! 开始时间:%s\n" % time.strftime("%Y-%m-%d_%H:%M:%S", time.localtime()))
return result_file
def write_case_result_to_case_file(file_path, content):
with open(file_path, "a+", encoding="UTF-8") as f:
lines = f.readlines()
text = content + "\n"
if lines:
if lines[-1] != "\n":
text = "\n" + "\n" + content + "\n"
f.write(text)
def clean_the_interface_info(result_file, interface_info, team):
interface = tuple(interface_info.strip("\n").split(","))
interface_infos = SwaggerDBInfo.get_interface_info_by_url_and_type(interface[1], interface[0], team)
if len(interface_infos) > 1:
msg = "接口url%s,%s,存在多条数据,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif len(interface_infos) == 0:
msg = "接口url%s,%s,在数据库中不存在,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
interface_info_attr = SetAttr(interface_infos[0])
if interface_info_attr.is_used == 0:
msg = "接口url%s,%s,已不在使用,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
elif interface_info_attr.at_numbers > 0:
msg = "接口url%s,%s,已存在用例,请确认!" % interface
log.warning(msg)
write_case_result_to_case_file(result_file, msg)
else:
msg = "接口url%s,%s,开始生成接口关键字和用例!" % interface
log.info(msg)
write_case_result_to_case_file(result_file, msg)
return interface_info_attr
return msg
def generate_swagger_url_and_option(swagger_info):
swagger_info_attr = SetAttr(swagger_info)
url = swagger_info_attr.sw_url
temp_list = url.split("v2")
swagger_url = temp_list[0] + 'swagger-ui.html'
option = '/v2' + temp_list[1]
return swagger_url, option, temp_list[0]
def get_demo_by_interface_info(interface_id):
body_demo = GenerateBody(interface_id)
request_data = body_demo.combine_request_parameters()
if request_data["d"]:
request_demo = body_demo.combine_request_data(request_data["d"][0], request_data["d"][1], key=0)
request_data["d"] = request_demo
return request_data
def clear_request_parameters(parameters):
"""
清除参数中作为token的参数
"""
parameters_result = copy.deepcopy(parameters)
for item in parameters:
if item.get("name").lower() in token_header:
parameters_result.remove(item)
return parameters_result
def clear_header_token_demo(demo_info):
"""
清除请求头中带有token这种无用参数
"""
demo_copy = json.loads(copy.deepcopy(demo_info.request_demo))
header = copy.deepcopy(demo_copy.get('h'))
for key, _ in demo_copy.get('h').items():
if key in token_header:
header.pop(key)
if not header:
demo_copy['h'] = {}
else:
demo_copy['h'] = header
demo_info.request_demo = json.dumps(demo_copy)
return demo_info
def generate_parameter_to_dict(parameters):
"""
将数据库查询到的参数以参数名组成字典
"""
dict_temp = dict()
for parameter in parameters:
parameter_attr = SetAttr(parameter)
dict_temp[parameter_attr.name] = parameter_attr
return dict_temp
def clean_un_use_parameter(demo, parameters):
"""
删除不再使用的参数
"""
if isinstance(demo, SetAttr):
demo_item = json.loads(demo.request_demo)
demo_copy = copy.deepcopy(demo_item)
else:
demo_item = copy.deepcopy(demo)
demo_copy = copy.deepcopy(demo)
parameters_dict = generate_parameter_to_dict(parameters)
if isinstance(demo, list):
demo_copy.clear()
for index in range(len(demo_item)):
demo_copy.append(clean_un_use_parameter(demo_item[index], parameters))
elif isinstance(demo_item, dict):
for key, values in demo_item.items():
if parameters_dict.get(key, None) and int(parameters_dict.get(key).is_need) == 2:
demo_copy.pop(key)
continue
demo_copy[key] = clean_un_use_parameter(values, parameters)
else:
demo_copy = demo_copy
return demo_copy
def confirm_demo_value_string(kw, case, demo_string, parameters):
"""
确认demo中的请求参数是不是纯值并非json说格式
"""
demo = copy.deepcopy(demo_string.request_demo)
demo_value = json.loads(demo)
if (not demo_value["d"]) and (demo_value["d"] != 0):
return demo_value
if (isinstance(demo_value["d"], str) or isinstance(demo_value["d"], int)):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
elif isinstance(demo_value["d"], list):
if not isinstance(demo_value["d"][0], dict):
demo_value["d"] = {parameters[0].get("name"): demo_value["d"]}
kw.kw_string = True
case.case_string = True
return demo_value
def confirm_correct_team_case(team):
interface_file_path = os.path.abspath(os.path.join(Project_Path,
"{team}".format(team=team), "library",
"{team}_interface.py".format(team=team.upper())))
try:
module = load_module_from_file(interface_file_path, team.upper())
real_team = team.upper()
except Exception as err:
real_team = team.lower()
return real_team
def run_interface_case(team, tags=None, server=None, normal=None):
team = confirm_correct_team_case(team)
kw_instance = KWFileOperation(team, server)
result_file = generate_result_file()
interface_contents = get_all_interface_info_from_file(interface_path)
keyword = KWOperation(team, kw_instance)
case = ITCaseContentOperation(team, kw_instance)
for interface_temp in interface_contents:
if interface_temp == "\n" or interface_temp.startswith("#"):
continue
interface = clean_the_interface_info(result_file, interface_temp, team)
if not isinstance(interface, str):
parameters = keyword.get_interface_parameters(interface.id)
parameters = clear_request_parameters(parameters)
if parameters:
demo = get_demo_by_interface_info(interface.id)
else:
demo = {"d": {}, "h": {}, "p": {}, "u": {}}
demo = [{"request_demo": json.dumps(demo)}]
demo_info = SetAttr(demo[0])
demo_info = clear_header_token_demo(demo_info)
demo_info.request_demo = json.dumps(clean_un_use_parameter(demo_info, parameters))
demo_info.request_demo = json.dumps(confirm_demo_value_string(keyword, case, demo_info, parameters))
kw_status = run_keyword_generage(result_file, interface, demo_info, parameters, keyword)
if kw_status:
msg = "接口url%s,%s,生成keyword成功接下来继续生成用例!" % (interface.type, interface.in_url)
log.info(msg)
write_case_result_to_case_file(result_file, msg)
run_interface_case_generate(result_file, interface, demo_info, parameters, case, tags, normal)
return True
else:
return interface
def print_usage():
parser_inst.print_usage()
parser_inst.exit(1, "生成用例失败,请根据使用帮助传入正确参数。")
def write_platform_to_running_env(platform, team):
if not platform:
platform = ""
cfg_opt = ReadConfig(env_choose_path)
cfg_opt.set_section("run_jira_id", "huohua-podenv", platform)
cfg_opt.set_section("run_evn_name", "current_team", team)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="请输入组名!")
parser.add_argument("-team", dest='team', help="TO or TMO")
parser.add_argument("-tag", dest='tags', help="标签", default=None)
parser.add_argument("-p", dest='platform', help="独立环境", default=None)
parser.add_argument("-s", dest='server', help="服务名", default=None)
parser.add_argument("-d", dest='driver', help="驱动路径", default=None)
parser.add_argument("-n", dest='normal', help="是否只生成正常用例", default=None)
args = parser.parse_args()
global parser_inst
parser_inst = parser
if not args.team:
print_usage()
else:
write_platform_to_running_env(args.platform, args.team)
run_interface_case(args.team, args.tags, args.platform, args.server, args.driver, args.normal)

View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
__author__ = 'huaxuemin'
import argparse
from runner9 import CaseRunner
usage_info = '''
usage:
$ case_runner.py -w %WORKSPACE% -c %case_path% -t tc1,tc2 -i p0,p1,p2 -e norun,del -r True
run tag cases in workspace
'''
def usage():
print(usage_info)
exit(-1)
def get_test_case_name(build_id):
try:
if build_id:
import pymysql
db = pymysql.connect(host="mysql.qa.huohua.cn", user="qa-dev", password="jaeg3SCQt0", database="sparkatp",
charset='utf8')
cursor = db.cursor()
get_sql = "SELECT test_case_id FROM sparkatp.build_params WHERE build_info_id={}".format(build_id)
cursor.execute(get_sql)
res = cursor.fetchone()
test_case_name = ""
if res:
test_case_name = res[0].replace("(", "*").replace(")", "*").replace(" ", "*")
cursor.close()
db.close()
return test_case_name
except Exception as e:
print(e)
return ""
def main():
parser = argparse.ArgumentParser(description="This is for introduction parameter")
parser.add_argument("-w", dest='workspace', help="case_runner workspace")
parser.add_argument("-c", dest='case_path', help="case path")
parser.add_argument("-t", dest='test_case', default='', help="test case")
parser.add_argument("-i", dest='include', default='', help="include tag")
parser.add_argument("-e", dest='exclude', default='', help="exclude tag")
parser.add_argument("-r", dest='rerun', default='true', help="rerun failed case")
parser.add_argument("-env", dest='special_env', default='', help="special env")
parser.add_argument("-team", dest='team', default='', help="job name with team ")
parser.add_argument("-penv", dest='physics_env', help="QA or SIM", default='QA')
parser.add_argument("-b", dest='business', help="hh or hhi", default='hh')
parser.add_argument("-b_id", dest='build_info_id', help="build_info_id", default='')
parser.add_argument("-b_url", dest='build_url', help="build_url", default='')
parser.add_argument("-is_db", dest='is_use_db', help="is_use_db", default='0')
args = parser.parse_args()
if args.workspace is None:
usage()
workspace = args.workspace
case_path = args.case_path
test_case = args.test_case
include = args.include
exclude = args.exclude
rerun = args.rerun
special_env = args.special_env
team = args.team.split("-")[0]
physics_env = args.physics_env
business = args.business
build_info_id = args.build_info_id
build_url = args.build_url
is_use_db = args.is_use_db
if str(is_use_db) == "1" and build_info_id:
test_case_name = get_test_case_name(build_info_id)
test_case = test_case_name if test_case_name else test_case
# if team not in ["CC", "EN", "H2R", "LaLive", "SCM", "TMO", "TO", "ASOP", "ASTWB", "ASORG"]:
# team = ""
case_runner = CaseRunner(workspace, case_path, test_case, include, exclude, rerun, special_env, team, physics_env,
business)
try:
# 更新独立环境代号到配置文件
case_runner.update_platform()
# 检查工作目录名字对应的端口,有就使用,没有就分配
case_runner.assign_port()
# 查找对应端口进程并杀掉
case_runner.check_port()
# 更新KWL和RF中的端口
case_runner.update_port()
# 更新重名测试套
case_runner.update_suite()
# 启动KWL
case_runner.start_kwl()
# 构建用例
case_runner.run_cases()
# 构建完成后停止KWL
case_runner.check_port()
except Exception as e:
print(e)
# 构建完成后入库build_url
case_runner.record_build_url(build_info_id, build_url)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,683 @@
# -*- coding: utf-8 -*-
__author__ = 'huaxuemin'
from logbook import Logger
import shutil
import errno
import os
import sqlite3
from pathlib import Path
import random
import socket
import configparser
import codecs
import time
import json
import re
import requests
logging = Logger(__name__)
HERE = os.path.dirname(os.path.abspath(__file__))
ROBOT_LOG_LEVEL = 'INFO'
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class OSType:
WIN, LINUX, UNKNOWN = range(3)
def __init__(self):
pass
@staticmethod
def get_type():
import platform
system_name = platform.system()
if system_name.lower() == 'windows':
return OSType.WIN
elif system_name.lower() == 'linux':
return OSType.LINUX
else:
return OSType.UNKNOWN
def run_process(cmd_str, out_p=False):
"""
run command
cmd_str unicode string.
"""
if OSType.WIN == OSType.get_type():
# cmd_str = cmd_str.encode('gbk')
cmd_str = cmd_str
elif OSType.LINUX == OSType.get_type():
cmd_str = cmd_str.encode('utf-8')
else:
raise RuntimeError("your os is not support.")
logging.info('cmd: %s' % cmd_str)
print(cmd_str)
import subprocess
from subprocess import PIPE
close_fds = False if OSType.WIN == OSType.get_type() else True
if out_p:
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=PIPE)
p.wait()
return p.returncode, p.stdout.read()
else:
p = subprocess.Popen(cmd_str, shell=True, close_fds=close_fds, stdout=subprocess.DEVNULL)
p.wait()
return p.returncode, None
class CaseRunner:
def __init__(self, workspace, case_path, test_case, include, exclude, rerun="true", special_env="", team="",
physics_env="QA", business="hh"):
self.workspace = workspace
self.case_path = case_path
self.test_case = test_case
self.include = include
self.exclude = exclude
self.rerun = rerun
self.special_env = special_env
self.team = team
self.physics_env = physics_env
self.business = business
self.con = sqlite3.connect(HERE + "/case_runner.db")
self.cur = self.con.cursor()
self.def_port = set("9" + "".join(map(str, random.choices(range(10), k=3))) for i in range(500))
self.env_port = None
self.pid = None
self.wait_time = 120
self.all_dir_name = set()
self.galaxy_server_name_to_swagger = {"peppa-teach-opt-cms-api": "teach-opt-cms-api",
"peppa-teach-biz-server": "peppa-teach-biz",
"peppa-market-server": "peppa-market",
"peppa-teach-parker-server": "peppa-teach-parker",
"peppa-course-server": "peppa-course"}
self.swagger_name_to_galaxy = {"teach-opt-cms-api": "peppa-teach-opt-cms-api",
"peppa-teach-biz": "peppa-teach-biz-server",
"peppa-market": "peppa-market-server",
"peppa-teach-parker": "peppa-teach-parker-server",
"peppa-course": "peppa-course-server"}
# 获取opengalaxy ssotoken相关变量
self.ops_uri = "http://opengalaxy.bg.huohua.cn"
self.showUsername = "luohong"
self.username = "luohong"
self.password = "Lh123456789@"
self.sso_login_url = "https://sso.huohua.cn/authentication/form"
self.redirect_url = "https://sso.huohua.cn/oauth/authorize?client_id=open-galaxy&response_type=code&redirect_uri=http://opengalaxy.bg.huohua.cn/api/v1/users/user/ssologin/"
def getSsoToken(self):
session = requests.session()
post_data = dict()
post_data['showUsername'] = self.showUsername
post_data['username'] = self.username
post_data['password'] = self.password
session.post(url=self.sso_login_url, data=post_data, allow_redirects=True, verify=False)
resp = session.get(
url=self.redirect_url,
allow_redirects=False,
verify=False)
resp1 = session.get(
url=resp.headers['Location'],
allow_redirects=False,
verify=False)
ssoToken = resp1.headers["Set-Cookie"].split(";")[4].split("=")[2]
return ssoToken
def update_platform(self):
if len(self.special_env) > 0:
config_file = str(Path(self.workspace) / "base_framework/base_config/env_choose.ini")
fd = open(config_file, encoding='utf-8')
data = fd.read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
files = codecs.open(config_file, "w")
files.write(data)
files.close()
fd.close()
cf = configparser.ConfigParser(allow_no_value=True)
cf.read(config_file, encoding='utf-8')
cf.set("run_jira_id", "huohua-podenv", self.special_env)
with open(config_file, 'w') as fw: # 循环写入
cf.write(fw)
def assign_port(self):
workspace_base_name = Path(self.workspace).name
query_port = "SELECT PORT FROM ENV_PORT WHERE NAME='{}'".format(workspace_base_name)
res_port = self.cur.execute(query_port).fetchall()
query_all_port = "SELECT PORT FROM ENV_PORT"
res_all_port = self.cur.execute(query_all_port).fetchall()
not_use_port = (self.def_port - set(str(item[0]) for item in res_all_port)).pop()
print("not use port is {}".format(not_use_port))
self.env_port = not_use_port
if len(res_port) == 0:
insert_name_port_sql = "INSERT INTO ENV_PORT(NAME,PORT)VALUES(?,?)"
self.cur.execute(insert_name_port_sql, (workspace_base_name, not_use_port)).fetchall()
self.con.commit()
else:
update_name_port_sql = "UPDATE ENV_PORT SET PORT='{}' WHERE NAME='{}'".format(self.env_port,
workspace_base_name)
self.cur.execute(update_name_port_sql).fetchall()
self.con.commit()
self.cur.close()
self.con.close()
print("use port is {}".format(self.env_port))
# def assign_port(self):
# workspace_base_name = Path(self.workspace).name
# query_port = "SELECT PORT FROM ENV_PORT WHERE NAME='{}'".format(workspace_base_name)
# res = self.cur.execute(query_port).fetchall()
# if len(res) == 0:
# query_all_port = "SELECT PORT FROM ENV_PORT"
# res_all_port = self.cur.execute(query_all_port).fetchall()
# not_use_port = (self.def_port - set(str(item[0]) for item in res_all_port)).pop()
# print("not use port is {}".format(not_use_port))
# insert_name_port_sql = "INSERT INTO ENV_PORT(NAME,PORT)VALUES(?,?)"
# self.cur.execute(insert_name_port_sql, (workspace_base_name, not_use_port)).fetchall()
# self.con.commit()
# self.env_port = not_use_port
# else:
# self.env_port = str(res[0][0])
# self.cur.close()
# self.con.close()
# print("use port is {}".format(self.env_port))
def _get_not_used_port(self):
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if len(res_context) > 0:
return True
else:
return False
def check_port(self):
if OSType.WIN == OSType.get_type():
find_pid_win_cmd = 'netstat -ano | findstr {} | findstr LISTENING'.format(self.env_port)
res_code, res_context = run_process(find_pid_win_cmd, out_p=True)
if res_code == 0:
print(res_context)
if len(res_context) > 0:
try:
self.pid = str(res_context).split()[-1].replace("\\r\\n'", "")
self._kill_pid()
except IndexError:
pass
elif OSType.LINUX == OSType.get_type():
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if res_code == 0:
print(res_context)
# 获取pid
if len(res_context) > 0:
try:
self.pid = str(res_context).split("\\n")[1].split()[1]
self._kill_3_pid()
except IndexError:
pass
else:
raise RuntimeError("your os is not support.")
def _kill_3_pid(self):
self._kill_pid()
count = 3
while count > 0:
find_pid_linux_cmd = "lsof -i:{}".format(self.env_port)
res_code, res_context = run_process(find_pid_linux_cmd, out_p=True)
if len(res_context) > 0:
time.sleep(2)
self.pid = str(res_context).split("\\n")[1].split()[1]
self._kill_pid()
count -= 1
continue
else:
break
def _kill_pid(self):
if OSType.WIN == OSType.get_type():
kill_pid_cmd = "taskkill /f /pid {}".format(self.pid)
elif OSType.LINUX == OSType.get_type():
kill_pid_cmd = "kill -9 {}".format(self.pid)
else:
raise RuntimeError("your os is not support.")
res_code, res_context = run_process(kill_pid_cmd)
if res_code:
raise RuntimeError("kill pid: {} failed. error: {}".format(self.pid, res_context))
def update_port(self):
main_py = Path(self.workspace) / "base_framework/main.py"
tmp_py = Path(self.workspace) / "base_framework/tmp.py"
shutil.copy(main_py, tmp_py)
src_context = "port=9999"
dst_context = "port={}".format(self.env_port)
self._replace_file_context(tmp_py, main_py, src_context, dst_context)
env_robot = Path(self.workspace) / "{}/test_case/Resource/AdapterKws/env.robot".format(self.team)
tmp_robot = Path(self.workspace) / "{}/test_case/Resource/AdapterKws/tmp.robot".format(self.team)
shutil.copy(env_robot, tmp_robot)
src_context = "127.0.0.1:9999"
dst_context = "127.0.0.1:{}".format(self.env_port)
self._replace_file_context(tmp_robot, env_robot, src_context, dst_context)
def update_suite(self):
case_dir = Path(self.workspace) / "{}/test_case".format(self.team)
self._set_path(case_dir)
case_src_path = Path(self.case_path)
if case_src_path.is_file():
base_name = case_src_path.name.split(case_src_path.suffix)[0]
if base_name.upper() in self.all_dir_name:
self.case_path = str(case_src_path.with_name(base_name + "9" + case_src_path.suffix))
self._replace_path(case_dir)
def _set_path(self, src_path):
for p in src_path.iterdir():
if p.is_dir():
self.all_dir_name.add(p.name.upper())
self._set_path(p)
def _replace_path(self, src_path):
for p in src_path.iterdir():
if p.is_dir():
self._replace_path(p)
elif p.is_file():
base_name = p.name.split(p.suffix)[0]
if base_name.upper() in self.all_dir_name:
p.rename(p.with_name(base_name + "9" + p.suffix))
def start_kwl(self):
startup_path = Path(self.workspace) / "base_framework"
startup = "startup.py"
exec_cmd_params = ""
if len(self.special_env) > 0:
exec_cmd_params = exec_cmd_params + "-j {} ".format(self.special_env)
if len(self.team) > 0:
exec_cmd_params = exec_cmd_params + "-t {} ".format(self.team)
if len(self.business) > 0:
exec_cmd_params = exec_cmd_params + "-b {} ".format(self.business)
if OSType.WIN == OSType.get_type():
exec_cmd_path = "cd {} && python {} ".format(startup_path, startup)
exec_cmd = exec_cmd_path + exec_cmd_params
elif OSType.LINUX == OSType.get_type():
exec_cmd_path = "cd {} && python3 {} ".format(startup_path, startup)
exec_cmd = exec_cmd_path + exec_cmd_params
else:
raise RuntimeError("your os is not support.")
exec_cmd = exec_cmd[:-1]
# import subprocess
# from subprocess import PIPE
# close_fds = False if OSType.WIN == OSType.get_type() else True
# subprocess.Popen(exec_cmd, shell=True, close_fds=close_fds, stdout=subprocess.DEVNULL)
try:
pro = self._start_kwl_server(exec_cmd)
if not pro.is_alive():
print("--------pro.exec_status: ", pro.is_alive())
pro.kill()
time.sleep(5)
self._kill_3_pid()
self._start_kwl_server(exec_cmd)
except Exception as e:
print("--------error: ", e)
@staticmethod
def _start_kwl_server(exec_cmd):
from multiprocessing import Process
p = Process(target=run_process, args=(exec_cmd,))
p.daemon = True
p.start()
time.sleep(30)
return p
@staticmethod
def _replace_file_context(src_file, dst_file, src_context, dst_context):
with open(src_file, "r") as f_src:
with open(dst_file, "w") as f_dst:
for line in f_src:
if src_context in line:
f_dst.writelines(line.replace(src_context, dst_context))
else:
f_dst.writelines(line)
def _get_report_dir(self):
"""
Create %WORKSPACE%/Report 用于存放对应构建的构建日志
"""
report_dir = os.path.join(self.workspace, 'Report')
if not os.path.exists(report_dir):
logging.info(u'create report directory: %s' % report_dir)
os.makedirs(report_dir)
return report_dir
def _get_report_ci_out_dir(self):
"""
Create %WORKSPACE%/Report/ci_out用于存放构建日志
"""
ci_out_dir = os.path.join(self.workspace, 'Report', 'ci_out')
if os.path.exists(ci_out_dir):
logging.info(u'create ci output directory: %s' % ci_out_dir)
shutil.rmtree(ci_out_dir)
return ci_out_dir
@staticmethod
def copy_any_thing(src, dst):
try:
shutil.copytree(src, dst)
except OSError as exc:
if exc.errno == errno.ENOTDIR:
shutil.copy(src, dst)
else:
raise
def _wait_kwl_run(self):
i = 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
while i < self.wait_time:
try:
s.connect(("127.0.0.1", int(self.env_port)))
s.shutdown(2)
s.close()
return
except socket.error:
i += 1
s.close()
def run_cases(self):
"""
run cases by workspace include exclude.
"""
self._wait_kwl_run()
report_dir = os.path.join(self.workspace, 'Report')
if os.path.exists(report_dir):
logging.info(u'clean report directory: %s' % report_dir)
shutil.rmtree(report_dir)
os.makedirs(report_dir)
if self.rerun == 'true' or self.rerun == '':
self._rerun_failed_cases()
else:
self._default_run_cases()
out_dir = os.path.join(self._get_report_dir(), 'out')
if os.path.exists(out_dir):
# copy report to ci_out.
self.copy_any_thing(self._get_report_dir(), self._get_report_ci_out_dir())
else:
raise RuntimeError(u'%s directory is not exist.' % out_dir)
def _default_run_cases(self):
"""
exec robot and output log to {report_dir}/out directory.
return command execute exit code.
"""
test_case_cmdstr = []
if len(self.test_case) > 0:
for tc in self.test_case.split(','):
test_case_cmdstr.append(u'--test %s' % tc)
includes_cmdstr = []
if len(self.include) > 0:
for include in self.include.split(','):
includes_cmdstr.append(u'--include %s' % include)
excludes_cmdstr = []
if len(self.exclude) > 0:
for exclude in self.exclude.split(','):
excludes_cmdstr.append(u'--exclude %s' % exclude)
output_directory = os.path.join(self._get_report_dir(), 'out')
Path(output_directory).mkdir(exist_ok=True)
# Set ROBOT_SYSLOG_FILE and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = os.path.join(self._get_report_dir(), 'robot_syslog.txt')
os.environ['ROBOT_SYSLOG_LEVEL'] = ROBOT_LOG_LEVEL
logging.info(u'building cases...')
res_code, _ = run_process(' '.join(
[u'robot'] + test_case_cmdstr + excludes_cmdstr + includes_cmdstr + [u'-d', output_directory,
self.case_path]))
# clear ROBOT_SYSLOG_FILE(NONE) and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = 'NONE'
return res_code
def _rerun_failed_cases(self):
"""
execute robot twice(the second execute is only failed in first.)
and remerge output to {report_dir}/out directory.
"""
logging.info('rerunfailed cases mode...')
return_code = self._default_run_cases()
logging.info('first run exit code: %s' % return_code)
if not return_code:
logging.info('first cases built successfully')
return
logging.info('the first cases to build there is failure cases')
output_directory = os.path.join(self._get_report_dir(), u'out')
if not os.path.exists(output_directory):
raise RuntimeError(u'the first cases to built throw exception.')
output_directory_r1 = os.path.join(self._get_report_dir(), u'first_out')
output_directory_r2 = os.path.join(self._get_report_dir(), u'second_out')
logging.info('rename the first cases to build the output directory')
cur_dir = os.getcwd()
os.chdir(self._get_report_dir())
if OSType.WIN == OSType.get_type():
cmd_str = 'ren out first_out'
elif OSType.LINUX == OSType.get_type():
cmd_str = 'cp -R out first_out'
else:
raise RuntimeError("your os is not support.")
os.system(cmd_str)
os.chdir(cur_dir)
output_directory_cmdstr = [u'-d', output_directory_r2]
rerun_failed_cmdstr = [u'--rerunfailed', os.path.join(output_directory_r1, u'output.xml')]
logging.info('rerunfailed test cases...')
run_process(' '.join([u'robot'] + output_directory_cmdstr + rerun_failed_cmdstr + [self.case_path]))
if not os.path.exists(output_directory_r2):
raise RuntimeError(u'the second cases to built throw throw exception.')
# Set ROBOT_SYSLOG_FILE and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = os.path.join(self._get_report_dir(), 'robot_syslog2.txt')
os.environ['ROBOT_SYSLOG_LEVEL'] = ROBOT_LOG_LEVEL
logging.info('merge report...')
run_process(' '.join([u'rebot', u'-d', output_directory, u'-o', u'output.xml', u'--merge',
os.path.join(output_directory_r1, u'output.xml'),
os.path.join(output_directory_r2, u'output.xml')]))
# clear ROBOT_SYSLOG_FILE(NONE) and ROBOT_SYSLOG_LEVEL environment variable.
os.environ['ROBOT_SYSLOG_FILE'] = 'NONE'
def record_build_url(self, build_id, report_url):
try:
if build_id:
import pymysql
db = pymysql.connect(host="mysql.qa.huohua.cn", user="qa-dev", password="jaeg3SCQt0",
database="sparkatp", charset='utf8')
cursor = db.cursor()
if build_id and report_url:
update_sql = "UPDATE sparkatp.build_info set report_url='{}',status=2 WHERE id={}".format(
report_url, build_id)
cursor.execute(update_sql)
cursor.fetchall()
try:
self.get_jacoco_report(cursor, build_id)
except Exception as e:
print(e)
db.commit()
cursor.close()
db.close()
except Exception as e:
print(e)
def get_tester_by_project_id(self):
"""
获取project_id和tester
:return:
"""
try:
import pymysql
db = pymysql.connect(host="10.250.200.53", user="root", password="peppa@test", database="tools",
charset='utf8')
cursor = db.cursor()
db.commit()
cursor.close()
db.close()
except Exception as e:
print(e)
def get_project_id_by_build_id(self, cursor, build_id):
try:
if build_id:
get_scene_id_sql = "SELECT scene_id FROM build_info where id='{}'".format(
build_id)
cursor.execute(get_scene_id_sql)
scene_id_info = cursor.fetchone()
if scene_id_info:
scene_id = scene_id_info[0]
get_project_id_sql = "SELECT project_id FROM scene_new where id='{}'".format(
scene_id)
cursor.execute(get_project_id_sql)
project_id_info = cursor.fetchone()
if project_id_info:
project_id = project_id_info[0]
return project_id
return 0
return 0
except Exception as e:
print(e)
def get_jacoco_report(self, cursor, build_id):
get_server_name_sql = "SELECT run_server_list FROM build_info where id='{}' and is_jacoco=1".format(build_id)
cursor.execute(get_server_name_sql)
server_name_info = cursor.fetchone()
if server_name_info:
server_name_list = eval(server_name_info[0])
for server_name in server_name_list:
if server_name == "PEPPA-TEACH-API" or server_name == "peppa-teach-api" or "-EXECUTOR" in server_name.upper():
continue
self._do_jacoco_report(server_name, build_id, cursor)
def _do_jacoco_report(self, project_name, build_id, cursor):
if not self.special_env:
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, self.special_env, "master",
self.special_env, "1", "", "QA环境构建不需要收集增量覆盖率")
cursor.execute(insert_data)
return
elif self.special_env.upper() == "NONE" or self.special_env.upper() == "QA":
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, self.special_env, "master",
self.special_env, "1", "", "QA环境构建不需要收集增量覆盖率")
cursor.execute(insert_data)
return
# 收集覆盖率报告
import requests
import json
if self.team.upper() in ["CC", "LALIVE", "H2R"]:
self.team = "CC"
if self.team.upper() in ["SCM", "ES"]:
self.team = "ES"
if self.team.upper() in ["TO", "TMO"]:
self.team = "TTS"
current_version = self.get_branch_from_open_galaxy(self.special_env, project_name)
if current_version == "master":
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, current_version,
self.special_env, "1", "", "master不需要收集增量覆盖率")
cursor.execute(insert_data)
return
if not current_version:
print("-----------------: {}, 独立环境: {} 服务名: {}".format("未在独立环境中服务,不收集覆盖率", self.special_env, project_name))
return
if project_name.lower() in self.galaxy_server_name_to_swagger.keys():
project_name = self.galaxy_server_name_to_swagger[project_name.lower()]
base_version = "master"
url = "http://10.250.0.252:8989/cov/syncCollectionCov"
params = {"baseVersion": base_version, "businessName": self.team, "currentVersion": current_version,
"departmentName": "质量保障中心",
"envName": self.special_env, "isBranch": 1, "isDiff": 2, "projectName": project_name}
res = requests.post(url, json=params)
if res.status_code == 200:
if json.loads(res.text)["msg"] != "success":
logging.error("{}收集覆盖率报告失败err: {}".format(project_name, res.text))
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, base_version,
self.special_env, "3", "", res.text)
cursor.execute(insert_data)
else:
jacoco_report_id = json.loads(res.text)["data"]
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, int(jacoco_report_id), self.team, project_name, current_version, base_version,
self.special_env, "0", "", "")
cursor.execute(insert_data)
else:
logging.error("{}收集覆盖率报告失败status{}err: {}".format(project_name, str(res.status_code), res.text))
insert_data = "INSERT INTO `sparkatp`.`build_jacoco`(`build_info_id`, `jacoco_report_id`, `team`, `server_name`, `now_version`, `base_version`, `env_name`, `status`, `report_url`, `remark`) VALUES ('{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}', '{}');".format(
build_id, "", self.team, project_name, current_version, base_version,
self.special_env, "3", "", res.text)
cursor.execute(insert_data)
# print(res.text)
def get_branch_from_open_galaxy(self, special_env, server_name):
if server_name.lower() in self.swagger_name_to_galaxy.keys():
server_name = self.swagger_name_to_galaxy[server_name.lower()]
try:
headers = {
"Api-Token": "2a1cbc25e0d183e1ec9fe5872c1433617c96da9e2905c4371d8f979479910aa1"}
open_galaxy_url = "http://opengalaxy.bg.huohua.cn/api/v1/hcloud/tree/node/sec/application?sec_name={}".format(
special_env)
res = requests.get(open_galaxy_url, headers=headers)
if res.status_code == 200:
result = json.loads(res.text)
for item in result["data"]["results"]:
if item["name"].lower() == server_name.lower():
branch = item["branch"]
return branch
return ""
else:
print("获取运维服务列表失败status: {}, res: {}".format(res.status_code, res.text))
return ""
except Exception as e:
print(e)
return ""
if __name__ == '__main__':
# workspace = "/root/workspaces/huaxuemin-dev"
workspace = r"E:\huohua\auto\huaxuemin-dev"
# case_path = "/root/workspaces/huaxuemin-dev/HuoHuaTestCase/EN/1.接口/Peppa-Eng-Live/play_back.robot"
case_path = r"E:\huohua\auto\huaxuemin-dev\HuoHuaTestCase\EN\1.接口\Peppa-Eng-Live\play_back.robot"
test_case = ""
include = ""
exclude = ""
test = CaseRunner(workspace, case_path, test_case, include, exclude)
# test.assign_port()
# test.check_port()
# test.update_port()
# test.start_kwl()
# test.run_cases()
# test.check_port()
# build_info_id = '16329'
# build_url = 'http://10.250.200.1:8080/jenkins/view/QE_JOB/job/qe_job1/63/'
# test.record_build_url(build_info_id, build_url)
res = test.get_branch_from_open_galaxy("HHC-92692", "peppa-asset-server")
print(res)

View File

@@ -0,0 +1,194 @@
# -*- coding:utf-8 -*-
import json
import requests
import logging
from base_framework.public_tools.utils import Tools
obj_tool = Tools()
class FeiShuMultidimensionalTableOperations:
def __init__(self, app_token, table_id, view_id):
"""
初始化飞书多维表格操作类
Args:
app_token: 多维表格token登录飞书多维表格按F12右侧接口列表中找带token的接口复制token
table_id: 多维表格id登录飞书多维表格在浏览器地址栏中找到table=后面的值
view_id: 多维表格视图id登录飞书多维表格在浏览器地址栏中找到view=后面的值
Note
如果返回显示禁止访问的话,说明你的飞书表格没有给应用开通权限,需要在更多-添加应用中添加此应用,并开通权限
这里我们使用的应用名是QualityAssurance-Customer-CD-QA
如果你搜多不到这个应用,说明你没有此应用的使用权限,找陈江或刚哥开通权限
"""
self.app_token = app_token # 表格token
self.table_id = table_id # 表格id
self.view_id = view_id # 表格视图id
self.app_id = 'cli_a53b456397bdd00c' # 飞书应用的app_id,使用前,多维表格需要在更多-添加应用中添加此应用,并开通权限
self.app_secret = 's3U4tXp9lTz7imMDQsHDNcoO4AgzXWk7' # 飞书应用的app_secret
self.fs_login_url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
self.http_session = requests.session()
self.tenant_access_token = ( # 以应用身份请求api的鉴权凭证每次获取后有效期为2小时
self.__get_tenant_access_token_by_feishu().get("tenant_access_token"))
self.http_session.headers.update({"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {self.tenant_access_token}",
"User-Agent": "lark-api-explorer/v1"})
def __get_tenant_access_token_by_feishu(self):
"""功能获取飞书应用的tenant_access_token以应用身份请求api的鉴权凭证每次获取后有效期为2小时"""
headers = {'Content-Type': 'application/json; charset=utf-8'}
data = {'app_id': self.app_id, 'app_secret': self.app_secret}
r = requests.post(self.fs_login_url, data=json.dumps(data), headers=headers, verify=False)
r_json = r.json()
if r_json.get('code') == 0:
return {"expire": r_json.get('expire'), "tenant_access_token": r_json.get('tenant_access_token')}
else:
raise Exception(f"获取原始tenant_access_token失败{r_json}")
def fsmt_search_from_m_table(self, filter_dict=None, sort=None, automatic_fields=False, field_names=None):
"""
功能从多维表格中查询数据详细说明见https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search
Args:
filter_dict: 过滤条件类型list格式[{"conjunction": "string", "conditions": "list"]
conjunction: 过滤条件之间的关系取值范围and、or当仅有一个条件时也需要填写此字段
conditions: 过滤条件,格式:[{"field_name": "string", "operator": "string", "value": "list"}]
field_name: 筛选条件的左值值为字段的名称类型string
operator: 筛选条件的运算符,
取值范围is等于
isNot不等于
contains包含
doesNotContain不包含
isEmpty为空
isNotEmpty不为空
isGreater大于
isGreaterEqual大于等于
isLess小于
isLessEqual小于等于
likeLIKE 运算符。暂未支持
inIN 运算符。暂未支持
value: 筛选条件的右值值为字段的值类型list
若查询条件是时间格式,且为具体时间或日期时,格式为:["ExactDate", "13位的时间戳"]
同时也支持时间段查询,格式为:["具体的时间段描述字符"],如:
["Today"]:今天
["Tomorrow"]: 明天
["Yesterday"]:昨天
["CurrentWeek"]:本周
["LastWeek"]:上周
["CurrentMonth"]:本月
["LastMonth"]:上月
["TheLastWeek"]:过去七天内
["TheNextWeek"]:未来七天内
["TheLastMonth"]:过去三十天内
["TheNextMonth"]:未来三十天内
sort: 排序条件类型list格式[{"field_name": "string", "desc": "bool"}]
field_name: 排序字段的名称类型string
desc: 是否倒序排序类型bool, 默认值False
automatic_fields: 控制是否返回自动计算的字段, true 表示返回
field_names: 指定本次查询返回记录中包含哪些字段
Returns: {"code": 0, "msg": "",
"data": {"items": "入参中自定义的返回字段"
"has_more": "bool,是否还有更多项",
"page_token": "boolean, 分页标记,当 has_more 为 true 时,会同时返回新的 page_token否则不返回 page_token",
"total": "int, 总数"}}
"""
url = ("https://open.feishu.cn/open-apis/bitable/v1/apps/{}/tables/{}/records/search"
.format(self.app_token, self.table_id))
request_data = {"view_id": self.view_id,
"filter": filter_dict,
"automatic_fields": automatic_fields,
"sort": sort,
"field_names": field_names}
try:
rs = self.http_session.post(url, data=json.dumps(request_data), verify=False)
except Exception as e:
raise Exception(f"调用飞书接口{url}失败:{e}")
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
raise Exception(f"调用飞书接口{url}失败:\n{rs.status_code}, {rs.text}")
def fsmt_insert_data_to_m_table(self, m_table_column_dict):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_create
api文档地址https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/batch_create
"""
url = ("https://open.feishu.cn/open-apis/bitable/v1/apps/{}/tables/{}/records/batch_create"
.format(self.app_token, self.table_id))
request_data = {"records": []}
for column_dict in m_table_column_dict:
request_data["records"].append({"fields": column_dict})
try:
rs = self.http_session.post(url, data=json.dumps(request_data), verify=False)
except Exception as e:
raise Exception(f"调用飞书接口{url}失败:{e}")
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
raise Exception(f"调用飞书接口{url}失败:\n{rs.status_code}, {rs.text}")
def fsmt_update_one_data_to_m_table(self, record_id, m_table_column_dict):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id/update
api文档https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/update
"""
url = f"""https://open.feishu.cn/open-apis/bitable/v1/apps/{self.app_token}/tables/{self.table_id}/records/{record_id}"""
data = {"fields": m_table_column_dict}
logging.info(f"更新数据请求参数为:{data}")
rs = self.http_session.put(url, data=json.dumps(data), verify=False)
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
logging.info(f"更新异常,异常返回结果为:{rs}")
def batch_update_data(self, table_id, update_records_data_list):
"""
https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_update
api文档https://open.feishu.cn/document/server-docs/docs/bitable-v1/app-table-record/batch_update
"""
url = f"""https://open.feishu.cn/open-apis/bitable/v1/apps/{self.app_token}/tables/{table_id}/records/batch_update"""
data = {"records": update_records_data_list}
logging.info(f"批量更新数据请求参数为:{data}")
rs = self.http_session.post(url, data=json.dumps(data), verify=False)
rj_json = rs.json()
if rj_json.get('code') == 0:
return rj_json
else:
logging.info(f"批量更新异常,异常返回结果为:{rs}")
raise Exception(f"调用飞书接口{url}失败:{rs}")
def delete_data(self):
pass
if __name__ == '__main__':
fs = FeiShuMultidimensionalTableOperations(app_token="HssibyRf4anbpxsA3G2c1IPwnic",
table_id="tblVbNoZE1RI3Hdo",
view_id="vewx9lK9hI")
filter_dict = {"conjunction": "and",
"conditions": [{"field_name": "时间", "operator": "is", "value": ["Today"]}]}
sort = [{"field_name": "时间", "desc": True}]
field_names = ["时间", "入班", "换班", "补课", "月份"]
rsp = fs.fsmt_search_from_m_table(filter_dict=filter_dict, sort=sort, field_names=field_names)
if rsp.get("code") == 0:
if int(rsp.get("data").get("total")) == 0:
ci_data = [['2024-07-24', 7022, 2040, 105]]
request_data = []
for item in ci_data:
ci_date = obj_tool.get_format_date(r_time="{} 00:00:00".format(item[0]), r_type=13)
request_data.append({"时间": ci_date, "入班": item[1], "换班": item[2], "补课": item[3],
"月份": "{}".format(int(item[0].split("-")[1]))})
for item in request_data:
print(item)
# request_data = [{"时间": 1721664000000, "入班": 123, "换班": 124, "补课": 125, "月份": "7月"},
# {"时间": 1721750400000, "入班": 223, "换班": 224, "补课": 225, "月份": "7月"}]
rsp = fs.fsmt_insert_data_to_m_table(m_table_column_dict=request_data)
print(rsp)
else:
print("数据已存在,本次不写入...")
else:
print("查询失败:{}".format(rsp))

View File

@@ -0,0 +1,204 @@
# -*- coding:utf-8 -*-
"""
功能通过kibana查询logstash日志
进度:待完善....
"""
import requests
import json
from datetime import datetime, timedelta, timezone
class LogstashLogKibana:
def __init__(self, k_user="wuyonggang", k_pwd="Mima@123"):
self.kibana_host = "https://logstashlog-kibana.qc.huohua.cn/internal/bsearch"
self.kibana_user = k_user
self.kibana_pwd = k_pwd
self.r_header = {'kbn-version': '7.14.2',
'Content-Type': 'application/json; charset=gbk',
'sec-ch-ua-mobile': r'?0'}
self.request = requests.session()
self._login_kibana()
def _login_kibana(self):
url = "https://logstashlog-kibana.qc.huohua.cn/internal/security/login"
payload = json.dumps({"providerType": "basic",
"providerName": "basic",
"currentURL": "https://logstashlog-kibana.qc.huohua.cn/login?msg=LOGGED_OUT",
"params": {"username": self.kibana_user, "password": self.kibana_pwd}
})
resp = self.request.post(url, headers=self.r_header, data=payload)
print(resp)
def _format_message(self, message):
"""
格式化消息
:param message: 消息
:return: 格式化后的消息
"""
def _get_time_tamp(self, minute=10):
# 获取当前时间
current_time = datetime.utcnow().replace(tzinfo=timezone.utc)
# 计算十分钟前的时间
ten_minutes_ago = current_time - timedelta(minutes=minute)
# 格式化时间为 Elasticsearch 时间戳格式
gte_timestamp = ten_minutes_ago.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
lte_timestamp = current_time.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
return gte_timestamp, lte_timestamp
def lk_query_kibana_log(self, minutes=15, app_name=None, querys=None):
if querys is None:
querys = ['java.lang.NullPointerException']
if not (isinstance(querys, list) or isinstance(querys, dict)):
raise ValueError('querys{},必须为列表或者字典'.format(querys))
gte_timestamp, lte_timestamp = self._get_time_tamp(minute=minutes)
query_con = []
if isinstance(querys, dict):
query_cons = {"bool": {"should": [{"match_phrase": querys}], "minimum_should_match": 1}}
else:
for query in querys:
if len(querys) == 1:
query_cons = {
"multi_match": {
"type": "phrase",
"query": query,
"lenient": True
}}
else:
query_con.append({
"multi_match": {
"type": "phrase",
"query": query,
"lenient": True
}
})
query_cons = {
"bool": {
"filter": query_con}}
url = "https://logstashlog-kibana.qc.huohua.cn/internal/bsearch"
payload = json.dumps({
"batch": [
{
"request": {
"params": {
"index": "logstash-qc-logstashlog*",
"body": {
"size": 10000,
"sort": [
{
"@timestamp": {
"order": "desc",
"unmapped_type": "boolean"
}
}
],
"version": True,
"fields": [
{
"field": "*",
"include_unmapped": "true"
},
{
"field": "@timestamp",
"format": "strict_date_optional_time"
},
{
"field": "end_data",
"format": "strict_date_optional_time"
},
{
"field": "end_date",
"format": "strict_date_optional_time"
},
{
"field": "start_date",
"format": "strict_date_optional_time"
}
],
"aggs": {
"2": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "30m",
"time_zone": "Asia/Shanghai",
"min_doc_count": 1
}
}
},
"script_fields": {},
"stored_fields": [
"*"
],
"runtime_mappings": {},
"_source": False,
"query": {
"bool": {
"must": [],
"filter": [
query_cons,
{
"range": {
"@timestamp": {
"gte": gte_timestamp,
"lte": lte_timestamp,
"format": "strict_date_optional_time"
}
}
},
{
"match_phrase": {
"APP_NAME": app_name
}
}
],
"should": [],
"must_not": []
}
},
"highlight": {
"pre_tags": [
"@kibana-highlighted-field@"
],
"post_tags": [
"@/kibana-highlighted-field@"
],
"fields": {
"*": {}
},
"fragment_size": 2147483647
}
},
"track_total_hits": True,
"preference": 1708481407352
}
},
"options": {
# "sessionId": "473ee7d3-be00-411e-a925-71e2f669a230",
"isRestore": False,
"strategy": "ese",
"isStored": False
}
}
]
})
print(payload)
response = self.request.post(url, headers=self.r_header, data=payload)
# binary_data = response.text
# 解码为字符串并解析为 JSON 对象
json_string = response.text
json_data = json.loads(json_string)
print(json_data)
# print(response.json())
# path = os.path.join(self.data_path_list, file_name)
# with open(path, 'w+', encoding='utf-8') as f:
# f.write(response.text)
if __name__ == '__main__':
lk = LogstashLogKibana()
lk.lk_query_kibana_log(minutes=15, app_name="peppa-sparkle-scheduler",
querys=['java.lang.NullPointerException'])

View File

@@ -0,0 +1,55 @@
import concurrent.futures
import requests
import time
import json
cost_time = []
ALL_TIMES = 10000
def make_request(url, method='GET', data=None):
try:
start_time = int(time.time() * 1000)
if method.upper() == 'POST':
response = requests.post(url, json=data, headers={"content-type": "application/json;charset=UTF-8"})
else: # 默认使用GET方法
response = requests.get(url, params=data)
end_time = int(time.time() * 1000)
elapsed_time = end_time - start_time
cost_time.append(elapsed_time)
return response.text, elapsed_time
except requests.RequestException as e:
return f"Request failed: {e}", None
def main():
r_url = {'url': 'https://swagger.qa.huohua.cn/peppa-teach-timetable-server/timetableStudentServiceApi/queryListByIds',
'method': 'POST', 'data': [225838679]}
requests_list = [r_url] * ALL_TIMES
m_start_time = int(time.time() * 1000)
success_times = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
future_to_req = {executor.submit(make_request, req['url'], req['method'], req['data']): req for req in requests_list}
for future in concurrent.futures.as_completed(future_to_req):
req = future_to_req[future]
try:
data, elapsed_time = future.result()
if data and json.loads(data).get('code') == 200:
success_times += 1
# print(f"URL: {req['url']}\nMethod: {req['method']}\nData: {req['data']}\nResponse:\n{data}\nElapsed time: {elapsed_time:.2f} seconds\n")
print("-----:本次耗时{}ms".format(elapsed_time))
except Exception as e:
print(f"Error fetching {req['url']}: {e}")
m_end_time = int(time.time() * 1000)
all_times = (m_end_time - m_start_time)/1000
tps = ALL_TIMES / all_times
print("共计访问接口:{}次,成功访问接口:{}次,持续时长:{}tps: {}"
.format(len(cost_time), success_times, all_times, tps))
print("成功访问接口:{}".format(success_times))
print("单次最大耗时:{}ms".format(max(cost_time)))
print("单次最小耗时:{}ms".format(min(cost_time)))
print("单次平均耗时:{}ms".format(sum(cost_time) / len(cost_time)))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,403 @@
import requests
import hashlib
import json
from bs4 import BeautifulSoup
from datetime import datetime
import time
def send_to_feishu(webhook_url, content, keyword="bug"):
"""
发送消息到飞书机器人
Args:
webhook_url: 飞书机器人的webhook地址
content: 要发送的消息内容
keyword: 关键词,需要包含在消息中
"""
try:
# 构建消息体
message = {
"msg_type": "text",
"content": {
"text": f"{keyword}\n{content}"
}
}
# 发送请求
headers = {'Content-Type': 'application/json'}
response = requests.post(webhook_url,
data=json.dumps(message),
headers=headers,
timeout=10)
if response.status_code == 200:
print(f"✅ 消息发送成功到飞书")
return True
else:
print(f"❌ 飞书消息发送失败: {response.status_code}")
print(f"响应: {response.text}")
return False
except Exception as e:
print(f"❌ 发送飞书消息异常: {e}")
return False
def get_all_bugs_and_send():
"""获取所有产品的Bug并发送到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug列表")
print("=" * 50)
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败: {login_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取所有产品
products_resp = session.get(f"{base_url}/api.php/v1/products")
if products_resp.status_code != 200:
error_msg = f"❌ 获取产品列表失败: {products_resp.status_code}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
products = products_resp.json().get('products', [])
print(f"📦 共 {len(products)} 个产品")
all_bugs = []
# 遍历每个产品获取Bug只获取产品ID为2的
for product in products:
product_id = product.get('id')
product_name = product.get('name')
# 只获取产品ID为2的
if product_id != 2:
continue
print(f"\n获取产品 '{product_name}' 的Bug...")
# 获取HTML页面
bugs_url = f"{base_url}/bug-browse-{product_id}.html"
bugs_resp = session.get(bugs_url, params={'product': product_id, 'limit': 100})
if bugs_resp.status_code == 200:
try:
html = bugs_resp.text
soup = BeautifulSoup(html, "html.parser")
# 找 bug 列表的 table
table = (
soup.find("table", id="bugList")
or soup.find("table", class_="table")
or soup.find("table")
)
if not table:
print(" ❌ 未找到 bug 列表 table")
continue
# 解析表头
header_tr = table.find("tr")
if not header_tr:
print(" ❌ 未找到表头行")
continue
header_map = {}
for idx, th in enumerate(header_tr.find_all(["th", "td"])):
text = th.get_text(strip=True)
if not text:
continue
if "ID" == text or text == "编号":
header_map["id"] = idx
elif "标题" in text:
header_map["title"] = idx
elif "状态" in text:
header_map["status"] = idx
elif "严重" in text:
header_map["severity"] = idx
elif "优先" in text or "优先级" in text:
header_map["pri"] = idx
elif "指派" in text:
header_map["assignedTo"] = idx
elif "创建" in text or "打开" in text:
header_map["openedDate"] = idx
bugs = []
# 遍历表体行
for tr in table.find_all("tr")[1:]:
tds = tr.find_all("td")
if not tds:
continue
# id
bug_id = tr.get("data-id")
if not bug_id and "id" in header_map and header_map["id"] < len(tds):
bug_id = tds[header_map["id"]].get_text(strip=True)
if not bug_id:
continue
# 标题
title = ""
if "title" in header_map and header_map["title"] < len(tds):
cell = tds[header_map["title"]]
link = cell.find("a")
title = (link or cell).get_text(strip=True)
# 状态
status = ""
if "status" in header_map and header_map["status"] < len(tds):
status = tds[header_map["status"]].get_text(strip=True)
# 严重程度
severity = ""
if "severity" in header_map and header_map["severity"] < len(tds):
sev_cell = tds[header_map["severity"]]
severity = sev_cell.get_text(strip=True)
if not severity:
sev_span = sev_cell.find("span")
if sev_span and sev_span.get("title"):
severity = sev_span.get("title").strip()
# 优先级
pri = ""
if "pri" in header_map and header_map["pri"] < len(tds):
pri = tds[header_map["pri"]].get_text(strip=True)
# 指派给
assigned_to = ""
if "assignedTo" in header_map and header_map["assignedTo"] < len(tds):
assigned_to = tds[header_map["assignedTo"]].get_text(strip=True)
# 创建/打开日期
opened_date = ""
if "openedDate" in header_map and header_map["openedDate"] < len(tds):
opened_date = tds[header_map["openedDate"]].get_text(strip=True)
bug = {
"id": bug_id,
"title": title,
"statusName": status,
"severity": severity,
"pri": pri,
"assignedToName": assigned_to,
"openedDate": opened_date,
"product_name": product_name,
}
bugs.append(bug)
print(f" ✅ 解析出 {len(bugs)} 个Bug")
all_bugs.extend(bugs)
except Exception as e:
print(f" ❌ 解析失败: {e}")
else:
print(f" ❌ 获取失败: {bugs_resp.status_code}")
# 处理并发送统计信息
if all_bugs:
# 计算统计数据
today_str = datetime.today().strftime("%Y-%m-%d")
today_md_str = datetime.today().strftime("%m-%d")
total_bugs = len(all_bugs)
# 今日新增Bug
today_bugs = [
b for b in all_bugs
if (
today_str in str(b.get("openedDate", "")).strip()
or today_md_str in str(b.get("openedDate", "")).strip()
)
]
# 未关闭Bug状态不包含"已关闭"、"已解决"等)
open_bugs = [
b for b in all_bugs
if not any(kw in str(b.get("statusName", "")).lower() for kw in ["closed", "resolved", "done", "cancel"])
and not any(kw in str(b.get("statusName", "")) for kw in ["已关闭", "已解决", "已完成", "已取消"])
]
# 按指派人员统计
assigned_stats = {}
for bug in all_bugs:
assigned = bug.get("assignedToName", "未指派")
assigned_stats[assigned] = assigned_stats.get(assigned, 0) + 1
# 按状态统计
status_stats = {}
for bug in all_bugs:
status = bug.get("statusName", "未知")
status_stats[status] = status_stats.get(status, 0) + 1
# 构建飞书消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug统计报告\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 Bug统计概览:\n"
message += f" • 总Bug数: {total_bugs}\n"
message += f" • 今日新增: {len(today_bugs)}\n"
message += f" • 未关闭Bug: {len(open_bugs)}\n"
message += f"\n📋 状态分布:\n"
# 添加状态统计
for status, count in sorted(status_stats.items()):
message += f"{status}: {count}\n"
message += f"\n👥 指派人员统计:\n"
# 添加指派统计前5名
sorted_assigned = sorted(assigned_stats.items(), key=lambda x: x[1], reverse=True)[:5]
for person, count in sorted_assigned:
message += f"{person}: {count}\n"
# 今日新增Bug详情
if today_bugs:
message += f"\n🆕 今日新增Bug详情 ({len(today_bugs)}个):\n"
today_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
for bug in today_bugs[:10]: # 只显示前10个
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
# 未关闭Bug详情前5个
if open_bugs:
open_bugs.sort(key=lambda x: int(x.get('id', 0)), reverse=True)
message += f"\n⚠️ 最新未关闭Bug (前5个):\n"
for bug in open_bugs[:5]:
bug_url = f"http://39.170.26.156:8888/bug-view-{bug.get('id')}.html"
message += f" [#{bug.get('id')}] {bug.get('title', '')[:30]}...\n"
message += f" 状态: {bug.get('statusName', '未知')} | 严重: {bug.get('severity', '未知')} | 指派: {bug.get('assignedToName', '未指派')}\n"
message += f"\n🔗 禅道地址: {base_url}"
# 打印到控制台
print(f"\n{'=' * 80}")
print(message)
print(f"{'=' * 80}")
# 发送到飞书
print("\n发送消息到飞书...")
success = send_to_feishu(feishu_webhook, message, keyword)
if success:
print(f"✅ Bug统计报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
else:
message = f"📭 禅道Bug统计报告\n"
message += f"⏰ 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"✅ 当前没有Bug数据\n"
message += f"🔗 禅道地址: {base_url}"
print(message)
send_to_feishu(feishu_webhook, message, keyword)
def send_simple_report():
"""发送简化的Bug统计报告到飞书"""
base_url = "http://39.170.26.156:8888"
username = "qiaoxinjiu"
password = "Qiao123456"
# 飞书配置
feishu_webhook = "https://open.feishu.cn/open-apis/bot/v2/hook/c7288ada-1c0c-472a-b652-a475a9586302"
keyword = "bug"
print("获取禅道Bug统计")
print("=" * 50)
try:
# 登录
session = requests.Session()
password_md5 = hashlib.md5(password.encode()).hexdigest()
login_resp = session.post(f"{base_url}/api.php/v1/tokens",
json={"account": username, "password": password_md5},
headers={'Content-Type': 'application/json'})
if login_resp.status_code not in [200, 201]:
error_msg = f"❌ 禅道登录失败"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
token = login_resp.json().get('token')
session.headers.update({'Token': token})
print("✅ 登录成功")
# 获取产品ID=2的Bug页面
bug_url = f"{base_url}/bug-browse-2.html"
response = session.get(bug_url)
if response.status_code != 200:
error_msg = f"❌ 无法获取Bug页面"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
return
# 简单统计Bug数量
import re
bug_matches = re.findall(r'bug-view-(\d+)\.html', response.text)
total_bugs = len(set(bug_matches)) # 去重
# 构建简单消息
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
message = f"🐛 禅道Bug速报\n"
message += f"⏰ 时间: {current_time}\n"
message += f"📊 产品: 智慧运营平台V1.0\n"
message += f"================================\n"
message += f"📈 当前总Bug数: {total_bugs}\n"
message += f"🔗 查看详情: {bug_url}\n"
message += f"\n💡 提示: 详细统计请查看禅道系统"
print(f"发现 {total_bugs} 个Bug")
# 发送到飞书
success = send_to_feishu(feishu_webhook, message, keyword)
if success:
print(f"✅ 简版Bug报告已发送到飞书")
else:
print(f"❌ 飞书消息发送失败")
except Exception as e:
error_msg = f"❌ 获取Bug统计异常: {str(e)}"
print(error_msg)
send_to_feishu(feishu_webhook, error_msg, keyword)
if __name__ == "__main__":
# 运行完整版本(带详细统计)
get_all_bugs_and_send()
# 或者运行简化版本(只发速报)
# send_simple_report()

View File

@@ -0,0 +1,237 @@
from __future__ import annotations
"""
ZenDao bug list crawler.
Design overview:
1. Read credentials + crawl settings from zendao_config.ini.
2. Use ZenDao REST API (`api.php/v1/accessTokens`) to obtain a short-lived token.
3. Pull paginated bug records under the configured product via `/api.php/v1/products/{product_id}/bugs`.
4. Export normalized bug data to CSV (default) or JSON, usable by other automation.
"""
import argparse
import configparser
import csv
import hashlib
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, Iterable, List, Sequence
import requests
from requests import Response, Session
BUG_EXPORT_FIELDS: Sequence[str] = (
"id",
"title",
"status",
"severity",
"pri",
"openedBy",
"openedDate",
"assignedTo",
"resolvedBy",
"resolution",
"lastEditedDate",
)
DEFAULT_CONFIG_PATH = Path(__file__).with_name("zendao_config.ini")
class ZenDaoAPIError(RuntimeError):
"""Raised when ZenDao returns an unexpected payload."""
@dataclass
class ZenDaoSettings:
base_url: str
username: str
password: str
product_id: int
per_page: int = 50
max_pages: int = 10
verify_ssl: bool = False
timeout: int = 10
auth_mode: str = "password" # password | token
api_token: str | None = None
auth_endpoint: str = "api.php/v1/tokens"
token_header: str = "Token"
password_hash: str = "md5" # md5 | plain
@classmethod
def from_file(cls, path: Path, section: str = "zendao") -> "ZenDaoSettings":
parser = configparser.ConfigParser()
if not path.exists():
raise FileNotFoundError(f"Config file not found: {path}")
parser.read(path, encoding="utf-8")
if section not in parser:
raise KeyError(f"Section [{section}] not found in {path}")
cfg = parser[section]
return cls(
base_url=cfg.get("base_url", "").rstrip("/"),
username=cfg.get("username", ""),
password=cfg.get("password", ""),
product_id=cfg.getint("product_id", fallback=0),
per_page=cfg.getint("per_page", fallback=50),
max_pages=cfg.getint("max_pages", fallback=10),
verify_ssl=cfg.getboolean("verify_ssl", fallback=False),
timeout=cfg.getint("timeout", fallback=10),
auth_mode=cfg.get("auth_mode", "password").lower(),
api_token=cfg.get("api_token"),
auth_endpoint=cfg.get("auth_endpoint", "api.php/v1/tokens"),
token_header=cfg.get("token_header", "Token"),
password_hash=cfg.get("password_hash", "md5").lower(),
)
def validate(self) -> None:
missing = [
name
for name, value in (
("base_url", self.base_url),
("username", self.username),
("password", self.password),
)
if not value
]
if missing:
raise ValueError(f"Missing config values: {', '.join(missing)}")
if self.product_id <= 0:
raise ValueError("product_id must be > 0")
if self.auth_mode not in {"password", "token"}:
raise ValueError("auth_mode must be 'password' or 'token'")
if self.password_hash not in {"md5", "plain"}:
raise ValueError("password_hash must be 'md5' or 'plain'")
if self.auth_mode == "token" and not self.api_token:
raise ValueError("api_token required when auth_mode=token")
class ZenDaoClient:
def __init__(self, settings: ZenDaoSettings, session: Session | None = None) -> None:
self.settings = settings
self.session: Session = session or requests.Session()
self._token: str | None = None
def authenticate(self) -> str:
if self.settings.auth_mode == "token":
if not self.settings.api_token:
raise ValueError("api_token missing in config")
self.session.headers.update({self.settings.token_header: self.settings.api_token})
self._token = self.settings.api_token
return self._token
payload = {
"account": self.settings.username,
"password": self._format_password(self.settings.password),
}
response = self._request("post", self.settings.auth_endpoint, json=payload)
data = response.json()
token = data.get("token") or data.get("accessToken")
if not token:
raise ZenDaoAPIError(f"Unexpected token response: {data}")
self._token = token
self.session.headers.update({self.settings.token_header: token})
return token
def _format_password(self, password: str) -> str:
if self.settings.password_hash == "md5":
return hashlib.md5(password.encode()).hexdigest()
return password
def fetch_bugs_page(self, page: int = 1) -> List[Dict]:
params = {"page": page, "limit": self.settings.per_page}
endpoint = f"api.php/v1/products/{self.settings.product_id}/bugs"
response = self._request("get", endpoint, params=params)
payload = response.json()
bugs = payload.get("bugs") or payload.get("data")
if bugs is None:
raise ZenDaoAPIError(f"Unexpected bug payload: {payload}")
return list(bugs)
def fetch_all_bugs(self) -> List[Dict]:
all_bugs: List[Dict] = []
for page in range(1, self.settings.max_pages + 1):
page_bugs = self.fetch_bugs_page(page)
if not page_bugs:
break
all_bugs.extend(page_bugs)
if len(page_bugs) < self.settings.per_page:
break
return all_bugs
def _request(self, method: str, path: str, **kwargs) -> Response:
url = f"{self.settings.base_url}/{path.lstrip('/')}"
kwargs.setdefault("timeout", self.settings.timeout)
kwargs.setdefault("verify", self.settings.verify_ssl)
response = self.session.request(method=method, url=url, **kwargs)
response.raise_for_status()
return response
def normalize_bug(bug: Dict, fields: Sequence[str] = BUG_EXPORT_FIELDS) -> Dict[str, str]:
normalized = {}
for field in fields:
value = bug.get(field, "")
if isinstance(value, dict):
value = value.get("realname") or value.get("account") or value
normalized[field] = value if isinstance(value, str) else str(value)
return normalized
def write_json(path: Path, bugs: Iterable[Dict]) -> None:
path.write_text(json.dumps(list(bugs), ensure_ascii=False, indent=2), encoding="utf-8")
def write_csv(path: Path, bugs: Iterable[Dict], fields: Sequence[str]) -> None:
bugs = list(bugs)
if not bugs:
path.write_text("", encoding="utf-8")
return
with path.open("w", encoding="utf-8", newline="") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fields)
writer.writeheader()
for bug in bugs:
writer.writerow(normalize_bug(bug, fields))
def run_crawler(config: Path, output: Path) -> Path:
settings = ZenDaoSettings.from_file(config)
settings.validate()
client = ZenDaoClient(settings)
client.authenticate()
bugs = client.fetch_all_bugs()
if output.suffix.lower() == ".json":
write_json(output, bugs)
else:
write_csv(output, bugs, BUG_EXPORT_FIELDS)
return output
def parse_args(argv: Sequence[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Fetch bug list from ZenDao.")
parser.add_argument("--config", type=Path, default=DEFAULT_CONFIG_PATH, help="Path to zendao_config.ini")
parser.add_argument(
"--output",
type=Path,
default=Path("bugs.csv"),
help="Output file path (.csv or .json)",
)
return parser.parse_args(argv)
def main(argv: Sequence[str] | None = None) -> None:
args = parse_args(argv or sys.argv[1:])
output = run_crawler(args.config, args.output)
print(f"Bug list saved to: {output}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,14 @@
[zendao]
base_url = http://39.170.26.156:8888
username = qiaoxinjiu
password = Qiao123456
product_id = 1
per_page = 50
max_pages = 10
verify_ssl = false
timeout = 10
auth_mode = password
api_token =
auth_endpoint = api.php/v1/tokens
token_header = Token
password_hash = md5

View File

@@ -0,0 +1,41 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/9/21 18:39
"""
import json
def check_resp(is_check, resp):
if is_check:
assert resp["code"] == 200, "{}, exp code: {}".format(resp, 200)
assert resp["success"] is True, "{}, exp code: {}".format(resp, True)
assert resp["message"] == "", "{}, exp code: {}".format(resp, "")
assert resp["data"] != "", "{}, exp code: {}".format(resp, "")
def custom_check_resp(is_check, assert_list):
"""传参数例子asert_list = [(f"{resp}['code']", 0), (f"{resp}['message']", ""), (f"{resp}['success']", "True")]"""
if is_check:
for al in assert_list:
assert str(eval(al[0])) == str(al[1]), "{}, exp code: {}".format(eval(al[0]), al[1])
else:
pass
def convert_json(parames):
temp_json = json.dumps(parames)
temp_json1 = temp_json.replace("\"NULL\"", "null")
kwargs = json.loads(temp_json1)
return kwargs
def get_user(kwargs):
if kwargs.get("user", None):
user = kwargs.pop("user")
else:
user = None
return user, kwargs

View File

@@ -0,0 +1,157 @@
# -*-coding:utf-8-*-
import requests, time
from base_framework.public_tools import log
from base_framework.public_tools.get_token import LazyProperty
from base_framework.public_tools.read_config import InitConfig
obj_log = log.get_logger()
class Apollo:
def __init__(self):
self.server = None
self.session = None
self.config = InitConfig()
self.show_username = self.config.show_username
self.password = self.config.password
self.apollo_url = self.config.apollo_url
self.apollo_host = self.config.apollo_host
self.current_evn = self.config.current_evn
# @LazyProperty
def apollo_login(self):
post_data = dict()
post_data['login-submit'] = '登录'
post_data['username'] = self.show_username
post_data['password'] = self.password
req_session = requests.Session()
resp = req_session.post(url=self.apollo_url, data=post_data)
return req_session
def __get_project__(self):
pass
def apollo_get_config_value(self, app_id, name_space, key, cluster='default'):
"""
功能读取apollo上的配置
Args:
app_id: url地址里的appid
name_space: 页面分组的命名空间applicationdict.configteach.common等
key: 具体的配置key
cluster: 集群名defaulthhi等
Returns: 具体的配置值
"""
return self.get_apollo_config_by_project_key(server=app_id, project=name_space, key=key, cluster=cluster)
def get_apollo_config_by_project_key(self, project, key, cluster='default', server=""):
"""
返回{id: 8812, namespaceId: 378, key: "", value: "", comment: "#线上不要此配置", lineNum: 1,…}
"""
self.session = self.apollo_login()
if server:
self.server = server
req_url = "%s/apps/%s/envs/%s/clusters/%s/namespaces" % (
self.apollo_host, self.server, self.current_evn, cluster)
obj_log.info("get apollo url is : %s" % req_url)
resp = self.session.get(url=req_url).json()
# obj_log.info("apollo value is : %s" % resp)
for item in resp:
base_info = item.get('baseInfo')
items = item.get('items')
if not base_info['namespaceName'].lower() == project.lower():
continue
for key_info in items:
item_detail = key_info["item"]
if item_detail["key"] == key:
return key_info["item"]
else:
obj_log.info("无法找到key:%s" % key)
return None
def set_apollo_config_by_project_key(self, project, key, value, cluster='default', server="", set_type='append',
evn_name=""):
"""
功能设置apollo配置项
Args:
server: 项目名也称appid
project: 属性名
key: 键值key
value: 具体要设置的值
cluster: 集群defaulthhi等
set_type: 设置类型append-追加new-重置
Returns:
"""
if evn_name:
self.current_evn = evn_name
self.session = self.apollo_login()
if server:
self.server = server
req_url = "%s/apps/%s/envs/%s/clusters/%s/namespaces/%s/item" % (
self.apollo_host, self.server, self.current_evn, cluster, project)
obj_log.info("set apollo url is : %s" % req_url)
items = self.get_apollo_config_by_project_key(project, key, cluster)
if items is None:
items = dict()
items["value"] = value
items["key"] = key
items["tableViewOperType"] = 'create'
items["addItemBtnDisabled"] = True
else:
if set_type == 'new':
items["value"] = value
elif set_type == 'append':
if str(value) in str(items["value"]):
obj_log.info("apollo配置已存在本次跳过....")
else:
items["value"] = items["value"] + ",{}".format(value)
items["tableViewOperType"] = 'update'
obj_log.info("set apollo parameters is : %s" % items)
update_resp = self.session.put(url=req_url, json=items)
obj_log.info("set apollo resp is : %s" % update_resp)
assert update_resp.status_code == 200
obj_log.info("为key:%s设置value%s成功" % (key, value))
release_body = dict()
release_time_stamp = time.localtime()
release_time = '%s-release' % time.strftime("%Y%m%d%H%M%S", release_time_stamp)
release_body["isEmergencyPublish"] = False
release_body["releaseComment"] = ""
release_body["releaseTitle"] = release_time
release_url = "%s/apps/%s/envs/%s/clusters/%s/namespaces/%s/releases" % (
self.apollo_host, self.server, self.current_evn, cluster, project)
resp = self.session.post(url=release_url, json=release_body)
assert resp.status_code == 200
time.sleep(5)
def add_new_apollo_config_by_project_key(self, project, key, value, cluster='default', server=""):
self.session = self.apollo_login()
if server:
self.server = server
items = dict()
items["value"] = value
items["key"] = key
items["tableViewOperType"] = 'create'
items["addItemBtnDisabled"] = True
req_url = "%s/apps/%s/envs/%s/clusters/%s/namespaces/%s/item" % (
self.apollo_host, self.server, self.current_evn, cluster, project)
update_resp = self.session.put(url=req_url, json=items)
assert update_resp.status_code == 200
release_body = dict()
release_time_stamp = time.localtime()
release_time = '%s-release' % time.strftime("%Y%m%d%H%M%S", release_time_stamp)
release_body["isEmergencyPublish"] = False
release_body["releaseComment"] = ""
release_body["releaseTitle"] = release_time
release_url = "%s/apps/%s/envs/%s/clusters/%s/namespaces/%s/releases" % (
self.apollo_host, self.server, self.current_evn, cluster, project)
resp = self.session.post(url=release_url, json=release_body)
assert resp.status_code == 200
time.sleep(5)
if __name__ == '__main__':
ap = Apollo()
print(ap.apollo_get_config_value(app_id='peppa-teach-api', name_space='application',
key='new_ticket_leave_logic_switch'))

View File

@@ -0,0 +1,8 @@
# -*- coding:utf-8 -*-
# 存放自定义异常类
class BusinessError(Exception):
"""功能用于AITA项目识别是业务异常需返给前端做展示"""
def __init__(self, message="这里是自定义的业务异常"):
self.message = message
super().__init__(self.message)

View File

@@ -0,0 +1,31 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/11/10 10:26
"""
import pymysql
DB_TEST_HOST = "mysql.qa.huohua.cn"
DB_TEST_PORT = 3306
DB_TEST_DBNAME = "crmthirdparty"
DB_TEST_USER = "qa-dev"
DB_TEST_PASSWORD = "jaeg3SCQt0"
# 数据库连接编码
DB_CHARSET = "utf8"
# mincached : 启动时开启的闲置连接数量(缺省值 0 开始时不创建连接)
DB_MIN_CACHED = 10
# maxcached : 连接池中允许的闲置的最多连接数量(缺省值 0 代表不闲置连接池大小)
DB_MAX_CACHED = 20
# maxshared : 共享连接数允许的最大数量(缺省值 0 代表所有连接都是专用的)如果达到了最大数量,被请求为共享的连接将会被共享使用
DB_MAX_SHARED = 20
# maxconnecyions : 创建连接池的最大数量(缺省值 0 代表不限制)
DB_MAX_CONNECYIONS = 100
# blocking : 设置在连接池达到最大数量时的行为(缺省值 0 或 False 代表返回一个错误<toMany......> 其他代表阻塞直到连接数减少,连接被分配)
DB_BLOCKING = True
# maxusage : 单个连接的最大允许复用次数(缺省值 0 或 False 代表不限制的复用).当达到最大数时,连接会自动重新连接(关闭和重新打开)
DB_MAX_USAGE = 0
# setsession : 一个可选的SQL命令列表用于准备每个会话如["set datestyle to german", ...]
DB_SET_SESSION = None
# creator : 使用连接数据库的模块
DB_CREATOR = pymysql

View File

@@ -0,0 +1,300 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/11/6 17:34
"""
import pymysql
import pymongo
# from DBUtils.PooledDB import PooledDB
from dbutils.pooled_db import PooledDB
import psycopg2
from psycopg2 import pool
from psycopg2.extras import RealDictCursor
from base_framework.public_tools.read_config import InitConfig
from base_framework.public_tools.read_config import ReadConfig, get_current_config
# from base_framework.public_tools import db_config as config
from base_framework.base_config.current_pth import *
"""
@功能:创建数据库连接池
"""
as_db = ['ZZYY']
class PgConnectionPool(InitConfig):
pool = None
pool_cache = dict()
def __init__(self):
try:
super().__init__()
self.DB_SSL = False
except Exception as e:
print(e)
# 创建数据库连接conn和游标cursor
def __enter__(self):
self.conn = self.__getconn()
self.cursor = self.conn.cursor()
def __getconn(self, choose_db=None):
# 如果未指定数据库,使用默认配置
if not choose_db:
choose_db = 'default'
try:
self.pool = self.pool_cache[choose_db]
except KeyError:
rc = ReadConfig(config_file_path)
# 根据choose_db值获取对应的PostgreSQL配置
if choose_db == 'default':
db_host = rc.get_value(sections='PostgreSQL', options='db_test_host')
db_port = rc.get_value(sections='PostgreSQL', options='db_test_port')
db_name = rc.get_value(sections='PostgreSQL', options='db_test_dbname')
db_user = rc.get_value(sections='PostgreSQL', options='db_test_user')
db_password = rc.get_value(sections='PostgreSQL', options='db_test_password')
db_min_cached = rc.get_value(sections='PostgreSQL', options='db_min_cached')
db_max_cached = rc.get_value(sections='PostgreSQL', options='db_max_cached')
db_max_shared = rc.get_value(sections='PostgreSQL', options='db_max_shared')
db_max_connecyions = rc.get_value(sections='PostgreSQL', options='db_max_connecyions')
db_max_usage = rc.get_value(sections='PostgreSQL', options='db_max_usage')
else:
# 可以扩展其他数据库连接
db_host = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_host')
db_port = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_port')
db_name = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_name')
db_user = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_user')
db_password = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_password')
db_min_cached = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_min_cached')
db_max_cached = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_max_cached')
db_max_shared = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_max_shared')
db_max_connecyions = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_max_connecyions')
db_max_usage = rc.get_value(sections='PostgreSQL', options=f'db_{choose_db}_max_usage')
# PostgreSQL连接池配置
try:
print("=" * 80)
print("PostgreSQL连接池配置信息:")
print(" 主机(Host): {}".format(db_host))
print(" 端口(Port): {}".format(db_port))
print(" 数据库名(Database): {}".format(db_name))
print(" 用户名(User): {}".format(db_user))
print(" 密码(Password): {} (已隐藏)".format('*' * len(db_password) if db_password else 'None'))
print(" 最小缓存连接数(MinCached): {}".format(db_min_cached))
print(" 最大缓存连接数(MaxCached): {}".format(db_max_cached))
print(" 最大共享连接数(MaxShared): {}".format(db_max_shared))
print(" 最大连接数(MaxConnections): {}".format(db_max_connecyions))
print(" 最大使用次数(MaxUsage): {}".format(db_max_usage))
print(" SSL模式(SSLMode): {}".format('require' if self.DB_SSL else 'disable'))
print(" 连接超时(ConnectTimeout): 30秒")
print("=" * 80)
self.pool = PooledDB(
creator=psycopg2,
host=db_host,
port=int(db_port),
user=db_user,
password=db_password,
database=db_name,
mincached=int(db_min_cached),
maxcached=int(db_max_cached),
maxshared=int(db_max_shared),
maxconnections=int(db_max_connecyions),
blocking=True,
maxusage=int(db_max_usage),
setsession=None,
# PostgreSQL特定参数
sslmode='require' if self.DB_SSL else 'disable',
connect_timeout=30,
keepalives=1,
keepalives_idle=30,
keepalives_interval=10,
keepalives_count=5
)
self.pool_cache[choose_db] = self.pool
print("PostgreSQL连接池创建成功")
except Exception as e:
error_msg = """
PostgreSQL连接池创建失败
连接配置信息:
主机(Host): {}
端口(Port): {}
数据库名(Database): {}
用户名(User): {}
密码(Password): {} (已隐藏)
SSL模式(SSLMode): {}
连接超时(ConnectTimeout): 30秒
错误详情: {}
""".format(
db_host, db_port, db_name, db_user,
'*' * len(db_password) if db_password else 'None',
'require' if self.DB_SSL else 'disable',
str(e)
)
print(error_msg)
raise Exception(error_msg)
try:
return self.pool.connection()
except Exception as e:
# 尝试获取连接配置信息用于错误提示
try:
rc = ReadConfig(config_file_path)
db_host = rc.get_value(sections='PostgreSQL', options='db_test_host')
db_port = rc.get_value(sections='PostgreSQL', options='db_test_port')
db_name = rc.get_value(sections='PostgreSQL', options='db_test_dbname')
db_user = rc.get_value(sections='PostgreSQL', options='db_test_user')
except:
db_host = db_port = db_name = db_user = 'unknown'
error_msg = """
PostgreSQL连接获取失败
连接配置信息:
主机(Host): {}
端口(Port): {}
数据库名(Database): {}
用户名(User): {}
错误详情: {}
""".format(db_host, db_port, db_name, db_user, str(e))
print(error_msg)
raise Exception(error_msg)
# 释放连接池资源
def __exit__(self, exc_type, exc_val, exc_tb):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
# 获取连接和游标(返回字典形式的结果)
def getconn(self, choose_db=None):
conn = self.__getconn(choose_db=choose_db)
# 使用RealDictCursor返回字典形式的游标
cursor = conn.cursor(cursor_factory=RealDictCursor)
return cursor, conn
class MyConnectionPool(InitConfig):
pool = None
pool_cache = dict()
def __init__(self):
try:
super().__init__()
except Exception as e:
print(e)
self.current_business = get_current_config(section='run_evn_name', key='current_business')
# 创建数据库连接conn和游标cursor
def __enter__(self):
self.conn = self.__getconn()
self.cursor = self.conn.cursor()
def __getconn(self, choose_db=None):
current_team = ReadConfig(env_choose_path).get_value(sections='run_evn_name', options='current_team')
if not choose_db: # 没有指定,则按小组默认设置
if current_team.upper() in as_db and choose_db is None:
choose_db = 'as'
elif current_team.upper() == "SE" and choose_db is None:
choose_db = 'se'
elif current_team.upper() == "XUEDAU" and choose_db is None:
choose_db = 'xdu'
elif current_team.upper() not in as_db and choose_db is None and self.current_business == 'hh':
choose_db = 'hh'
elif current_team.upper() not in as_db and choose_db is None and self.current_business == 'hhi':
choose_db = 'hhi'
try:
self.pool = self.pool_cache[choose_db]
except Exception as e:
rc = ReadConfig(config_file_path)
if choose_db == 'as':
db_host = rc.get_value(sections='Mysql', options='db_as_svr')
elif choose_db == 'se':
db_host = rc.get_value(sections='Mysql', options='db_se_svr')
elif choose_db == 'xdu':
db_host = rc.get_value(sections='Mysql', options='db_xdu_svr')
self.DB_TEST_USER = rc.get_value(sections='Mysql', options='db_xdu_user')
self.DB_TEST_PASSWORD = rc.get_value(sections='Mysql', options='db_xdu_password')
elif choose_db == 'hh' or choose_db == 'huohua':
db_host = rc.get_value(sections='Mysql', options='db_hh_svr')
elif choose_db == 'hhi':
db_host = rc.get_value(sections='Mysql', options='db_hhi_svr')
elif choose_db == 'hh.qa': # 自动化和信息化的数据都走huohua
db_host = 'mysql.qa.huohua.cn'
elif not choose_db: # 没有传入则默认走huohua
choose_db = 'hh.qa'
db_host = 'mysql.qa.huohua.cn'
else:
raise Exception("当前仅支持hh,hhi,as,se四个数据库服务器而你选择的是{}".format(choose_db))
default_db = 'sys'
self.pool = PooledDB(
creator=pymysql,
host=db_host,
port=int(self.DB_TEST_PORT),
user=self.DB_TEST_USER,
passwd=self.DB_TEST_PASSWORD,
db=default_db,
mincached=int(self.DB_MIN_CACHED),
maxcached=int(self.DB_MAX_CACHED),
maxshared=int(self.DB_MAX_SHARED),
maxconnections=int(self.DB_MAX_CONNECYIONS),
blocking=True,
maxusage=int(self.DB_MAX_USAGE),
setsession=None,
use_unicode=True,
charset=self.DB_CHARSET
)
self.pool_cache[choose_db] = self.pool
return self.pool.connection()
# 释放连接池资源
def __exit__(self, exc_type, exc_val, exc_tb):
self.cursor.close()
self.conn.close()
# 关闭连接归还给链接池
# def close(self):
# self.cursor.close()
# self.conn.close()
# 从连接池中取出一个连接
def getconn(self, choose_db=None):
conn = self.__getconn(choose_db=choose_db)
# 字典形式返回
cursor = conn.cursor(pymysql.cursors.DictCursor)
return cursor, conn
# 获取连接池,实例化
def get_my_connection():
return MyConnectionPool()
def get_pg_connection():
return PgConnectionPool()
class MongoConnectionPool(InitConfig):
def __init__(self):
try:
# super().__init__()
super(MongoConnectionPool, self).__init__()
except Exception as e:
print(e)
def mongo_connect(self):
try:
self.connect_ = pymongo.MongoClient(host=self.MONGO_HOST,
port=int(self.MONGO_PORT),
username=self.MONGO_USER,
password=self.MONGO_PASSWORD,
authSource="hulk_teach_marketing"
)
except Exception as e:
raise Exception("mongdb连接失败{}".format(e))
return self.connect_
def get_my_mongo_connection():
return MongoConnectionPool()

View File

@@ -0,0 +1,168 @@
# encoding: utf-8
# @Time : 2022/4/18 上午10:36
# @Author : chenjiang
# @Site :
# @File : edu_user_helper.py
import requests
# import py_eureka_client.eureka_client as eureka_client
from base_framework.public_tools.sqlhelper import MySqLHelper
from base_framework.public_tools.my_faker import MyFaker
from base_framework.public_tools import log
from base_framework.public_tools.utils import Tools
obj_log = log.get_logger()
obj_my_faker = MyFaker()
obj_my_sql_helper = MySqLHelper()
obj_tools = Tools()
# def get_ip_by_server_name(env_name, service_name, type):
# """
# 根据服务名称环境as/hh
# :param env_name: 环境信息
# :param service_name: 服务名称
# :param type: as/hh
# :return:
# """
# all_school_eureka_server= 'http://eureka.qa.allschool.com/eureka/'
# hh_eureka_server = 'http://eureka.qa.huohua.cn/eureka/'
# try:
# if type.lower() == 'as':
# eureka_server = all_school_eureka_server
# else:
# eureka_server = hh_eureka_server
#
# eureka_client.init(
# eureka_server=eureka_server,
# app_name="ASC--",
# instance_ip="127.0.0.1",
# instance_port=8080)
# client = eureka_client.get_client()
# app = client.applications.get_application(service_name)
# ip_list = []
# for app_in in app.up_instances:
# if app_in.metadata.get('ver').lower() == env_name.lower() and app_in.ipAddr:
# ip_list.append(app_in.ipAddr)
# else:
# pass
# if ip_list:
# return ip_list
# else:
# obj_log.info("未获取到ip")
# return False
# except Exception as e:
# obj_log.info(e)
# return False
class EDUUserHelper:
"""
用户中心用户相关操作
"""
def __init__(self):
pass
def get_unregistered_phone(self):
"""
| 功能说明: | 获取未注册的手机号 |
| 输入参数: | |
| 返回参数: | phone |
| 作者信息: | 陈江 | 2022/4/18 |
"""
phone = obj_my_faker.gen_phone_number()
if phone:
is_exit = obj_my_sql_helper.select_one(
'SELECT id FROM `ucenter`.`user_profile` WHERE `phone` = \'{}\''.format(phone[0]))
if is_exit:
obj_log.info('{}手机号码已经存在,正在重新获取'.format(phone))
self.get_unregistered_phone()
else:
return phone[0]
else:
obj_log.error('生成手机号码失败')
return False
def get_unused_email(self):
"""
| 功能说明: | 获取未使用的邮箱 |
| 输入参数: | |
| 返回参数: | email|
| 作者信息: | 陈江 | 2022/4/18 |
"""
email = obj_my_faker.gen_email()
if email:
is_exit = obj_my_sql_helper.select_one(
'SELECT id FROM `ucenter`.`user_contact` WHERE `contact_info` = \'{}\''.format(email))
if is_exit:
obj_log.info('{}邮箱已经存在,正在重新获取'.format(email))
self.get_unused_email()
else:
return email
else:
obj_log.error('生成邮箱失败')
return False
def get_sms_code_by_phone(self, phone):
"""
| 功能说明: | 根据手机号码获取短信验证码 |
| 输入参数: | phone | 手机号|
| 返回参数: | 验证码 |
| 作者信息: | 陈江 | 2022/4/18 |
"""
# 根据phone获取phone_code
phone_server_ip = obj_tools.get_container_ip_from_eureka('PHONE-SERVER', need_jira_id='qa',
eureka_url='http://eureka.qa.huohua.cn')
if phone_server_ip.get('container_ip'):
url = 'http://{}:8080/encrypt/regdata?biztype=phone&uid=123456&sourceData={}'.format(
phone_server_ip.get('container_ip'), phone)
response = requests.post(url=url) # 三个参数
response_json = response.json()
else:
obj_log.info('未获取到phone-server的ip')
return False
if response_json.get('data'):
msg = obj_my_sql_helper.select_all(
'SELECT msg FROM `push_service`.`sms` WHERE `phone_code` = \'{}\' ORDER BY id DESC LIMIT 10 '.format(
response_json.get('data')))
if msg:
for m in msg:
try:
if ',' in m.get('msg'):
msg_split = m.get('msg').split(',')[0]
elif '' in m.get('msg'):
msg_split = m.get('msg').split('')[0]
else:
return False
if ':' in msg_split:
code = msg_split.split(':')[1].strip(' ')
elif '' in msg_split:
code = msg_split.split('')[1].strip(' ')
else:
import re
pattern = r'\b(\d{4,6})\b.*?verification code'
match = re.search(pattern, msg_split)
if match:
verification_code = match.group(1)
return verification_code
return False
return code
except Exception as e:
return e
else:
obj_log.info('未找到发送的短信记录')
return False
else:
obj_log.info('获取{}该手机号的phonecode失败'.format(phone))
return False
if __name__ == '__main__':
a = EDUUserHelper()
print(a.get_sms_code_by_phone('13563963497'))
# c = {'msg': 'SMS verification code: 6378, valid within 10 minutes, please ignore if you are not operating by yourself.'}
#
# print(c.get('msg').split(',')[0].split(':')[1].strip(' '))

View File

@@ -0,0 +1,85 @@
# coding: utf-8
import json
import requests
from base_framework.public_tools.custom_error import BusinessError
class ElasticsearchApi:
"""
| 功能说明: | 查询Elasticsearch |
| 作者信息: | 作者 huaxuemin |
| 修改时间: | 2022-12-05 |
"""
def __init__(self):
self.es_url = "http://es-cn-zvp2bgn8a004cbosn.elasticsearch.aliyuncs.com:9200"
self.headers = {"Authorization": "Basic ZWxhc3RpYzpMa3N3aV5hbEl3"}
def kw_es_query(self, es_query_body, index="user_personas_index_qa", es_url=None, headers=None):
"""
| 功能说明: | 查询es |
| 输入参数: |
| | es_query_body | es请求参数符合es语法 |
| | index | 索引 |
| | es_url | es_url |
| 返回参数: | json |
| 作者信息: | 作者 huaxuemin | 修改时间 2022-12-05 |
说明根据es语法查询es
"""
es_url = es_url if es_url else self.es_url
req_es_url = es_url + "/" + index + "/" + "_search"
headers = self.headers.update(headers) if headers else self.headers
try:
res = requests.post(req_es_url, json=es_query_body, headers=headers)
return json.loads(res.text)
except Exception as e:
return e
def kw_es_replace(self, es_id, es_replace_body, index="user_personas_index_qa", es_url=None, headers=None):
"""
| 功能说明: | 根据id替换es数据 |
| 输入参数: |
| | es_id | es_id |
| | es_query_body | es请求参数符合es语法 |
| | index | 索引 |
| | es_url | es_url |
| 返回参数: | json |
| 作者信息: | 作者 huaxuemin | 修改时间 2022-12-05 |
说明根据es语法替换es数据
"""
es_url = es_url if es_url else self.es_url
req_es_url = es_url + "/" + index + "/" + "_doc" + "/" + es_id
headers = self.headers.update(headers) if headers else self.headers
try:
res = requests.post(req_es_url, json=es_replace_body, headers=headers)
return json.loads(res.text)
except Exception as e:
return e
def kw_dbs_to_es(self, index_name, env="qa", es_svr="db-to-es"):
"""
| 功能说明 | 同步某个索引的es数据 |
| 请求参数名 | 说明 | 类型 | 是否必填 | 如无要求时的值 |
| index_name | 索引名称 | string | 0 | peppa-classes-supply |
| env | 环境qasim | string | 0 | qa |
| es_svr | ES服务db-to-esteach-to-es等 | string | 0 | db-to-es |
:return
详见: https://tm.huohua.cn/162891389180100609/articles/215540479291179010
教务接口切换ES详见apollo配置peppa-teach-common teach.common change.to.es
"""
if env not in ("qa", "sim"):
raise BusinessError("只支持qa和sim")
resp = requests.get(url="http://{}.{}.huohua.cn?index={}".format(es_svr, env, index_name))
if resp.status_code != 200:
raise Exception('同步失败,错误信息:{}'.format(resp.text))
return True
if __name__ == '__main__':
es = ElasticsearchApi()
# print(es.kw_dbs_to_es(index_name='peppa-classes-supply'))
q_sql = {"query":{"bool":{"must":[{"term":{"classes.business_region":"101"}},{"term":{"classes.year":"2023"}}],"must_not":[],"should":[]}},"from":0,"size":10,"sort":[{"classes.id":{"order":"desc"}}],"aggs":{}}
q_url = "http://10.250.200.194:9200"
rsp = es.kw_es_query(es_query_body=q_sql, es_url=q_url, index="peppa-classes-supply2")
print(rsp)

View File

@@ -0,0 +1,243 @@
# -*-coding:utf-8-*-
import requests
import re
import configparser
import time
import os
from base_framework.platform_tools.Message_service.Feishu_api import FeiShuMessage
from base_framework.public_tools.read_config import get_current_config,get_zhyy_config
from base_framework.public_tools.sqlhelper import MySqLHelper
from base_framework.base_config.current_pth import HERE
from bs4 import BeautifulSoup
from base_framework.public_tools.utils import Tools
from lxml import etree
from base_framework.public_tools import log
obj_log = log.get_logger()
obj_tool = Tools()
class EurekaAPI:
def __init__(self):
self.business = get_current_config(section="run_evn_name", key="current_business")
self.team = get_current_config(section="run_evn_name", key="current_team")
self.evn = get_current_config(section="run_evn_name", key="current_evn")
self.jira_id = get_current_config(section="run_jira_id", key="huohua-podenv")
self.is_ip_from_ini = get_current_config(section="is_ip_from_ini", key="is_ip_from_ini")
self.server_list = list()
self.message = FeiShuMessage(team='AUTOMATION')
if self.jira_id == '':
self.jira_id = 'qa'
self.server_to_domain = {"PEPPA-CORE-API": "https://core-api.qa.huohua.cn/"}
self.sim_server_to_domain = {"PEPPA-TEACH-API": "https://teach-api.sim.huohua.cn/"}
def __get_eureka_url_from_db(self, team):
"""
| 功能 | 从DB中获取服务对应的eureka_url供get_container_ip_from_eureka函数使用 |
"""
sql_str = "select eureka_url as hh, eureka_url_hhi as hhi " \
"from sparkatp.swagger_info where team='{}';".format(team)
res = MySqLHelper().select_one(sql=sql_str)
return res[self.business]
def __get_eureka_info_by_type(self, eureka_type):
"""
| 功能 | 根据eureka类型返回对应的url和配置文件名 |
"""
if eureka_type.lower() == "hh":
eureka_url = "http://eureka.{}.huohua.cn/".format(self.evn.lower())
eureka_file_name = "eureka_{}_huohua_cn.ini".format(self.evn.lower())
elif eureka_type.lower() == "vsl":
eureka_url = "http://eureka.{}.visparklearning.com/".format(self.evn.lower())
eureka_file_name = "eureka_{}_visparklearning_com.ini".format(self.evn.lower())
elif eureka_type.lower() == "as":
eureka_url = "http://eureka.{}.allschool.com/".format(self.evn.lower())
eureka_file_name = "eureka_{}_allschool_com.ini".format(self.evn.lower())
elif eureka_type.lower() == "ec":
eureka_url = "http://eureka-core.{}.huohua.cn/".format(self.evn.lower())
eureka_file_name = "eureka_core_{}_huohua_cn.ini".format(self.evn.lower())
elif eureka_type.lower() == "xdu":
eureka_url = "http://eureka.{}.xuedau.com/".format(self.evn.lower())
eureka_file_name = "eureka_{}_xuedau_com.ini".format(self.evn.lower())
else:
raise Exception("eureka类型仅支持HH,VSL,AS,EC和XDU但你的输入的是{}".format(eureka_type))
return eureka_url, eureka_file_name
def get_all_server_ip_from_eureka(self, eureka="HH"):
"""
| 功能说明: | 获取Eureka全部服务的IP并写入对应文件中文件中 |
| 输入参数: | eureka | 类型HH | http://eureka.qa.huohua.cn/ |
| | | 类型VSL | http://eureka.qa.visparklearning.com/ |
| | | 类型AS | http://eureka.qa.allschool.com/ |
| | | 类型EC | http://eureka-core.qa.huohua.cn/ |
| | | 类型XDU | http://eureka.qa.xuedau.com/ |
| 返回参数: | 无 | |
| 作者信息: | 谯新久 | 2022.03.27 |
"""
eureka_url, eureka_file_name = self.__get_eureka_info_by_type(eureka_type=eureka)
server_ip_path = os.path.join(HERE, eureka_file_name)
temp_text = requests.get(url=eureka_url).content
soup = BeautifulSoup(temp_text.decode('utf-8'), "html.parser")
all_container_ip = soup.find_all(href=re.compile("actuator/info"))
tree_dict = dict()
for temp in all_container_ip:
server = list(temp.parent.parent)[1].get_text()
server_ip = list(temp)
if server in tree_dict.keys():
tree_dict[server].extend(server_ip)
else:
tree_dict[server] = server_ip
cof = configparser.ConfigParser()
cof.read(server_ip_path, encoding='utf-8')
eureka_section = self.business
if eureka_section not in cof.sections():
cof.add_section(section=eureka_section)
for svr_name in tree_dict:
cof.set(section=eureka_section, option=svr_name, value=str(tree_dict[svr_name]))
with open(server_ip_path, 'w') as fw: # 循环写入
cof.write(fw)
time.sleep(2) # 等待5s让ip写入到文件中去
def get_server_ip_from_config(self, server_name, eureka="HH"):
"""
| 功能说明: | 获取server_ip.ini文件中获取server_name对应的IP |
| 输入参数: | server_name | 服务名 |
| | eureka | 类型HH | http://eureka.qa.huohua.cn/ |
| | | 类型VSL | http://eureka.qa.visparklearning.com/ |
| | | 类型AS | http://eureka.qa.allschool.com/ |
| | | 类型EC | http://eureka-core.qa.huohua.cn/ |
| 返回参数: | string | 如10.10.10.10:8080 |
| 作者信息: | 谯新久 | 2022.03.27 |
特别说明:
| 1 | 独立环境取startup.py启动时传入的值没有对应的独立环境则取qa的ip返回 |
| 2 | 当同一独立环境存在多个ip时打印error日志后返回第一个ip |
"""
eureka_url, eureka_file_name = self.__get_eureka_info_by_type(eureka_type=eureka)
server_ip_path = os.path.join(HERE, eureka_file_name)
ip_list = get_current_config(file_path=server_ip_path, section=self.business, key=server_name.lower())
if self.evn.lower() == "sim":
ip_list = ip_list.replace("${server.port}", "8080") # 替换成8080端口
if self.jira_id in ip_list:
jira = self.jira_id
elif "sim" in ip_list:
jira = "sim"
elif "no" in ip_list:
jira = "no"
else:
jira = ""
elif self.jira_id in ip_list:
jira = self.jira_id
elif 'qa' in ip_list:
jira = 'qa'
elif 'groot' in ip_list:
jira = 'groot'
else:
jira = 'qa'
if ip_list != 'server not exist':
if jira and jira not in ip_list:
if server_name.lower() not in self.server_list:
self.server_list.append(server_name.lower())
message = "{}在启动startup时发现服务{}在独立环境{}未部署成功,请确认。".format(self.team, server_name, jira)
self.message.send_text(message)
obj_ip = []
ip_str = ip_list.replace('[', '').replace(']', '').replace('\'', '').replace(' ', '')
ip_list = ip_str.split(',')
for ip in ip_list:
if jira and jira in ip: # 找到对应独立环境的ip
if ip.startswith('10.'): # 跳过非
obj_ip.append(ip)
if self.evn.lower() == "sim": # sim环境只有一个ip时直接返回次ip
if not obj_ip and len(ip_list) == 1:
ips = ip_list[0].split(":")
return "{}:{}".format(ips[0], ips[1])
if self.evn.lower() != "sim" and len(obj_ip) > 1: # sim环境不判断是否有多个部署
obj_log.error("eureka中含有{}{}{}环境,请检查.........".format(len(obj_ip), server_name, jira))
elif len(obj_ip) == 0:
obj_log.error("eureka中未找到{}{}相关的ip信息请检查.........".format(server_name, jira))
return "server not exist"
# return obj_ip[0].rstrip("{}".format(jira))[:-1]
return obj_ip[0].split("{}".format(jira))[0].rstrip(":")
else:
obj_log.error("eureka中未找到{}{}相关的ip信息请检查.........".format(server_name, jira))
return "server not exist"
def get_server_url_from_config(self, server_name, eureka="HH", is_domain=True):
"""
| 功能说明: | 从对应文件中获取server_name对应的IP组装成url后返回 |
| 输入参数: | server_name | 服务名 |
| | eureka | 类型HH | http://eureka.qa.huohua.cn/ |
| | | 类型VSL | http://eureka.qa.visparklearning.com/ |
| | | 类型AS | http://eureka.qa.allschool.com/ |
| | | 类型EC | http://eureka-core.qa.huohua.cn/ |
| | | 类型XDU | http://eureka.qa.xuedau.com/ |
| | is_domain | 是否域名True域名False IP |
| 返回参数: | string | 如http://10.10.10.10:8080/ |
| 作者信息: | 谯新久 | 2022.03.27 |
特别说明:
| 1 | 独立环境取startup.py启动时传入的值没有对应的独立环境则取qa的ip返回 |
| 2 | 当同一独立环境存在多个ip时打印error日志后返回第一个ip |
"""
if is_domain:
if self.evn == "SIM":
if server_name.upper() in self.sim_server_to_domain.keys():
return self.sim_server_to_domain[server_name.upper()]
return "https://swagger.sim.huohua.cn/{}/".format(server_name.lower())
if server_name.upper() in self.server_to_domain.keys():
return self.server_to_domain[server_name.upper()]
return "https://swagger.qa.huohua.cn/{}/".format(server_name.lower())
else:
# eureka_url, _ = self.__get_eureka_info_by_type(eureka_type=eureka)
# ip_res = obj_tool.get_container_ip_from_eureka(server_name=server_name, jira_id_dev=self.jira_id,
# eureka_url=eureka_url)
# if ip_res:
# ip_info = ip_res["container_ip"]
# url_info = "http://" + ip_info + ":8080/"
# return url_info
# # if self.is_ip_from_ini == "true":
ip_info = self.get_server_ip_from_config(server_name=server_name.lower(), eureka=eureka)
if ip_info != 'server not exist':
url_info = "http://" + ip_info + "/"
return url_info
else:
raise Exception("eureka中未找到{}{}相关的ip信息请检查.........".format(server_name, self.jira_id))
def get_url_from_config(self, is_domain=True):
"""
| 功能说明: | 从对应文件中获取对应环境的域名组装成url后返回 |
| 输入参数: | 环境 | 服务名 |
| | is_domain | 是否域名True域名False IP |
| 返回参数: | string | 如http://10.10.10.10:8080/ |
| 作者信息: | 谯新久 | 2026.01.15 |
"""
if is_domain:
env = self.evn.upper()
team = self.team.lower()
domain_url = get_zhyy_config(section=env, key=team)
return domain_url
else:
#todo 暂无ip
return
# # if self.is_ip_from_ini == "true":
# ip_info = self.get_server_ip_from_config(server_name=server_name.lower(), eureka=eureka)
# if ip_info != 'server not exist':
# url_info = "http://" + ip_info + "/"
# return url_info
# else:
# raise Exception("eureka中未找到{}的{}相关的ip信息请检查.........".format(server_name, self.jira_id))
if __name__ == '__main__':
er = EurekaAPI()
# er.get_all_server_ip_from_eureka
# er.get_all_server_ip_from_eureka(eureka="VSL")
res = er.get_server_url_from_config(server_name='peppa-scm-server', is_domain=False)
print(res)
# res = er.get_server_url_from_config(server_name='sparkedu-api', eureka="VSL")
# print(res)

View File

@@ -0,0 +1,272 @@
# -*- coding: UTF-8 -*-
# @File: operation_Xlsx.py
# @Description: excle的基本操作
# @Author: WenQing
# @Date: 2021-08-26 11:15:08
import os
import xlrd, xlwt
import pandas as pd
from xlutils.copy import copy
import openpyxl
from base_framework.base_config.current_pth import env_choose_path
from base_framework.public_tools.read_config import ReadConfig
class ExcelApi:
def __init__(self):
self.p_here = os.path.dirname(os.path.abspath(__file__))
self.c_team = ReadConfig(env_choose_path).get_value(sections='run_evn_name', options='current_team')
self.up_file_path = os.path.abspath(os.path.join(self.p_here, '../../{}/library/UpFile/'.format(self.c_team)))
def excel_create_excel_file(self, file_name, file_dict_data=None, file_path=None):
"""
功能按列将数据写入excel文件没有文件时就新建文件
Args:
file_name: 文件名
file_dict_data: 文件内容,字典类型,格式:{'title1':[], 'title2':[],..... 'titleN':[],}
file_path: 文件路径没有时默认放在小组目录下的UpFile文件夹中
Returns:
"""
if not file_path:
full_path = self.up_file_path + os.sep + file_name
else:
full_path = file_path + os.sep + file_name
if not file_dict_data:
file_dict_data = {}
df = pd.DataFrame(file_dict_data)
# 将 DataFrame 的数据保存到 Excel 文件中
df.to_excel(full_path, index=False)
def excel_create_file_by_column(self, file_name, file_dict_data=None, file_path=None):
"""
功能按列将数据写入excel文件没有文件时就新建文件
Args:
file_name: 文件名
file_dict_data: 文件内容,字典类型,格式:{'title1':[], 'title2':[],..... 'titleN':[],}
file_path: 文件路径没有时默认放在小组目录下的UpFile文件夹中
Returns:
"""
if not file_path:
full_path = self.up_file_path + os.sep + file_name
else:
full_path = file_path + os.sep + file_name
if not file_dict_data:
file_dict_data = {}
df = pd.DataFrame(file_dict_data)
# 将 DataFrame 的数据保存到 Excel 文件中
df.to_excel(full_path, index=False)
def excel_write_file_by_line(self, file_name, line_data=None, file_path=None, w_type='new'):
"""
功能按行将数据写入excel文件没有文件时就新建文件
Args:
file_name: 文件名
line_data: 文件内容,列表类型,格式:[[字段1,字段2],[字段1,字段2]]
file_path: 文件路径没有时默认放在小组目录下的UpFile文件夹中
w_type: 写入类型new-重写append-追加
"""
if not file_path:
full_path = self.up_file_path + os.sep + file_name
else:
full_path = file_path + os.sep + file_name
if w_type == 'new':
self.write_xlsx(path=full_path, sheet_name="Sheet1", value=line_data)
elif w_type == 'append':
self.append_xlsx(path=full_path, value=line_data)
else:
raise Exception("目前仅支持new和append两种模式而当前传入的是{}".format(w_type))
def excel_replace_cell_value(self, file_name, replace_dict, sheet_name=None, skip_first_row=True):
"""
功能替换excel文件中的指定单元格的值
Args:
file_name: 文件名,如果文件不在UpFile文件夹中需要传入完整路径
sheet_name: 工作表名称,默认为全部工作表
replace_dict: 替换的数据,字典类型,格式:{'old_txt_1': 'new_txt_1', 'old_txt_2': 'new_txt_2',..... }
skip_first_row: 是否跳过第一行,默认跳过,用于第一行是标题的情况
Returns:
"""
if "/" not in file_name: # 如果没有路径就默认在UpFile文件夹中
file_name = self.up_file_path + os.sep + file_name
# 读取指定工作表的数据
wb = openpyxl.load_workbook(file_name)
# 遍历所有要替换的数据
for old_text, new_text in replace_dict.items():
# 遍历所有工作表
for sheet in wb.worksheets:
# 遍历工作表中的所有行和列
for row in sheet.iter_rows():
if skip_first_row: # 跳过第一行
skip_first_row = False
continue
for cell in row:
if cell.value == old_text:
cell.value = new_text
wb.save(file_name)
def excel_read_columns(self, file_name, sheet_name, column_names):
"""
功能按列读取excel文件的数据
Args:
file_name: 文件名如果文件不在UpFile文件夹中需要传入完整路径
sheet_name: 工作表名称
column_names: 列名列表,格式:['title1', 'title2',..... 'titleN']
Returns:
返回指定列的数据,格式为:[[字段1,字段2],[字段1,字段2]]
"""
if "/" not in file_name: # 如果没有路径就默认在UpFile文件夹中
file_name = self.up_file_path + os.sep + file_name
# 读取指定工作表的数据
df = pd.read_excel(file_name, sheet_name=sheet_name)
# 检查指定列是否存在
missing_columns = [col for col in column_names if col not in df.columns]
if missing_columns:
raise ValueError(f"Columns {missing_columns} do not exist in the sheet '{sheet_name}'.")
# 返回指定列的数据
return df[column_names].values.tolist()
def read_asDict(self, path):
"""
| 功能说明: | 读取excle,输出字典格式|
| 传入参数: | 读取文件路径 |
| 返回数据: | 表头字段作为key,单元格值作为value |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
self.table = pd.read_excel(path)
data = []
for i in self.table.index.values:
data_dict = self.table.loc[i].to_dict()
data.append(data_dict)
return data
def read_asList(self, path, sheetname=None):
"""
| 功能说明: | 读取excle和sheetname,输出列表格式|
| 传入参数: | 读取excle和sheetname |
| 返回数据: | 列表[] |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
wb = openpyxl.load_workbook(path)
if not sheetname:
sheets = wb.sheetnames
sheetname = sheets[0]
ws = wb[sheetname]
rows = ws.rows
columns = ws.columns
data = []
for row in rows:
line = [col.value for col in row]
data.append(line)
return data
def read_xls_txt(self, path):
"""
| 功能说明: | 读取excle的第一列放在一个列表中|
| 传入参数: | 读取excle路径 |
| 返回数据: | 列表[] |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
data = pd.read_excel(path, header=None)
data_list = []
nrows = data.shape[0]
for irow in range(nrows):
data_list.append(data.iloc[irow, 0])
string = ''
for i in range(len(data_list)):
string += data_list[i] + ','
return string
def write_xls(self, value, sheetname, path):
"""
| 功能说明: | 创建一个xls的文件并且写入数据 |
| 传入参数: | value:传入列表,格式:[[字段1,字段2],[字段1,字段2],[字段1,字段2],[字段1,字段2]] |
|sheetname:sheet名称 |
|path:写入文件路径 |
| 返回数据: | |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
index = len(value) # 获取需要写入数据的行数
workbook = xlwt.Workbook() # 新建一个工作簿
sheet = workbook.add_sheet(sheetname=sheetname) # 在工作簿中新建一个sheetname的表格
for i in range(0, index):
for j in range(0, len(value[i])):
sheet.write(i, j, value[i][j])
workbook.save(path)
# return "写入成功!"
def append_xls(self, value, path):
"""
| 功能说明: | 对xls文件第一个sheet内容进行追加 |
| 传入参数: | value:传入列表,格式:[[字段1,字段2],[字段1,字段2],[字段1,字段2],[字段1,字段2]] |
|path:写入文件路径 |
| 返回数据: | |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
index = len(value) # 获取需要写入数据的行数
workbook = xlrd.open_workbook(path) # 打开工作簿
sheets = workbook.sheet_names() # 获取所有表格
worksheet = workbook.sheet_by_name(sheets[0]) # 获取所有表格中的第一个表格
rows_old = worksheet.nrows # 获取表格的总行数
new_workbook = copy(workbook) # 把原文件复制一份
new_worksheet = new_workbook.get_sheet(0) # 获取副本第一个表格
for i in range(0, index):
for j in range(0, len(value[i])):
new_worksheet.write(i + rows_old, j, value[i][j])
new_workbook.save(path)
# return "追加成功!!"
def write_xlsx(self, path, sheet_name, value):
"""
| 功能说明: | 创建一个xls的文件并且写入数据 |
| 传入参数: | value:传入列表,格式:[[字段1,字段2],[字段1,字段2],[字段1,字段2],[字段1,字段2]] |
|sheetname:sheet名称 |
|path:写入文件路径 |
| 返回数据: | |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
index = len(value)
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = sheet_name
for i in range(0, index):
for j in range(0, len(value[i])):
sheet.cell(row=i + 1, column=j + 1, value=str(value[i][j]))
workbook.save(path)
return "写入成功!!"
def append_xlsx(self, path, value):
"""
| 功能说明: | 对xls文件第一个sheet内容进行追加 |
| 传入参数: | value:传入列表,格式:[[字段1,字段2],[字段1,字段2],[字段1,字段2],[字段1,字段2]] |
|path:写入文件路径 |
| 返回数据: | |
| 作者信息: | 作者 文青 | 修改时间 |
举例说明:
"""
data = openpyxl.load_workbook(path)
sheets = data.sheetnames
table = data[sheets[0]]
for i in value:
table.append(i)
data.save(path)
return "追加成功!!"
if __name__ == '__main__':
ea = ExcelApi()
f_data = {"教师ID": [1, 2, 3, 4],
"教师姓名": [11, 22, 33, 44],
"课程ID": [111, 222, 333, 444],
"课程名称": [1111, 2222, 3333, 4444],
"更新类型1.新增0.删除,填写数字)": [0, 1, 0, 1]
}
ea.excel_create_excel_file(file_name="测试大盘A标签.xlsx", file_dict_data=f_data)

View File

@@ -0,0 +1,911 @@
import json
from urllib import parse
import requests
from retrying import retry
from base_framework.public_tools.read_config import InitConfig
from base_framework.public_tools.read_config import ReadConfig, get_current_config, get_current_env
from base_framework.base_config.current_pth import *
from requests.packages.urllib3.connectionpool import InsecureRequestWarning
from base_framework.public_tools import log
from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
import base64
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import time
import re
# get_cfg = ReadConfig(env_choose_path)
token_dict = dict()
student_token_cache = {}
obj_log = log.get_logger()
class LazyProperty:
def __init__(self, fun):
self.fun = fun
def __get__(self, instance, owner):
if instance is None:
return self
value = self.fun(instance)
setattr(instance, self.fun.__name__, value)
return value
class LoginSys(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
try:
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
except Exception as e:
print(e)
self.host = host
self.curt_user = self.current_user
self.env = get_current_env()
@LazyProperty
def get_session(self):
"""
获取SSO session
:return:
"""
sys_type = self.host.split(".")
if 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
api_url = self.curt_sso_url
elif 'hulk-content-audit-server' in sys_type or 'hulk-content-manage-gateway' in sys_type or 'hulk-class-help-manager' in sys_type or 'hulk-ark-gateway' in sys_type or 'manage-api' in sys_type :
api_url = self.all_school_sso_url
else:
api_url = self.curt_sso_url
post_data = dict()
post_data['showUsername'] = self.show_username
post_data['username'] = self.username
post_data['password'] = self.password
req_session = requests.session()
resp = req_session.post(
url=api_url,
data=post_data,
allow_redirects=False,
verify=False)
return req_session
@LazyProperty
def get_code(self):
"""
获取code
:return:
"""
post_data = dict()
sys_type = self.host.split(".")
if 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
post_data['client_id'] = "vispark-crm"
post_data['response_type'] = 'code'
post_data['redirect_uri'] = self.crm_vispark_url
authorization = self.cc_auth
# elif sys_type == "smm":
# post_data['client_id'] = "sms-manage"
# post_data['response_type'] = 'code'
# post_data['redirect_uri'] = self.sms_url
if 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
api_url = self.get_code_url
elif 'hulk-content-audit-server' in sys_type or 'hulk-content-manage-gateway' in sys_type or 'hulk-class-help-manager' in sys_type or 'hulk-ark-gateway' in sys_type or 'manage-api' in sys_type:
api_url = self.get_all_school_code_url
else:
api_url = self.get_code_url
req_session = self.get_session
if 'mq-console' in sys_type:
allow_redirects = True
else:
allow_redirects = False
obj_log.info(api_url)
obj_log.info(post_data)
resp = req_session.get(
url=api_url,
params=post_data,
allow_redirects=allow_redirects,
verify=False)
if 'mq-console' in sys_type:
return [req_session, 'MQ']
code_temp = resp.headers
obj_log.info(code_temp)
code = code_temp.get('location')
obj_log.info(code)
if 'client_id=gmp' in code:
return code
try:
code = code.split("=")[-1]
except Exception:
raise IndexError("获取token失败请检查环境或者登录信息是否正确")
resp_list = [code, post_data['redirect_uri'], authorization]
return resp_list
# @LazyProperty
def get_token(self, type_name=None):
"""
获取token
:return:
"""
try:
token = token_dict[self.host].get(self.curt_user, None)
except KeyError as e:
token = False
if token:
return token
else:
token_dict_temp = dict()
sys_type = self.host.split(".")
post_data = dict()
temp_code = self.get_code
if isinstance(temp_code, str):
session = self.get_session
resp = session.get(url=temp_code)
for key, value in session.cookies.items():
if key == 'peppa_sso_token':
token = value
break
token_header = {"accesstoken": token}
session.headers.update(token_header)
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
session.headers.update(podenv)
token_dict_temp[self.curt_user] = session
token_dict[self.host] = token_dict_temp
return session
if temp_code[1] == 'MQ': # MQ特殊处理
return temp_code[0]
post_data['code'] = temp_code[0]
post_data['redirect_uri'] = temp_code[1]
post_data['grant_type'] = 'authorization_code'
req_session = self.get_session
authorization = temp_code[2]
if authorization:
header = {'authorization': authorization}
if 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
resp = req_session.get(
url=api_url+'&code='+temp_code[0], data=post_data, headers=header, allow_redirects=False, verify=False)
elif 'visparklearning' in sys_type and 'manage-gw' in sys_type:
resp = req_session.get(url=api_url + '&code=' + temp_code[0], data=post_data, headers=header,
allow_redirects=False, verify=False)
else:
resp = req_session.post(
url=api_url, data=post_data, headers=header, allow_redirects=False, verify=False)
if 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
token_temp = parse.parse_qs(parse.urlparse(resp.headers['location']).query)
elif 'visparklearning' in sys_type and 'manage-gw' in sys_type:
token_temp = parse.parse_qs(parse.urlparse(resp.headers['location']).query)
else:
token_temp = resp.json()
if type_name == 'BFF': # bff架构使用
token = {"token": token_temp['access_token']}
elif 'visparklearning' in sys_type and ('cc-manage' or 'cti-manage' in sys_type):
token = {"accesstoken": token_temp['peppa_sso_token'][0]}
elif 'visparklearning' in sys_type and 'manage-gw' in sys_type:
token = {"accesstoken": token_temp['peppa_sso_token'][0]}
else:
if "access_token" not in token_temp:
obj_log.warning("---------获取token失败-------")
obj_log.warning("type_name:{}\ntoken_temp:{}".format(type_name, token_temp))
obj_log.warning("-----------------------------")
token = {"accesstoken": token_temp['access_token']}
req_session.headers.update(token)
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
req_session.headers.update(podenv)
token_dict_temp[self.curt_user] = req_session
token_dict[self.host] = token_dict_temp
return req_session
else:
if 'kunpeng' in post_data['redirect_uri']: # 鲲鹏/mp系统登录
req_session.get(url=post_data['redirect_uri'] + '&code=' + temp_code[0], verify=False)
return req_session
elif 'allschool-mp' in post_data['redirect_uri']:
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
req_session.headers.update(podenv)
req_session.get(url=post_data['redirect_uri'] + '&code=' + temp_code[0], verify=False)
access_token = req_session.cookies.get("peppa_sso_token")
req_session.headers.update({"accesstoken": access_token})
return req_session
run_session = self.sso_oauth_login(session=req_session, code=temp_code[0])
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
run_session.headers.update(podenv)
token_dict_temp[self.curt_user] = run_session
token_dict[self.host] = token_dict_temp
return run_session
def sso_oauth_login(self, type='manage', session=None, code=None):
"""
| 功能说明: | |
| 输入参数: | | |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
举例说明:
| sso_oauth_login | |
"""
if type == 'manage':
url = self.manage_url + '?code=' + code
elif type == 'opengalaxy': # 独立环境发布系统
url = self.opengalaxy_url + '?code=' + code
session.get(url=url, allow_redirects=True, verify=False)
return session
def ug_hhi_sso_login_by_session_id(self):
"""
ug的manage后台登录获取sessionId
:return:
"""
req_session = requests.session()
headers = {"Content-Type": "application/json"}
# sso登录
url_form = self.curt_sso_url
obj_log.info(url_form)
post_data = dict()
post_data['showUsername'] = self.show_username
post_data['username'] = self.username
post_data['password'] = self.password
res_form = req_session.post(url=url_form, data=post_data, allow_redirects=False, verify=False)
obj_log.info("ug_hhi_sso_login_by_session_id_url_form:{}".format(url_form))
obj_log.info("ug_hhi_sso_login_by_session_id_res_form:{}".format(res_form.headers))
# 进行sso.qa.sparkedu.com/oauth/authorize
url_authorize = "{}?client_id=manage&response_type=code&redirect_uri={}".format(
self.get_code_url, self.manage_url)
res_authorize = req_session.get(url_authorize, allow_redirects=False)
obj_log.info("ug_hhi_sso_login_by_session_id_url_authorize:{}".format(url_authorize))
obj_log.info("ug_hhi_sso_login_by_session_id_res_authorize:{}".format(res_authorize.headers))
# 获取到res_authorize的location地址(主要是用到code)
url_sso_oauth_login = res_authorize.headers.get('location')
obj_log.info("ug_hhi_sso_login_by_session_id_url_sso_oauth_login:{}".format(url_sso_oauth_login))
res_url_sso_oauth_login = req_session.get(url_sso_oauth_login, allow_redirects=False)
obj_log.info("ug_hhi_sso_login_by_session_id_res_url_sso_oauth_login:{}".format(res_url_sso_oauth_login.headers))
# 因为环境问题所以多请求一次域名
res_manage_host = req_session.get(self.manage_host)
obj_log.info("ug_hhi_sso_login_by_session_id_url_manage:{}".format(self.manage_host))
obj_log.info(
"ug_hhi_sso_login_by_session_id_res_manage_host:{}".format(res_manage_host.headers))
token_dict_temp = dict()
token_dict_temp[self.curt_user] = req_session
token_dict[self.host] = token_dict_temp
return req_session
def ug_sso_login_by_starlight(self):
"""
ug的starlight.qa.huohua.cn登录
:return:
"""
req_session = requests.session()
headers = {"Content-Type": "application/json"}
# 访问starlight.qa.huohua.cn/生成JSESSION_STARLIGHT
starlight_url = self.starlight_url
obj_log.info(starlight_url)
req_session.get(starlight_url, allow_redirects=False)
# sso登录
url_form = self.curt_sso_url
obj_log.info(url_form)
post_data = dict()
post_data['showUsername'] = self.show_username
post_data['username'] = self.username
post_data['password'] = self.password
res_form = req_session.post(url=url_form, data=post_data, allow_redirects=False, verify=False)
obj_log.info(f'res_form的header{res_form.headers}')
# 进行sso.qa.sparkedu.com/oauth/authorize
url_authorize = "{}?client_id=starlight&response_type=code&redirect_uri={}".format(
self.get_code_url, self.suyang_manage_sso_login_url)
res_authorize = req_session.get(url_authorize, allow_redirects=False)
obj_log.info(f"res_authorize的header{res_authorize.headers.get('location')}")
# 获取到res_authorize的location地址(主要是用到code)
req_session.get(res_authorize.headers.get('location'), allow_redirects=False)
# 访问https://starlight.qa.huohua.cn/
req_session.get(self.starlight_url, allow_redirects=False)
token_dict_temp = dict()
token_dict_temp[self.curt_user] = req_session
token_dict[self.host] = token_dict_temp
return req_session
class SparkleLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=None, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=None, current_evn=current_evn)
self.pc_token = None
self.session = requests.session()
# self.jira_id = get_cfg.get_value(sections='run_jira_id', options='huohua-podenv')
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
# def sparkle_pc_login(self, phone, passwd=123456):
def sparkle_pc_login(self, phone, passwd='Mima@123'):
if token_dict.get(str(phone)):
return token_dict.get(str(phone))
url = self.sparkle_pc_token_url
data = {"username": phone, "userType": 2, "password": str(passwd)}
self.session.headers.update({"Authorization": self.spark_pc_auth})
resp = json.loads(self.session.post(url, params=data).content)
header = {"account-token": resp.get("data").get("accessToken")}
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
self.session.headers.update(podenv)
self.session.headers.update(header)
token_dict[str(phone)] = self.session
return self.session
class AgentApiLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=None, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=None, current_evn=current_evn)
self.agent_api_token = None
self.session = requests.session()
# self.jira_id = get_cfg.get_value(sections='run_jira_id', options='huohua-podenv')
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def agent_api_login(self, phone, authCode, countryCode=86):
if token_dict.get(str(phone)):
return token_dict.get(str(phone))
url = '{}/login/loginByAuthCode'.format(self.hhr_api_host)
data = {"phone": str(phone), "authCode": str(authCode), "countryCode": str(countryCode)}
resp = json.loads(self.session.post(url, json=data).content)
header = {"user-token": resp.get("data").get("token")}
if self.jira_id:
podenv = {"huohua-podenv": self.jira_id}
self.session.headers.update(podenv)
self.session.headers.update(header)
token_dict[str(phone)] = self.session
return self.session
class MarketApiLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
InitConfig.__init__(self, run_user_name=None, current_evn=current_evn)
self.session = requests.session()
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def market_api_login(self, user=None):
login_url = self.market_url
result = parse.urlparse(url=login_url ,allow_fragments=True)
host = result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
if host in choose_user:
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name' ,options=host)
self.current_user = default_user
if user:
self.current_user = user
try:
login_user = ReadConfig().get_value(sections=self.current_user ,options='username')
login_password = ReadConfig().get_value(sections=self.current_user ,options='password')
except Exception as e:
login_user = ReadConfig(astwb_config).get_value(sections=self.current_user ,options='username')
login_password = ReadConfig(astwb_config).get_value(sections=self.current_user ,options='password')
post_data = {'phone': login_user ,'password': login_password}
resp = self.session.post(url=login_url ,json=post_data , verify=False,
headers={'huohua-podenv': self.jira_id})
if resp.status_code != 200:
raise KeyError('获取 市场管理投放系统 token失败')
set_cookie = resp.headers['set-cookie'].split(";")[-1].split(",")[1]
headers = {"Cookie":"gray_id=3077c7810744b47fc06ec513cf07801e; gray_tag=stable; "+set_cookie}
self.session.headers.update(headers)
return self.session
class AllSchool(InitConfig):
def __init__(self, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def all_school_token(self, user=None, login_type=1):
"""
:param user: 登录用户
:param login_type: 登录系统 1教师工作台
2机构工作台
3选课平台
:return: token
"""
if login_type == 1:
login_url = self.all_school_url
elif login_type == 2:
login_url = self.all_school_org_url
elif login_type == 3:
login_url = self.find_classes_url
result = parse.urlparse(url=login_url, allow_fragments=True)
host = result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
if host in choose_user:
if login_type == 2:
host = 'org-' + host
elif login_type == 3:
host = 'find-' + host
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name', options=host)
self.current_user = default_user
if user:
self.current_user = user
try:
login_user = ReadConfig().get_value(sections=self.current_user, options='username')
login_password = ReadConfig().get_value(sections=self.current_user, options='password')
except Exception as e:
login_user = ReadConfig(astwb_config).get_value(sections=self.current_user, options='username')
login_password = ReadConfig(astwb_config).get_value(sections=self.current_user, options='password')
if int(login_type) == 3:
post_data = {"data": {"email": login_user, "password": login_password, "countryCode": "86",
"loginType": "EMAIL_PSW"}}
else:
post_data = {'email': login_user, 'password': login_password}
as_token = requests.post(url=login_url, json=post_data, verify=False,
headers={'huohua-podenv': self.jira_id})
rtn_temp = as_token.json()
if as_token.status_code != 200:
raise KeyError('获取 allSchool token失败')
user_token = rtn_temp['data']['token']
return user_token
class ParentLogin(InitConfig):
def __init__(self,host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
@retry(stop_max_attempt_number=5, wait_fixed=3000)
def parent_send_msg(self, phone, authType=2, countryCode=None,
sessionId=None, **kwargs):
"""
| 功能说明: | M站学生端发送短信 |
| 输入参数: | phone | 手机号 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
举例说明:
| m_send_msg | |
"""
url = self.m_host + '/passport/auth_code/send'
post_data = {
"phone": phone,
"authType": authType,
"countryCode": countryCode,
"isLogin": False,
"sessionId": sessionId}
resp = requests.post(
url=url,
json=post_data,
verify=False)
time.sleep(2)
return resp
def get_parent_sms_code(self, phone):
"""
M站获取手机验证码
:param phone:电话号码
:return: token
"""
auth_url = self.curt_sso_url
get_code_url = self.get_code_url
token_url = self.get_token_url
sms_url = "https://smm.qa.huohua.cn/sms/log/page/all?WHERE.%5BEQ%5Dphone={}&WHERE.%5BEQ%5DtypeName=&WHERE.%5BEQ%5Dchannel=&WHERE.%5BEQ%5DgatewayType=&WHERE.%5BGT%5DcreateTime=&WHERE.%5BLT%5DcreateTime=&WHERE.%5BGT%5DsubmitTime=&WHERE.%5BLT%5DsubmitTime=&pageNum=1&pageSize=20&orderBy=id%20desc".format(
phone)
session = requests.session()
session.post(url=auth_url, verify=False,
data={'showUsername': 'liuruiquan', 'username': 'liuruiquan@huohua.cn', 'password': 'lrq5823LRQ'})
get_code = session.get(url=get_code_url, verify=False,
params={'client_id': 'crmnew', 'response_type': 'code',
'redirect_uri': 'https://crmv2.qa.huohua.cn'}, allow_redirects=False)
code_temp = get_code.headers
code = code_temp['location']
code = code.split("=")[-1]
token_get = session.post(url=token_url, verify=False, data={'code': code, 'redirect_uri': 'https://crmv2.qa.huohua.cn',
'grant_type': 'authorization_code'},
headers={'authorization': 'Basic Y3JtbmV3OmNybW5ldw=='})
token_temp = token_get.json()
token = {"accesstoken": token_temp['access_token']}
sms_temp = session.post(url=sms_url, verify=False, headers=token).json()
smm_code = sms_temp['list'][0]['msg']
reg = re.compile(r"(?<=手机短信验证码:)\d+")
match = reg.search(smm_code)
smm_code = match.group(0)
return smm_code
def get_parent_token(self, phone=None):
"""
M站token获取
:param phone:电话号码
:return: token
"""
login_url = "http://m.qa.huohua.cn/passport/login"
result = parse.urlparse(url=login_url, allow_fragments=True)
host = 'parent-' + result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
if host in choose_user:
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name', options=host)
if phone:
user = ReadConfig().get_value(sections=phone, options='username')
self.current_user = user
else:
phone = ReadConfig().get_value(
sections=default_user, options='username')
self.current_user = phone
if student_token_cache.get(self.current_user):
return student_token_cache[self.current_user]
send_code = self.parent_send_msg(phone=self.current_user).json()
# if not send_code['success']:
# raise ValueError(send_code)
verify_code = self.get_parent_sms_code(phone=self.current_user)
post_data = {"phone": self.current_user, "authCode": verify_code, "countryCode": None, "loginType": 1,
"userType": 1,
"subjectType": 0}
m_token = requests.post(url=login_url, json=post_data, verify=False, headers={'huohua-podenv': self.jira_id})
if m_token.status_code == 200:
token = m_token.json()
token = token['data']['token']
student_token_cache[self.current_user] = token
return token
else:
raise KeyError('获取 parent token失败')
def get_parent_token_by_pwd(self, phone=None):
env_name = get_current_config(section='run_evn_name', key='current_business')
obj_log.info("env_name:{}".format(env_name))
if env_name and env_name=='hhi':
login_url = "https://hhi-user-auth-api.qa.visparklearning.com/login"
elif 'hhi' in phone.split('-'):
login_url = "https://pst-gw.qa.huohua.cn/passport/login"
else:
login_url = "http://m.qa.huohua.cn/passport/login"
result = parse.urlparse(url=login_url, allow_fragments=True)
host = 'parent-' + result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
obj_log.info("host:{},choose_user:{}".format(host, choose_user))
if host in choose_user:
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name', options=host)
if phone.split('-')[1]:
self.current_user = phone.split('-')[1]
else:
phone = ReadConfig(config_file_path).get_value(
sections=default_user, options='username')
self.current_user = phone
else:
phone = ReadConfig(config_file_path).get_value(
sections='lzp', options='username')
self.current_user = phone
if student_token_cache.get(self.current_user):
return student_token_cache[self.current_user]
# 支持传入海外区号用户
if '+' in self.current_user :
phone_list =self.current_user.split("+")
countryCode = phone_list[1]
self.current_user = phone_list[0]
else:
countryCode = 86
post_data = {"phone": self.current_user, "password": "A123456", "countryCode": countryCode, "loginType": 2,
"userType": 1,
"subjectType": 0}
try:
m_token = requests.post(url=login_url, json=post_data, verify=False,
headers={'huohua-podenv': self.jira_id})
if m_token.status_code != 200:
raise KeyError('获取 parent token失败')
token = m_token.json()
token = token['data']['token']
student_token_cache[self.current_user] = token
return token
except :
raise TypeError('获取 parent token失败')
def get_xdu_parent_token_by_pwd(self, phone=None, host=None):
env_name = get_current_config(section='run_evn_name', key='current_business')
obj_log.info("env_name:{}".format(env_name))
login_url = 'http://'+host+"/app/sign/phone"
result = parse.urlparse(url=login_url, allow_fragments=True)
host = result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
obj_log.info("host:{},choose_user:{}".format(host, choose_user))
if host in choose_user:
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name', options=host)
post_data = {"phone": phone, "verifyCode": 1111, "countryCode": 86, "type": 1}
try:
m_token = requests.post(url=login_url, json=post_data, verify=False,
headers={'huohua-podenv': self.jira_id})
if m_token.status_code != 200:
raise KeyError('获取 parent token失败')
token = m_token.json()
token = token['data']['token']
student_token_cache[self.current_user] = token
return token
except :
raise TypeError('获取 parent token失败')
class StudentLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def get_student_token_by_pwd(self, phone=None):
env_name = get_current_config(section='run_evn_name', key='current_business')
if env_name and env_name=='hhi':
login_url = "https://hhi-core-api.qa.visparklearning.com/token"
else:
login_url = "https://core-api.qa.huohua.cn/token"
result = parse.urlparse(url=login_url, allow_fragments=True)
host = result.hostname
choose_user = ReadConfig(env_choose_path).get_options('run_user_name')
if host in choose_user:
default_user = ReadConfig(env_choose_path).get_value(
sections='run_user_name', options=host)
if phone:
self.current_user = phone
else:
phone = ReadConfig(config_file_path).get_value(
sections=default_user, options='username')
self.current_user = phone
else:
phone = ReadConfig(config_file_path).get_value(
sections='lzp', options='username')
self.current_user = phone
if student_token_cache.get(self.current_user):
return student_token_cache[self.current_user]
# 支持传入海外区号用户
if '+' in self.current_user :
phone_list =self.current_user.split("+")
countryCode = phone_list[1]
self.current_user = phone_list[0]
else:
countryCode = 86
post_data = {"phone": self.current_user, "password": "A123456", "countryCode": countryCode, "authCodeType":2,"loginType": 2}
try:
m_token = requests.post(url=login_url, json=post_data, verify=False, headers={'huohua-podenv': self.jira_id})
if m_token.status_code != 200:
raise KeyError('获取 student token失败')
token = m_token.json()
token = token['data']['token']
student_token_cache[self.current_user] = token
return token
except :
raise TypeError('获取 student token失败')
class SparkEduLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
team_name = ReadConfig(env_choose_path).get_value(sections='run_evn_name', options="current_team")
def get_spark_edu_graphic_code(self):
"""
功能:获取图形验证码
"""
code_url = "https://api.qa.sparkedu.com/third/passport/captcha"
post_data = {"width":240,"height":90}
resp = requests.post(url=code_url, json=post_data, verify=False, headers={'huohua-podenv': self.jira_id})
resp = resp.json()
return resp['data']['captchaId']
def check_spark_edu_graphic_code(self, captcha_id, captcha='1234',):
"""
功能:校验图形验证码
captcha_id图形验证码id由get_spark_edu_graphic_code返回
captcha图形验证码默认1234需事先配置好
"""
check_url = "https://api.qa.sparkedu.com/third/passport/captcha/check"
post_data = {"captchaId":captcha_id,"captcha":"1234"}
resp = requests.post(url=check_url, json=post_data, verify=False, headers={'huohua-podenv': self.jira_id})
resp = resp.json()
print(resp)
def spark_edu_login_by_code(self, phone=None, auth_code=None,
auth_code_type=17, country_code=86, language="zh-HK", platform="1",
login_type=1, user_type=1):
"""
功能:验证码登录
phone: 登录用户手机号
auth_code验证码这里默认验证码为1234需提前配置
其余参数:默认
"""
login_url = "https://api.qa.sparkedu.com/third/passport/login"
if not phone:
# 如果没有传入phone则从小组配置文件中读取
team_config = os.path.abspath(os.path.join(ROOT_PATH, 'UBJ/library/Config/team_config.ini'))
phone = ReadConfig(team_config).get_value(sections='default_spark_edu_user', options='phone')
if not auth_code:
# 如果没有传入验证码,则从小组配置文件中读取
team_config = os.path.abspath(os.path.join(ROOT_PATH, 'UBJ/library/Config/team_config.ini'))
auth_code = ReadConfig(team_config).get_value(sections='default_spark_edu_user', options='auth_code')
post_data = {"phone":phone,
"authCode":auth_code,
"countryCode":country_code,
"authCodeType":auth_code_type,
"language":language,
"platform":platform,
"loginType":login_type,
"userType":user_type}
m_token = requests.post(url=login_url, json=post_data, verify=False, headers={'huohua-podenv': self.jira_id})
if m_token.status_code == 200:
token = m_token.json()
return token['data']['token']
else:
print(m_token.content)
raise KeyError('获取 student token失败')
class SparkSaasLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.team_config = os.path.abspath(os.path.join(ROOT_PATH, 'BIZ/library/Config/team_config.ini'))
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def login_biz_saas(self, phone="", email="", password="", country_code="", auth_code="9999"):
"""
Args:
phone:
email:
password: 密码,
country_code: 86, config.country_code - 目前有问题暂时写到86
auth_code: 验证码
Returns:
"""
url = "https://sc-saas-api.qa.huohua.cn/api/session/integration/login"
team_config = os.path.abspath(os.path.join(ROOT_PATH, 'BIZ/library/Config/team_config.ini'))
a = ReadConfig(team_config).get_value(sections='saas_org_info', options='SAAS_BELONG_SCHOOL')
country_code = ReadConfig(team_config).get_value(sections='saas_org_info', options='COUNTRY_CODE')
data = {
}
if phone and password == "":
data["authCode"] = auth_code
data["loginType"] = 2 # 登录类型
data["phone"] = str(phone)
data["countryCode"] = country_code
elif phone and password:
data["password"] = password
data["loginType"] = 1 # 登录类型
data["phone"] = str(phone)
data["countryCode"] = country_code
elif email and password:
data["password"] = password
data["loginType"] = 3 # 登录类型
data["email"] = email
elif phone == "" and password == "":
phone = ReadConfig(team_config).get_value(sections='saas_org_info', options='SAAS_NAME')
data["authCode"] = auth_code
data["loginType"] = 2 # 登录类型
data["phone"] = str(phone)
data["countryCode"] = country_code
print(str(country_code),str(phone),str(auth_code))
else:
print("登录方式错误...")
return False
print("开始登录 saas 系统..., 打印登录信息: {}".format(phone))
response = requests.post(url=url, json=data, verify=False, headers={'huohua-podenv': self.jira_id, "Content-Type": "application/json;charset=UTF-8"})
if response:
pass
else:
print("登录 SaaS 系统失败...{}".format(response))
return False
print(response.text)
token = response.json()['data']['token']
status = response.status_code
if status:
if status == 200 and token:
print("登录 SaaS 系统成功..., Token: {}".format(token[0]))
print("写入到config配置文件...")
return token
else:
print("登录 SaaS 系统失败...{}".format(response))
return False
else:
print("登录 SaaS 系统失败...{}".format(response))
return False
class SparkSaasTeacherLogin(InitConfig):
def __init__(self, host=None, curt_user=None, current_evn=None):
# super().__init__(run_user_name=curt_user, current_evn=current_evn)
InitConfig.__init__(self, run_user_name=curt_user, current_evn=current_evn)
self.team_config = os.path.abspath(os.path.join(ROOT_PATH, 'BIZ/library/Config/team_config.ini'))
self.jira_id = ReadConfig(env_choose_path).get_value(sections='run_jira_id', options='huohua-podenv')
def rsa_encrypt(self, text):
"""
Args:
text:
Returns:
"""
public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGsNh8Pu1zY9SrblsJDuITHQ+ObAyjGbQARgh3TQtaz5vYQ+avTZJJsJLxiZ5hpf1/5OCb3QZRzbWebrk9cgU892GGc8MDBtH7l/I1GrZq2mY9LO4wWjA0gC7qK0o3yqAwwNyh3uXkSsN6KlpXnac1G1o+Sjlr1P1IXoB4I5MlAQIDAQAB"
public_key_str = "-----BEGIN PUBLIC KEY-----\n{}\n-----END PUBLIC KEY-----".format(public_key)
# 字符串指定编码转为bytes
text = text.encode('utf-8')
# 构建公钥对象
cipher_public = PKCS1_v1_5.new(RSA.importKey(public_key_str))
# 加密bytes
text_encrypted = cipher_public.encrypt(text)
# base64编码并转为字符串
text_encrypted_base64 = base64.b64encode(text_encrypted).decode()
return text_encrypted_base64
def spark_login_teacher(self, phone, auth_code='9999', auth_code_type=2, country_code="", pass_word="abc123", login_type="验证码"):
"""
Args:
phone:
auth_code:
auth_code_type:
country_code:
login_type: 验证码,密码
pass_word: 密码
Returns: token
"""
team_config = os.path.abspath(os.path.join(ROOT_PATH, 'BIZ/library/Config/team_config.ini'))
country_code = ReadConfig(team_config).get_value(sections='saas_org_info', options='TEACHER_COUNTRY_CODE')
data = {
"authCodeType": auth_code_type,
"countryCode": country_code
}
phone_encrypt = SparkSaasTeacherLogin().rsa_encrypt(str(phone))
data["phone"] = phone_encrypt
if login_type == "验证码":
print("教师端验证码登录")
url = "https://sc-mce-teacher-api.qa.huohua.cn/api/session"
data["authCode"] = auth_code
print(data)
else:
print("教师端密码登录")
url = "https://sc-mce-teacher-api.qa.huohua.cn/api/session/phone_pass"
pass_word_encrypt = SparkSaasTeacherLogin().rsa_encrypt(pass_word)
data["password"] = pass_word
print("开始登录 教师端 系统..., 打印登录手机号码, {}".format(phone))
# response = self.request.http_request(url, method='post', json=data)
response = requests.post(url=url, json=data, verify=False, headers={'huohua-podenv': self.jira_id, "Content-Type": "application/json;charset=UTF-8"})
if response:
pass
else:
print("登录 教师端 系统失败...{}".format(response))
return False
token = response.json()['data']['token']
status = response.status_code
if status:
if status and token:
print("登录 教师端 系统成功..., Token: {}".format(token))
print("写入到config配置文件...")
return token
else:
print("登录 教师端 系统失败...{}".format(response))
return False
else:
print("登录 教师端 系统失败...{}".format(response))
return False
if __name__ == '__main__':
test = LoginSys()
id = test.ug_sso_login_by_starlight()
print(id)
headers = {"Content-Type": "application/json;charset=UTF-8"}
kwargs = {"lessonQueryModel":{},"pageModel":{"pageNum":1,"pageSize":10}}
url = "https://starlight.qa.huohua.cn/api/lesson/queryList"
resp = id.post(url=url, json=kwargs, headers=headers, allow_redirects=False, verify=False)
print(resp)

View File

@@ -0,0 +1,405 @@
# encoding: utf-8
# 维护人员qiaoxinjiu
import re
import time
import requests
import json
import urllib3
from base_framework.public_tools.read_config import get_current_env, ReadConfig
urllib3.disable_warnings()
class HuoHuaDBS:
def __init__(self, user_name=None, pwd=None):
self.conf = ReadConfig()
# if not user_name:
# user_name = self.conf.get_value(sections="lrq", options="show_username")
# pwd = self.conf.get_value(sections="lrq", options="password")
self.req = requests.session()
self.user_name = user_name
self.user_pwd = pwd
self.headers = {}
self.session_id = None
self.authenticate_csrf_token = None
self.query_url = "https://dbs.qc.huohua.cn/query/"
self.check_url = "https://dbs.qc.huohua.cn/simplecheck/"
self.commit_url = "https://dbs.qc.huohua.cn/autoreview/"
self.execute_url = "https://dbs.qc.huohua.cn/execute/"
self.get_status_url = "https://dbs.qc.huohua.cn/sqlworkflow/detail_content/?workflow_id="
self.env = get_current_env().lower()
if self.env == "sim": # sim环境读取一次db配置, 方便后续场景中直接使用,避免重复查询
self.sim_db = {} # 最终格式: {"teach_teacher": "sim126", "teach_classes": "sim128"}
instance_list = self.conf.get_options(section="DB_LIST")
for instance in instance_list:
db_list = self.conf.get_value(sections="DB_LIST", options=instance)
dbs = db_list.split(',')
for db in dbs:
self.sim_db[db] = instance
def __get_instance_name(self, sql_content):
if self.env.lower() == "qa" or "-" in self.env: # 如果是qa或独立环境则默认查询qadb-slave
if sql_content.split(' ')[0].lower() == "select":
instance_name = "qadb-slave" # sql查询时使用此名
else:
instance_name = "qadb-master" # sql执行时使用此名
else:
# db_name = sql_content.split(".")[0].split(" ")[-1].replace("`", "").replace(" ", "")
db_names = self.__get_db_names_from_sql(sql_statements=sql_content)
instance_list = []
no_config_list = []
for db_name in db_names:
if db_name not in self.sim_db:
no_config_list.append(db_name)
continue
if self.sim_db[db_name] not in instance_list:
instance_list.append(self.sim_db[db_name])
if len(no_config_list) > 0:
raise Exception("你本次查询的数据库:{} 未做sim配置请添加....".format(no_config_list))
if len(set(instance_list)) > 1:
raise Exception("你本次查询的数据库:{}\n位于不同的实例:{}\n请修改sql....".format(db_names,instance_list))
instance_name = instance_list[0]
return instance_name
@staticmethod
def __check_sql_content(sql_content):
"""检查sql语句是否符合规范"""
sql_list = sql_content.split(" ")
if sql_list[0].lower() in ["insert", "update", "delete", "select"]:
if "." not in sql_content:
raise Exception("sql语句必现是db_name.table_name格式请修正:{}".format(sql_content))
@staticmethod
def __get_db_names_from_sql(sql_statements):
"""
提取sql语句中的数据库名称
:param sql_statements: sql语句
:return: 数据库名称列表,去重
"""
sql_statements = sql_statements.replace("`", "")
pattern = re.compile(r'\b(?:FROM|INTO|UPDATE|JOIN)\s+([a-zA-Z0-9_]+)\.', re.IGNORECASE)
databases = set()
# 移除注释和多余空格
sql_clean = re.sub(r'--.*', '', sql_statements) # 移除行注释
sql_clean = ' '.join(sql_clean.split()) # 合并多余空格
# 查找所有匹配项
matches = pattern.findall(sql_clean)
if matches:
databases.update(matches)
return list(databases)
def dbs_login(self):
login_index = 'http://dbs.qc.huohua.cn/login/'
res_login = self.req.get(login_index)
self.authenticate_csrf_token = res_login.cookies.get('csrftoken')
authenticate_url = 'https://dbs.qc.huohua.cn/authenticate/'
params_json_str = {"username": self.user_name, "password": self.user_pwd}
self.headers.update({"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"Cookie": "experimentation_subject_id=IjdmMmE5MmYxLTk4N2QtNDU1YS1hMGRjLTZhZWIwZWY3YWEyMiI%3D--fc2a7fdd42c874af494e99f3f5fdf0c372d3b4d2; __UUID=93b21b91-675f-4019-fe4f-4442ec9f6e65-x; __DEVICE_ID=93b21b91-675f-4019-fe4f-4442ec9f6e65-x; csrftoken={}".format(
self.authenticate_csrf_token)})
self.headers.update({"X-CSRFToken": "{}".format(self.authenticate_csrf_token), "X-Requested-With": "XMLHttpRequest"})
resp = self.req.post(authenticate_url, params_json_str, headers=self.headers)
# print(json.loads(resp.text).get("msg"))
self.session_id = resp.cookies.get('sessionid')
self.headers.update({
"Cookie": "experimentation_subject_id=IjdmMmE5MmYxLTk4N2QtNDU1YS1hMGRjLTZhZWIwZWY3YWEyMiI%3D--fc2a7fdd42c874af494e99f3f5fdf0c372d3b4d2; __UUID=93b21b91-675f-4019-fe4f-4442ec9f6e65-x; __DEVICE_ID=93b21b91-675f-4019-fe4f-4442ec9f6e65-x; csrftoken={};sessionid={}".format(
self.authenticate_csrf_token, self.session_id)})
# print(self.session_id)
def dbs_query(self, instance_name, sql_content, limit_num="100"):
"""
dbs查询
| 请求参数名 | 说明 | 类型 | 是否必填 |
| instance_name | 数据库实例: 例如sim-my-allschool | string | True |
| db_name | 数据库名称: hulk_class_help | string | True |
| schema_name | 数据库模式: 默认空不填 | string | True |
| tb_name | 表名 | string | True |
| sql_content | 查询sql | string | True |
| limit_num | 查询行数 | string | True |
"""
if not self.session_id:
self.dbs_login()
db_name = sql_content.split(".")[0].split(" ")[-1].replace("`", "").replace(" ", "")
data = {"instance_name": instance_name, "db_name": db_name, "schema_name": "", "tb_name": "",
"sql_content": sql_content, "limit_num": limit_num}
self.headers.update({"Referer": "https://dbs.qc.huohua.cn/sqlquery/"})
resp = self.req.post(self.query_url, data, headers=self.headers)
result = json.loads(resp.text)
return result.get('data').get('rows')
def dbs_query_as_json(self, instance_name, sql_content, limit_num="1000"):
"""
通过DBS查询数据并返回json格式
Args:
instance_name: 数据库实例: 例如sim-my-allschool
sql_content: 查询语句
limit_num: 查询行数
Returns:
[{key:value}]
"""
if not self.session_id:
self.dbs_login()
self.headers.update({"Referer": "https://dbs.qc.huohua.cn/sqlquery/"})
# db_name = sql_content.lower().split("from ")[1].split(".")[0].replace("`", "").replace(" ", "")
db_name = self.__get_db_names_from_sql(sql_statements=sql_content)[0]
data = {"instance_name": instance_name, "db_name": db_name, "schema_name": "", "tb_name": "",
"sql_content": sql_content, "limit_num": limit_num, "workflow_name": "ODS线上数据监控"}
resp = self.req.post(self.query_url, data, headers=self.headers)
result = json.loads(resp.text)
if result.get('status') != 0:
raise Exception(result)
else:
r_data = self.__make_format_data(key_list=result.get('data').get('column_list'),
column_list=result.get('data').get('rows'))
return r_data
def dbs_query_as_json_auto_identify_env(self, sql_content, instance_name=None, limit_num="1000"):
"""
通过DBS查询数据并返回json格式【建议别链表查询因为sim环境不支持除非你的函数不在sim使用】
Args:
sql_content: 查询语句
instance_name: 数据库实例: env_choose中是qa或独立环境时此参数失效否则已输入为准不传则默认为sim126
limit_num: 查询行数
Returns:
Note可以根据你的查询表直接指定sim环境的instance_name因为qa环境时不使用此参数
"""
self.__check_sql_content(sql_content) # sql格式检查
if not instance_name:
instance_name = self.__get_instance_name(sql_content=sql_content) # 自动识别环境
if not self.session_id:
self.dbs_login()
self.headers.update({"Referer": "https://dbs.qc.huohua.cn/sqlquery/"})
# db_name = sql_content.split(".")[0].split(" ")[-1].replace("`", "").replace(" ", "")
db_name = sql_content.lower().split("from ")[1].split(".")[0].replace("`", "").replace(" ", "")
data = {"instance_name": instance_name, "db_name": db_name, "schema_name": "", "tb_name": "",
"sql_content": sql_content, "limit_num": limit_num, "workflow_name": "Test_online_data_check"}
resp = self.req.post(self.query_url, data, headers=self.headers)
result = json.loads(resp.text)
if result.get('status') != 0:
raise Exception(result)
else:
r_data = self.__make_format_data(key_list=result.get('data').get('column_list'),
column_list=result.get('data').get('rows'))
return r_data
def dbs_query_all_data_as_json(self, sql_content, instance_name=None, sort_key='id', sort_type='asc'):
"""
通过DBS查询db中的所有数据(超过1000条也行)并返回json格式别带limit关键字带了就走普通查询
Args:
sql_content: 查询语句
instance_name: 数据库实例: env_choose中是qa或独立环境时此参数失效否则已输入为准不传则默认为sim126
sort_key: 排序字段默认为id
sort_type: 排序类型默认为asc-顺排desc-倒排
"""
# 如果查询语句带有limit限制则直接走普通查询
if " limit " in sql_content.lower():
return self.dbs_query_as_json_auto_identify_env(sql_content=sql_content, instance_name=instance_name)
# 否则走全量查询
if " join " in sql_content.lower() or " union " in sql_content.lower() or sql_content.lower().count("from") > 1:
raise Exception("链表查询不支持全量查询,请修改查询语句")
if "where" not in sql_content.lower():
raise Exception("全量查询必须带有where条件防止数量过多请修改查询语句")
# 全量查询
all_data = []
off_set = 0
str_list = sql_content.lower().split("where")
while True:
if sort_type.lower() == 'asc':
sql_str = ("{} where {}>{} and {}"
.format(str_list[0], sort_key, off_set, str_list[1]))
elif sort_type.lower() == 'desc':
sql_str = ("{} where {}<{} and {}"
.format(str_list[0], sort_key, off_set, str_list[1]))
db_data = self.dbs_query_as_json_auto_identify_env(instance_name='prod-my-teach_classes-slave01',
sql_content=sql_str)
all_data += db_data
if len(db_data) < 1000:
break
else:
off_set = db_data[-1][sort_key]
return all_data
def dbs_query_as_list(self, instance_name, sql_content, limit_num="100"):
"""
通过DBS查询数据并返回list格式
Args:
instance_name: 数据库实例: 例如sim-my-allschool
sql_content: 查询语句
limit_num: 查询行数
Returns:
[{key:value}]
"""
res = self.dbs_query_as_json(instance_name=instance_name, sql_content=sql_content, limit_num=limit_num)
out_data = []
if len(res) > 0:
for item in res:
tmp_data = []
for key in item:
if len(item) > 1: # 查询结课是多列,则返回二维数组
tmp_data.append(item[key])
else: # 查询结课是单列,则返回一维数组
tmp_data = item[key]
out_data.append(tmp_data)
return out_data
@staticmethod
def __make_format_data(key_list, column_list):
"""
组装json格式的数据类型
Args:
key_list: 列名,如['a','b']
column_list: 数据,如[[1,2],[3,4]]
Returns:
[{key:value}],如[['a':1,'b':2],['a':3,'b':4]]
"""
obj_data = []
for column in column_list:
col_data = {}
for index in range(len(key_list)):
col_data[key_list[index]] = column[index]
obj_data.append(col_data)
return obj_data
def dbs_execute(self, instance_name, sql_content, time_out=60):
"""
SIM环境执行insert、update、del数据库操作
Args:
instance_name: 选择实例例如sim126
sql_content: sql语句
time_out: 超时时间默认60秒
"""
self.__check_sql_content(sql_content) # sql格式检查
sql_info = sql_content.split(".")[0].split(" ")
db_name = sql_info[-1].replace("`", "").replace(" ", "")
if not self.session_id:
self.dbs_login()
try:
# 检查SQL
req_params = {"sql_content": sql_content, "instance_name": instance_name, "db_name": db_name,
"sql_type": "online"}
resp_check = self.req.post(self.check_url, req_params, headers=self.headers)
if resp_check.status_code == 200:
result_check = json.loads(resp_check.text)
if result_check.get("data").get("CheckErrorCount") != 0:
raise Exception("sql检查未通过请检查调整后再提交msg {}".format(result_check))
else:
raise Exception("dbs执行检查sql失败错误状态码 {} res{}".format(resp_check.status_code, resp_check.text))
# 提交SQL
req_params = {"csrfmiddlewaretoken": self.authenticate_csrf_token, "sql_content": sql_content,
"instance_name": instance_name, "db_name": db_name,
"sql_type": "online", "sql-upload": "", "workflow_id": "", "workflow_name": "test",
"demand_url": "",
"group_name": "测试组", "run_date_start": "", "run_date_end": "", "workflow_auditors": 1}
resp_commit = self.req.post(self.commit_url, req_params, headers=self.headers)
if resp_commit.status_code == 200:
workflow_id = resp_commit.history[0].headers.get("Location").split("/")[2]
else:
raise Exception("dbs执行提交sql失败错误状态码 {} res{}".format(resp_commit.status_code, resp_commit.text))
# 执行sql
req_params = {"csrfmiddlewaretoken": self.authenticate_csrf_token, "workflow_id": workflow_id,
"mode": "auto"}
resp_execute = self.req.post(self.execute_url, req_params, headers=self.headers)
if resp_execute.status_code == 200:
if not resp_execute.ok:
raise Exception("sql执行未通过请检查调整后再提交msg {}".format(resp_execute.reason))
else:
raise Exception("dbs执行sql失败错误状态码 {} res{}".format(resp_execute.status_code, resp_execute.text))
# 查询执行状态
time_out = int(time_out)
while time_out:
time_out -= 1
resp_get_status = self.req.get(self.get_status_url + str(workflow_id), headers=self.headers)
if resp_get_status.status_code == 200:
result_status = json.loads(resp_get_status.text)
if "Execute Successfully" in result_status.get("rows")[1].get("stagestatus"):
return True
else:
time.sleep(1)
else:
time.sleep(1)
raise Exception("超时未查询到结果请确认sql是不是需要执行很久若有必要请调整超时参数")
except Exception as e:
raise Exception(e)
def dbs_query_by_db_name(self, instance_name, db_name, sql_content, limit_num="100"):
"""
dbs查询
| 请求参数名 | 说明 | 类型 | 是否必填 |
| instance_name | 数据库实例: 例如sim-my-allschool | string | True |
| db_name | 数据库名称: hulk_class_help | string | True |
| schema_name | 数据库模式: 默认空不填 | string | True |
| tb_name | 表名 | string | True |
| sql_content | 查询sql | string | True |
| limit_num | 查询行数 | string | True |
"""
if not self.session_id:
self.dbs_login()
data = {"instance_name": instance_name, "db_name": db_name, "schema_name": "", "tb_name": "",
"sql_content": sql_content, "limit_num": limit_num}
self.headers.update({"Referer": "https://dbs.qc.huohua.cn/sqlquery/"})
resp = self.req.post(self.query_url, data, headers=self.headers)
result = json.loads(resp.text)
return result.get('data').get('rows')
def dbs_select(self, sql_content, r_type='json', instance_name=None):
"""
功能通过dbs查询并返回全部数据
Arg
sql: 查询语句
r_type: 返回类型json或list
instance_name: 数据库实例名不传则根据env_choose文件中的环境配置来获取
Note:
1. 若想要返回一条数据自行在sql语句中添加limit 1限制
2. 用于线上环境查询时指定instance_name参数即可
"""
self.__check_sql_content(sql_content) # sql格式检查
if not instance_name:
instance_name = self.__get_instance_name(sql_content=sql_content) # 获取数据库实例名
if r_type == 'json':
db_data = self.dbs_query_as_json(sql_content=sql_content, instance_name=instance_name)
elif r_type == 'list':
db_data = self.dbs_query_as_list(instance_name=instance_name, sql_content=sql_content)
else:
raise Exception("返回类型只能是json或list")
# if 'limit 1;' in sql_content:
# if len(db_data) == 0:
# return []
# return db_data[0]
# else:
# return db_data
return db_data
def dbs_execute_sql(self, sql_content, instance_name=None, time_out=60):
"""
通过DBS执行insert、update、del操作支持concat拼接sql
Args:
sql_content: 待执行语句
instance_name: 数据库实例: env_choose中是qa或独立环境时此参数失效否则已输入为准不传则默认为sim126
time_out: 超时时间默认60秒
Returns:
Note可以根据你的查询表直接指定sim环境的instance_name因为qa环境时不使用此参数
"""
if not instance_name:
instance_name = self.__get_instance_name(sql_content=sql_content) # 自动识别环境
if "concat" in sql_content.lower(): # 如果是拼接sql则拆分后执行
sql_list = self.dbs_query_as_json_auto_identify_env(sql_content=sql_content)
for sql in sql_list:
for key in sql:
self.dbs_execute_sql(sql_content=sql[key])
else:
self.dbs_execute(instance_name=instance_name, sql_content=sql_content, time_out=time_out)
if __name__ == '__main__':
hh_dbs = HuoHuaDBS()
# hh_dbs.dbs_query_as_json("")
sql = "SELECT ui.user_id,sp.id AS student_id,ui.user_name,ui.logo,ui.phone,ui.phone_code,ui.nick_name,ui.english_nickname,ui.share_code,ui.invite_code,ui.customer_id,ui.channel_id,ui.from_user_id,ui.email,ss.business_line FROM (SELECT up.id as user_id,up.user_name,up.logo,up.phone,up.phone_code,up.nick_name,up.english_nickname,up.share_code,up.invite_code,up.customer_id,up.channel_id,up.from_user_id,uc.contact_info as email FROM ucenter.user_profile up LEFT JOIN ucenter.user_contact uc ON up.id=uc.user_id WHERE up.phone_code='653666709542760459') ui LEFT JOIN peppa_channel.statistics_setting ss ON ui.channel_id = ss.channel_id LEFT JOIN ucenter.student_profile sp ON sp.user_id=ui.user_id;"
print(hh_dbs.get_instance_name(sql_content=sql))

View File

@@ -0,0 +1,91 @@
import logging
import time
from os import path
from base_framework.base_config.current_pth import *
from concurrent_log_handler import ConcurrentRotatingFileHandler
import platform
time_flag = time.time()
d = path.dirname(__file__)
parent_path = os.path.dirname(d)
parent_path = os.path.dirname(parent_path)
# obj_tool = Tools()
if platform.system() == 'Darwin':
LOG_FILENAME = log_path + '/' + 'run.log'
else:
# Windows路径
LOG_FILENAME = log_path + '\\' + 'run.log'
if not os.path.exists(log_path):
try:
os.mkdir(log_path)
except Exception as e:
print('日志文件夹创建失败:{}'.format(e))
# MAC 路径
# LOG_FILENAME = log_path + '/' + 'run.log'
# LOG_FILENAME = (os.path.split(os.path.realpath(__file__)))[0] + '\\' + 'log\\run.log'
# Color escape string
COLOR_RED = '\033[1;31m'
COLOR_GREEN = '\033[1;32m'
COLOR_YELLOW = '\033[1;33m'
COLOR_BLUE = '\033[1;34m'
COLOR_PURPLE = '\033[1;35m'
COLOR_CYAN = '\033[1;36m'
COLOR_GRAY = '\033[1;37m'
COLOR_WHITE = '\033[1;38m'
COLOR_RESET = '\033[1;0m'
# Define log color
LOG_COLORS = {
'DEBUG': COLOR_BLUE + '%s' + COLOR_RESET,
'INFO': COLOR_GREEN + '%s' + COLOR_RESET,
'WARNING': COLOR_YELLOW + '%s' + COLOR_RESET,
'ERROR': COLOR_RED + '%s' + COLOR_RESET,
'CRITICAL': COLOR_RED + '%s' + COLOR_RESET,
'EXCEPTION': COLOR_RED + '%s' + COLOR_RESET,
}
class log_colour(logging.Formatter):
def __init__(self, fmt=None, datefmt=None):
logging.Formatter.__init__(self, fmt, datefmt)
def format(self, record):
level_name = record.levelname
msg = logging.Formatter.format(self, record)
return LOG_COLORS.get(level_name, '%s') % msg
def get_logger():
logger = logging.Logger('Automation')
logger.setLevel(0)
terminal = logging.StreamHandler()
terminal.setLevel(logging.DEBUG)
# log_file = logging.handlers.TimedRotatingFileHandler(filename=LOG_FILENAME, when="S", backupCount=3,
# encoding='utf-8', interval=5)
log_file = ConcurrentRotatingFileHandler(
LOG_FILENAME, maxBytes=5 * 1024 * 1024, backupCount=4, encoding="utf-8")
log_file.setLevel(logging.INFO)
formatter_ter = log_colour(
'%(asctime)s [tid:%(thread)d] %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
formatter_log = logging.Formatter(
'%(asctime)s [tid:%(thread)d pid:%(process)d] %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
terminal.setFormatter(formatter_ter)
log_file.setFormatter(formatter_log)
logger.addHandler(terminal)
logger.addHandler(log_file)
return logger
def set_log_file(logfilename):
global LOG_FILENAME
LOG_FILENAME = logfilename

View File

@@ -0,0 +1,98 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2021/11/22 10:41
"""
import time
from base_framework.public_tools.my_faker import MyFaker
from base_framework.public_tools.pymailtm.pymailtm import MailTm
from retrying import retry
from base_framework.public_tools import log
from base_framework.public_tools.runner import Runner
magic_faker = MyFaker()
class MailHelper:
"""
邮件相关操作
"""
new_mt_mail_obj = None
def __init__(self):
self.mail_account = MailTm()
self.obj_runner = Runner()
self.obj_log = log.get_logger()
@retry(stop_max_attempt_number=5, wait_fixed=5000)
def get_ci_new_mail(self):
"""
| 功能说明: | 生成随机邮件地址 |
| 返回参数: | dic | 邮件地址 |
| 作者信息: | 作者 林于棚 | 修改时间 |
举例说明:
| get_ci_new_mail() |
"""
faker_mail = magic_faker.gen_str(min_chars=8)
faker_num = magic_faker.create_num(start=1, end=10000)
return faker_mail.lower()+str(faker_num)+'@citest.com'
@retry(stop_max_attempt_number=5, wait_fixed=5000)
def get_new_mail_mt(self):
"""
| 功能说明: | https://mail.tm/zh/ 生成随机邮件地址 |
| 返回参数: | dic | 邮件地址 |
| 作者信息: | 作者 林于棚 | 修改时间 |
举例说明:
| get_new_email() |
"""
self.new_mt_mail_obj = self.mail_account.get_account(password='666666')
return self.new_mt_mail_obj.address
# obj_log.info('生成的邮件地址:{}'.format(self.new_mt_mail_obj.address))
# @retry(stop_max_attempt_number=5, wait_fixed=5000)
def get_mt_mail_message(self, address=None, password='666666'):
"""
| 功能说明: | 获取 https://mail.tm/zh/ 生成邮件地址的信息 |
| 输入参数: | mail_obj | self.new_mt_mail_obj |
| 返回参数: | dic | 邮件地址 |
| 作者信息: | 作者 林于棚 | 修改时间 |
举例说明:
| get_mt_mail_message() |
"""
mail_message = []
first_flag = 0
self.get_new_mail_mt()
account = self.new_mt_mail_obj
if account.address != address and address:
account.address = address
account.password = password
account.jwt = MailTm._make_account_request("token", address, password)
account.auth_headers = {
"accept": "application/ld+json",
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(account.jwt["token"])
}
self.obj_log.info('生成的邮件地址:{}'.format(account.address))
self.obj_log.info('收取邮件中.............')
for i in range(2):
first_len = len(mail_message)
mail_message = account.get_messages()
last_len = len(mail_message)
if first_len <= last_len != 0:
continue
elif last_len == 0 and first_flag < 1:
first_flag += 1
self.obj_log.info('没有收到邮件,重试中..............')
time.sleep(4)
else:
self.obj_log.info('没有收任何到邮件!')
break
return mail_message
if __name__ == '__main__':
obj_mail = MailHelper()
print(obj_mail.get_ci_new_mail())
# print(obj_mail.get_mt_mail_message())

View File

@@ -0,0 +1,231 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/10/15 17:35
"""
import time
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.my_faker import MyFaker
from base_framework.public_tools.runner import Runner
import re
obj_log = get_logger()
obj_faker = MyFaker()
obj_runner = Runner()
class ManageKeyWord:
def __init__(self):
pass
@staticmethod
def kw_get_my_permissions(app_type, employee_id, **kwargs):
"""
| 功能说明: | 获取权限 |
| 输入参数: | phone | 新增线索的手机号 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
| 存放位置: | | |
举例说明:
| kw_get_my_permissions | |
"""
options_dict = dict(**kwargs)
change_user = options_dict.get('user', None)
api_url = obj_runner.manage_host + '/permission/getMyPermissions'
req_type = 'POST'
post_data = {
"appType": app_type,
"employeeId": employee_id}
resp = obj_runner.call_rest_api(
user=change_user,
API_URL=api_url,
req_type=req_type,
params=post_data)
return resp
@staticmethod
def kw_get_sms_code(phone=None, **kwargs):
"""
| 功能说明: | 获取短信验证码 |
| 输入参数: | phone | 手机号码 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
| 存放位置: | | |
举例说明:
| kw_get_sms_code | |
"""
options_dict = dict(**kwargs)
change_user = options_dict.get('user', None)
current_evn = options_dict.get('current_evn', None)
url = "https://smm.qa.huohua.cn/sms/log/page/all?WHERE.%5BEQ%5Dphone={}&WHERE.%5BEQ%5DtypeName=&WHERE.%5BEQ%5Dchannel=&WHERE.%5BEQ%5DgatewayType=&WHERE.%5BGT%5DcreateTime=&WHERE.%5BLT%5DcreateTime=&WHERE.%5BGT%5DsubmitTime=&WHERE.%5BLT%5DsubmitTime=&pageNum=1&pageSize=20&orderBy=id%20desc".format(
phone)
req_type = 'POST'
resp = obj_runner.call_rest_api(
user=change_user,
API_URL=url,
req_type=req_type,
current_evn=current_evn)
smm_code = resp['list'][0]['msg']
reg = re.compile(r"(?<=验证码:)\d+")
match = reg.search(smm_code)
smm_code = match.group(0)
return smm_code
@staticmethod
def kw_get_authcode_without_message(phone,type=2,**kwargs):
"""
| 功能说明: | 直接获取短信验证码,不会发送短信 |
| 输入参数: | phone | 手机号码 |
| | type | 获取类型(2、磐石) |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
| 存放位置: | | |
举例说明:
| kw_get_authcode_without_message | phone=XXX type=XXX |
"""
if "country_code" in kwargs.keys():
if "-" not in phone and not str(phone).startswith("0") and int(kwargs.get("country_code")) != 86:
phone="{}-{}".format(kwargs.get("country_code"),phone)
url = "{}/user_profile/sendLoginAuthCodeWithoutMessage?phone={}&type={}".format(obj_runner.manage_host, phone, type)
obj_log.info("your input:{0}".format(phone))
resp = obj_runner.call_rest_api(API_URL=url, req_type="POST")
try:
auth_code = resp['data'][-4::1]
obj_log.info(auth_code)
except:
raise Exception("未获取到验证码,获取接口返回:%s"%resp)
return auth_code
@staticmethod
def kw_execute_xxl_job(job_id, para="", **kwargs):
"""
| 功能说明: | 执行xxljob定时任务 |
| 输入参数: | job_id | job的任务id |
| | para=none | job运行时的参数 |
| 返回参数: | 无 |
| 作者信息: | 林于棚 | 2020.12.14 |
| 函数位置: | Public/Common/mg_keyword.py | |
举例说明:
| execute_xxl_job | 2109 |
"""
startTime_stamp = time.localtime()
# startTime = time.strftime("%Y-%m-%d %H:%M:%S", startTime_stamp)
endTime = time.strftime("%Y-%m-%d 23:59:59", startTime_stamp)
jobGroup = None
if "jobGroup" in kwargs:
jobGroup = kwargs.pop("jobGroup")
post_data = {
"id": job_id,
'executorParam': para}
api_url = obj_runner.xxl_job_host + "/jobinfo/trigger"
# obj_runner.call_rest_api(API_URL=api_url, req_type="POST", data=post_data, user="ci009")
startTime = time.strftime("%Y-%m-%d %H:%M:%S", startTime_stamp)
time.sleep(1)
tragger_resp = obj_runner.call_rest_api(API_URL=api_url, req_type="POST", data=post_data)
if tragger_resp.get("code") != 200:
raise EnvironmentError("%s job 触发失败!" % job_id)
time.sleep(3)
if jobGroup:
data_query = {"jobGroup": jobGroup, "jobId": job_id, "logStatus": -1,
"filterTime": "%s - %s" % (startTime, endTime), "start": 0, "length": 10}
url = obj_runner.xxl_job_host + "/joblog/pageList"
i = 0
while i < 300:
resp = obj_runner.call_rest_api(API_URL=url, params=data_query, req_type="POST")
if 'data' in resp and len(resp['data']) > 0:
if resp['data'][0].get("handleCode") == 200:
obj_log.info(resp['data'][0])
obj_log.info("job%s执行成功" % job_id)
break
time.sleep(5)
i += 1
else:
raise EnvironmentError("%s job 执行失败!" % job_id)
@staticmethod
def kw_modify_apollo_configuration():
"""
| 功能说明: | 修改apollo配置 |
| 输入参数: | phone | 手机号码 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
| 存放位置: | | |
举例说明:
| execute_xxl_job | |
"""
post_data = {
"id": 28959,
"key": "sms.start.time",
"value": "9:50",
"comment": "",
"dataChangeCreatedBy": "apollo",
"tableViewOperType": "update"}
api_url = "http://apollo.qa.huohua.cn/apps/peppa-cc-manage/envs/QA/clusters/default/namespaces/application/item"
obj_runner.call_rest_api(API_URL=api_url, req_type="PUT", json=post_data)
@staticmethod
def course_upload(**kwargs):
"""
| 功能说明: | 上传图片 |
| 输入参数: | phone | 手机号码 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
| 存放位置: | | |
举例说明:
| execute_xxl_job | |
"""
options_dict = dict(**kwargs)
change_user = options_dict.get('user', None)
file = r"C:\Users\HuoHua\Pictures\桌面图片\download.jpg"
api_url = obj_runner.teach_host + "/peppa-teach-api/common/upload"
files = {
"type": (None, "lesson"),
"file": ("file", open(file, "rb"), "image/jpeg"),
}
# headers = {
# "Content-Type": "multipart/form-data" # 上传文件不要设置type会报错
# }
response = obj_runner.call_rest_api(
user=change_user,
req_type="POST",
API_URL=api_url,
files=files
)
return response
@staticmethod
def sku_classfiy_get(**kwargs):
"""
| 功能说明: | 获取SKU分类枚举值 |
| 输入参数: | 无 | 无 |
| 返回参数: | XXX |
| 作者信息: | 作者 | 修改时间 |
举例说明:
| sku_classfiy_get | |
"""
options_dict = dict(**kwargs)
change_user = options_dict.get('user', None)
api_url = obj_runner.scm_host + '/smart/expressSku/skuClassifyEnum'
req_type = 'GET'
resp = obj_runner.call_rest_api(
user=change_user,
API_URL=api_url,
req_type=req_type)
return resp
if __name__ == '__main__':
url = "https://teach-opt-api.qa.huohua.cn/api/quality-manage/template/items/385/1/"
res = obj_runner.call_rest_api(url, "get")
a = ManageKeyWord()
# [a.kw_add_case(phone) for phone in ['1878201' +
# str(random.randrange(1111, 9999, 2)) for _ in range(2)]]
# a.kw_get_my_permissions('crmnew', 586470)
# a.kw_get_sms_code('18782019436')
# a.course_upload(user='xl')
a.execute_xxl_job(2865, "[262]")
# a.sku_classfiy_get()

View File

@@ -0,0 +1,175 @@
# -*- coding:utf-8 -*-
import pymongo
from base_framework.public_tools.db_dbutils_init import get_my_mongo_connection
from base_framework.public_tools import log
obj_log = log.get_logger()
class MongoDbHelper:
def __init__(self):
# """
# 初始化mongodb、并且链接指定数据库
# :param collection:
# :param collect_db_name:
# """
# init = InitConfig()
# self.host = init.MONGO_HOST
# self.port = init.MONGO_PORT
# self.user = init.MONGO_USER
# self.pwd = init.MONGO_PASSWORD
#
# try:
# self.connect_ = pymongo.MongoClient(host=self.host,
# port=int(self.port),
# username=self.user,
# password=self.pwd,
# authSource="hulk_teach_marketing"
# )
# except Exception as e:
# obj_log.error("mongdb连接失败{}".format(e))
self.conn = get_my_mongo_connection().mongo_connect()
def mongo_find(self, db_name, collection_name, query={}, select_value=None):
db = self.conn[db_name]
data = db.get_collection(collection_name).find(query, select_value)
obj_log.info("mongo查询语句db.getCollection({}).find({},{}).limit(501)".format(collection_name, query, select_value))
return data
def mongo_select_all(self, db_name, collection_name, query={}, select_value=None):
"""
| 功能说明: | 查询mongo数据 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | query={} | 查询条件 |
| | column={} | 展示列:默认会展示 "_id" |
| 返回参数: | 查询结果(tuple) |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
data = self.mongo_find(db_name, collection_name, query, select_value)
d = []
for i in data:
d.append(i)
return tuple(d)
def mongo_insert_many(self, db_name, collection_name, insert_value):
"""
| 功能说明: | 插入mongo数据 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | insert_value | 插入值 |
| 返回参数: | TRUE/FALSE |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
try:
db = self.conn[db_name]
db.get_collection(collection_name).insert_many(insert_value)
obj_log.info("mongo插入语句db.getCollection({}).insertMany({})".format(collection_name, insert_value))
except Exception as e:
obj_log.error("插入数据失败:{}".format(e))
return False
return True
def mongo_update_many(self, db_name, collection_name, query, update_value):
"""
| 功能说明: | 修改mongo数据 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | query | 修改条件 |
| | update_value | 修改值 |
| 返回参数: | TRUE/FALSE |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
try:
db = self.conn[db_name]
result = db.get_collection(collection_name).update_many(query, update_value)
obj_log.info("mongo修改语句db.getCollection({}).updateMany({})".format(collection_name, update_value))
obj_log.info("修改数量:{}".format(result.modified_count))
except Exception as e:
obj_log.error("修改失败:{}".format(e))
return False
return True
def mongo_delete_many(self, db_name, collection_name, delete_value):
"""
| 功能说明: | 删除mongo数据 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | delete_value | 删除值 |
| 返回参数: | TRUE/FALSE |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
try:
db = self.conn[db_name]
result = db.get_collection(collection_name).delete_many(delete_value)
obj_log.info("mongo删除语句db.getCollection({}).deleteMany({})".format(collection_name, delete_value))
obj_log.info("删除数量:{}".format(result.deleted_count))
except Exception as e:
obj_log.error("删除失败:{}".format(e))
return False
return True
def mongo_count(self, db_name, collection_name, filter, session=None, **kwargs):
"""
| 功能说明: | 查询mongo数据 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | filter | 查询条件 |
| 返回参数: | 统计结果 |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
db = self.conn[db_name]
db_collection = db[collection_name]
db_count = db_collection.count_documents(filter, session, **kwargs)
return db_count
def mongo_aggregate(self, db_name, collection_name, pipline, session=None, **kwargs):
"""
| 功能说明: | (aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果 |
| 输入参数: | db_name | 数据库 |
| | collection_name | 表(集合) |
| | pipline | a list of aggregation pipeline stages |
| 返回参数: | 返回聚合结果 |
| 作者信息: | xl | 2020/11/26 21:11 |
| 函数位置: | public_tool/mongohelper.py ||
"""
db = self.conn[db_name]
db_collection = db[collection_name]
data = db_collection.aggregate(pipline, session, **kwargs)
s = []
for i in data:
s.append(i)
return s
if __name__ == '__main__':
s = MongoDbHelper()
# 查询
select_value = {"name": {"$regex": "^测试"}, "status":0}
ss = s.mongo_count(db_name='hulk_teach_marketing', collection_name='activity', filter=select_value)
print(ss)
# print("{}\n".format(ss))
# for i in ss:
# print(i)
# 插入数据
# dd = [{"code": "ACT2079479569078477", "created_time":1637897233410, "creator_id":586470, "creator_name":"xiaoliang02", "creator_type":1, "delete_flag":0, "deleted_time":0,"language":0,"name":"自动插入活动1-page-1001","region":"1","remark":"自动添加活动备注信息","status":1,"type":0,"user_type":0},
# {"code": "ACT2079479569078476", "created_time":1637897233410, "creator_id":586470, "creator_name":"xiaoliang02", "creator_type":1, "delete_flag":0, "deleted_time":0,"language":0,"name":"自动插入活动2-page-1001","region":"1","remark":"自动添加活动备注信息","status":2,"type":0,"user_type":0}]
# s.mongo_insert_many(db_name='hulk_teach_marketing', collection_name='activity', insert_value=dd)
# # 修改数据
# q = {"code": "ACT210477219578032135"}
# y = {"$set": {"name": "切图信息-右下角B区"}}
# dd = s.mongo_update_many(db_name='hulk_teach_marketing', collection_name='activity', query=q, update_value=y)
# # 删除数据
# ddd = {"name": {"$regex": "^自动插入"}}
# s.mongo_delete_many(db_name='hulk_teach_marketing', collection_name='activity', delete_value=ddd)
# pipline = [{"$match": {"region": "5", "delete_flag": 0}}, {"$group": {"_id": "$region", "max_order": {"$max": "$order"}}}]
# p = s.mongo_aggregate('hulk_teach_marketing', 'activity', pipline)

View File

@@ -0,0 +1,105 @@
from faker import Faker
import random
'''
简体中文zh_CN
繁体中文zh_TW
美国英文en_US
英国英文en_GB
德文de_DE
日文ja_JP
韩文ko_KR
法文fr_FR'''
class MyFaker:
def __init__(self):
# 选择中文语言
self.fake = Faker("zh_CN")
def gen_name(self):
return self.fake.name()
def gen_address(self):
return self.fake.address()
def gen_phone_number(self, num=1):
return [self.fake.phone_number() for _ in range(num)]
# 随机身份证
def gen_identity_number(self):
return self.fake.ssn()
# 信用卡号
def credit_card_number(self):
return self.fake.credit_card_number()
# MD5
def gen_md5(self):
return self.fake.md5()
# sku_name
def gen_word(self):
return self.fake.word()
# password
def gen_password(self):
return self.fake.password()
# 随机文本
def gen_txt(self, max_nb_chars=15):
return self.fake.text(max_nb_chars=max_nb_chars)
def gen_str(self, min_chars=1, max_chars=10):
"""
范围长度内随机字符串
:param min_chars:
:param max_chars:
:return:
"""
return self.fake.pystr(min_chars=min_chars, max_chars=max_chars)
def gen_letter(self, length=1):
"""
生成范围长度内随机字母a-z
:param length: 生成的长度
:return: str
"""
return_str = ''
for _ in range(length):
return_str = return_str + self.fake.random_letter()
return return_str.lower()
def create_num(self, start, end):
"""
随机生成一个数字
:param:起始范围
:return:int
"""
return random.randint(start, end)
def gen_email(self):
"""
生成虚拟测试邮箱
"""
host_name_list=['roptaoti.com','prcf.site','curcuplas.me','hotmail.red','wpdork.com','gmailni.com']
self.create_num(0,len(host_name_list)-1)
host_name=host_name_list[self.create_num(0,len(host_name_list)-1)]
# return self.fake.email() #这种方法可能存在真实邮箱
return self.gen_str().lower() + '@' + host_name
def gen_word(self):
"""
随机生成一个汉字
:return:
"""
head = random.randint(0xb0, 0xf7)
body = random.randint(0xa1, 0xfe)
val = f'{head:x} {body:x}'
word = bytes.fromhex(val).decode('gbk')
return word
if __name__ == '__main__':
my_faker = MyFaker()
# print(my_faker.gen_name())
print(my_faker.gen_email())

View File

@@ -0,0 +1,555 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/11/6 17:30
"""
import time
import re
from retrying import retry
from base_framework.public_tools import log
from base_framework.public_tools.db_dbutils_init import get_pg_connection # 假设有PostgreSQL连接池
from base_framework.public_tools.read_config import get_current_config, get_current_env
from base_framework.public_tools.huohua_dbs import HuoHuaDBS
obj_log = log.get_logger()
class PgSqlHelper:
"""
PostgreSQL数据库操作
"""
def __init__(self):
self.db = get_pg_connection() # PostgreSQL连接池
self.current_business = get_current_config(section='run_evn_name', key='current_business')
self.current_evn = get_current_env()
self.qa_db_to_sim_instance_name = {}
self.sim_dbs = HuoHuaDBS()
# 封装执行命令
def execute(self, sql, param=None, auto_close=False, choose_db=None):
"""
| 功能说明: | 执行具体的sql语句 |
| 输入参数: | sql | 待执行的sql语句 |
| | param=None | sql语句中where后跟的参数也可直接写在sql语句中 |
| | auto_close=True | 是否自动关闭数据库连接,默认:自动关闭 |
| 返回参数: | conncursorcount | 连接,游标,行数 |
"""
if self.current_evn.lower() == "sim":
raise Exception("SIM环境请直接使用huohua_dbs.py中的函数")
try:
cursor, conn = self.db.getconn(choose_db=choose_db) # 从连接池获取连接
except Exception as e:
# 打印详细的连接信息
try:
from base_framework.public_tools.read_config import ReadConfig
from base_framework.base_config.current_pth import config_file_path
rc = ReadConfig(config_file_path)
db_host = rc.get_value(sections='PostgreSQL', options='db_test_host')
db_port = rc.get_value(sections='PostgreSQL', options='db_test_port')
db_name = rc.get_value(sections='PostgreSQL', options='db_test_dbname')
db_user = rc.get_value(sections='PostgreSQL', options='db_test_user')
db_password = rc.get_value(sections='PostgreSQL', options='db_test_password')
error_info = """
PostgreSQL连接失败
连接配置信息:
主机(Host): {}
端口(Port): {}
数据库名(Database): {}
用户名(User): {}
密码(Password): {} (已隐藏)
选择数据库(ChooseDB): {}
错误详情: {}
""".format(
db_host, db_port, db_name, db_user,
'*' * len(db_password) if db_password else 'None',
choose_db or 'default',
str(e)
)
print(error_info)
obj_log.error(error_info)
except Exception as config_error:
error_info = "PostgreSQL连接失败: {} (无法读取配置信息: {})".format(str(e), str(config_error))
print(error_info)
obj_log.error(error_info)
raise ValueError("PostgreSQL连接失败: {}".format(str(e)))
try:
# count : 为改变的数据条数
if param:
# PostgreSQL使用 %s 作为占位符与MySQL相同
count = cursor.execute(sql, param)
else:
count = cursor.execute(sql)
conn.commit()
if auto_close:
self.close(cursor, conn)
except Exception as e:
obj_log.error("PostgreSQL执行SQL失败: {}, SQL: {}".format(str(e), sql))
raise ValueError("数据库操作失败SQL语句{}, 错误: {}".format(sql, str(e)))
return cursor, conn, count
# 释放连接
@staticmethod
def close(cursor, conn):
cursor.close()
conn.close()
# 查询所有
def select_all(self, sql, param=None, choose_db=None, show_log=True):
"""
| 功能说明: | 查询数据库 | 并返回所有结果 |
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_select(sql_content=sql)
if show_log:
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
res = cursor.fetchall()
if show_log:
obj_log.info('数据库查询结果:{}'.format(res))
return res
except Exception as e:
if cursor and conn:
self.close(cursor, conn)
raise RuntimeError(e.args)
finally:
if cursor and conn and not show_log:
self.close(cursor, conn)
def select_all_as_list(self, sql, choose_db=None):
"""
| 功能说明: | 查询数据库 | 功能同select_all,仅返回结果为list |
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_select(sql_content=sql, r_type='list')
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, choose_db=choose_db)
res = cursor.fetchall()
has_data = False
for item in res:
for key in item:
if item[key]:
has_data = True
break
if not has_data:
return []
res_list = []
for index in range(len(res)):
if len(res[index]) > 1:
res_row = []
for key in res[index]:
res_row.append(res[index][key])
res_list.append(res_row)
else:
for key in res[index]:
res_list.append(res[index][key])
return res_list
except Exception as e:
if cursor and conn:
self.close(cursor, conn)
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 查询单条
def select_one(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 查询数据库,并返第一行 |
"""
if self.current_evn.lower() == "sim":
if 'limit' not in sql.lower():
sql = sql.split(';')[0] + ' limit 1;'
sim_data = self.sim_dbs.dbs_select(sql_content=sql)
if sim_data:
return sim_data[0]
else:
return {}
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
res = cursor.fetchone()
return res
except Exception as e:
if cursor and conn:
self.close(cursor, conn)
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 增加单条数据
def insert_one(self, sql, param=None, choose_db=None, log_level='info'):
"""
| 功能说明: | insert一行数据 |
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if log_level.lower() == 'info':
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
conn.commit()
if log_level.lower() == 'info':
obj_log.info('插入数据库条数:{}'.format(count))
return count
except Exception as e:
if conn:
conn.rollback()
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 插入后返回插入IDPostgreSQL方式
def insert_one_extension(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert一行数据并返回插入行的ID |
"""
return_dict = dict()
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
# PostgreSQL需要在INSERT语句后添加RETURNING子句来获取ID
# 如果SQL中已有RETURNING则直接使用
if 'RETURNING' not in sql.upper():
# 尝试提取表名和主键列名(简化处理,实际需根据业务调整)
table_match = re.search(r'INSERT INTO\s+(\w+\.)?(\w+)', sql, re.IGNORECASE)
if table_match:
sql = sql.rstrip(';') + ' RETURNING id;'
cursor, conn = None, None
try:
cursor, conn = self.db.getconn(choose_db=choose_db)
if param:
cursor.execute(sql, param)
else:
cursor.execute(sql)
# 获取返回的ID
inserted_id = cursor.fetchone()[0] if 'RETURNING' in sql.upper() else None
count = cursor.rowcount
conn.commit()
obj_log.info('插入数据库条数:{}'.format(count))
return_dict['insert_count'] = count
return_dict['insert_id'] = inserted_id
return return_dict
except Exception as e:
if conn:
conn.rollback()
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 插入多条数据
def insert_many(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert多行数据 |
"""
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn = self.db.getconn(choose_db=choose_db)
count = cursor.executemany(sql, eval(str(param)))
conn.commit()
obj_log.info('插入数据库条数:{}'.format(count))
return count
except Exception as e:
if conn:
conn.rollback()
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 插入多条并返回ID
def insert_many_extension(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert多行数据并返回ID列表 |
"""
return_dict = dict()
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
# PostgreSQL需要在INSERT语句后添加RETURNING子句
if 'RETURNING' not in sql.upper():
sql = sql.rstrip(';') + ' RETURNING id;'
cursor, conn = None, None
try:
cursor, conn = self.db.getconn(choose_db=choose_db)
if param:
cursor.executemany(sql, eval(str(param)))
else:
cursor.execute(sql)
# 获取所有返回的ID
inserted_ids = [row[0] for row in cursor.fetchall()] if 'RETURNING' in sql.upper() else []
count = cursor.rowcount
conn.commit()
obj_log.info('插入数据库条数:{}'.format(count))
return_dict['insert_count'] = count
return_dict['insert_ids'] = inserted_ids
if inserted_ids:
return_dict['insert_id'] = inserted_ids[0] # 第一条数据的ID
return return_dict
except Exception as e:
if conn:
conn.rollback()
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 删除
def delete(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 删除数据库记录 |
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
obj_log.info('删除数据库条数:{}'.format(count))
return count
except Exception as e:
if cursor and conn:
self.close(cursor, conn)
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 更新
def update(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 更新数据库记录 |
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
conn.commit()
obj_log.info('更新数据库条数:{}'.format(count))
return count
except Exception as e:
if conn:
conn.rollback()
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
# 以下方法保持原样,仅需确保内部调用的方法正确
def check_result_exist(self, *select_statement, retry_count=3):
start = 0
flag = 0
while start <= retry_count:
for sql in select_statement:
res = self.select_all(sql=sql)
if not res:
if start == retry_count:
obj_log.info('the result is not exist,but expect at least one')
raise RuntimeError(u'No results, when exec sql: %s' % sql)
else:
obj_log.info('the result is not exist,retry {}'.format(start + 1))
start += 1
time.sleep(1)
else:
obj_log.info('find [{0}] results when exec {1}'.format(len(res), sql))
flag += 1
break
if flag > 0:
break
def check_result_not_exist(self, *select_statement):
for sql in select_statement:
res = self.select_all(sql=sql)
if res:
obj_log.info('find [{0}] results, but expect 0'.format(len(res)))
raise RuntimeError(u'the result exist, when exec sql: %s' % sql)
else:
obj_log.info('the result is not exist, this step pass...')
def row_count(self, selectStatement, param=None):
cursor, conn = None, None
try:
cursor, conn, count = self.execute(selectStatement, param)
return count
except Exception as e:
print("error_msg:", e.args)
return 0
finally:
if cursor and conn:
self.close(cursor, conn)
# 数据库断言方法适配PostgreSQL
def kw_check_if_exists_in_database(self, selectStatement, choose_db=None):
obj_log.info('Executing : Check If Exists In Database | %s ' % selectStatement)
if not self.select_one(selectStatement, choose_db=choose_db):
raise AssertionError("Expected to have have at least one row from '%s' "
"but got 0 rows." % selectStatement)
else:
return True
def kw_check_if_not_exists_in_database(self, selectStatement, choose_db=None):
obj_log.info('Executing : Check If Not Exists In Database | %s ' % selectStatement)
queryResults = self.select_one(selectStatement, choose_db=choose_db)
if queryResults:
raise AssertionError("Expected to have have no rows from '%s' "
"but got some rows : %s." % (selectStatement, queryResults))
else:
return True
def kw_row_count_is_0(self, selectStatement):
obj_log.info('Executing : Row Count Is 0 | %s ' % selectStatement)
num_rows = self.row_count(selectStatement)
if num_rows > 0:
raise AssertionError("Expected zero rows to be returned from '%s' "
"but got rows back. Number of rows returned was %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_equal_to_x(self, selectStatement, numRows):
obj_log.info('Executing : Row Count Is Equal To X | %s | %s ' % (selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows != int(str(numRows)):
raise AssertionError("Expected same number of rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_greater_than_x(self, selectStatement, numRows):
obj_log.info('Executing : Row Count Is Greater Than X | %s | %s ' % (selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows <= int(numRows):
raise AssertionError("Expected more rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_less_than_x(self, selectStatement, numRows):
obj_log.info('Executing : Row Count Is Less Than X | %s | %s ' % (selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows >= int(numRows):
raise AssertionError("Expected less rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_table_must_exist(self, tableName):
"""
PostgreSQL检查表是否存在
"""
obj_log.info('Executing : Table Must Exist | %s ' % tableName)
# PostgreSQL检查表是否存在的查询
selectStatement = """
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = %s
);
"""
try:
result = self.select_one(selectStatement, (tableName,))
if not result or not result[0]: # 结果为False或None
raise AssertionError("Table '%s' does not exist in the database" % tableName)
return True
except Exception as e:
raise AssertionError("Error checking table existence: %s" % str(e))
# 生成器方式查询
def select_all_as_generator(self, sql, param=None, choose_db=None):
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = None, None
try:
cursor, conn, counts = self.execute(sql, param, choose_db=choose_db)
for count in range(counts):
item = cursor.fetchone()
obj_log.info(item)
yield item
except Exception as e:
raise RuntimeError(e.args)
finally:
if cursor and conn:
self.close(cursor, conn)
def get_qa_to_sim_dbs(self):
get_sql = "select qa_db,sim_instance from sparkatp.qa_db_mapping_sim_dbs where status=1 and is_delete=0"
res_info = self.sim_dbs.dbs_query_by_db_name("qadb-slave", "sparkatp", get_sql, limit_num=1000)
for item in res_info:
self.qa_db_to_sim_instance_name.update({item[0]: item[1]})
return self.qa_db_to_sim_instance_name
def get_instance_name(self, sql_str):
db_name = sql_str.split(".")[0].split(" ")[-1].replace("`", "").replace(" ", "")
return self.qa_db_to_sim_instance_name.get(db_name)
if __name__ == '__main__':
# 测试示例
db = PgSqlHelper()
# 测试查询
sql = "SELECT * FROM account.account LIMIT 5;"
res = db.select_one(sql=sql)
print(res)
# 测试插入带RETURNING
insert_sql = """
INSERT INTO account.account (username, email, created_at)
VALUES (%s, %s, NOW())
RETURNING id;
"""
insert_params = ('test_user', 'test@example.com')
insert_result = db.insert_one_extension(insert_sql, insert_params)
print(f"插入结果: {insert_result}")

View File

@@ -0,0 +1,3 @@
from base_framework.public_tools.pymailtm.pymailtm import MailTm, Account, Message
__version__ = '1.0'

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
from argparse import ArgumentParser
import signal
import sys
from pymailtm import MailTm
def init():
def signal_handler(sig, frame) -> None:
print('\n\nClosing! Bye!')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
parser = ArgumentParser(
description="A python interface to mail.tm web api. The temp mail address "
"will be copied to the clipboard and the utility will then "
"wait for a message to arrive. When it does, it will be "
"opened in a browser. Exit the loop with ctrl+c.")
parser.add_argument('-n', '--new-account', action='store_true',
help="whether to force the creation of a new account")
parser.add_argument('-l', '--login', action='store_true',
help="print the credentials and open the login page, then exit")
args = parser.parse_args()
if args.login:
MailTm().browser_login(new=args.new_account)
else:
MailTm().monitor_new_account(force_new=args.new_account)
if __name__ == "__main__":
init()

View File

@@ -0,0 +1,226 @@
import json
import os
import pyperclip
import random
import requests
import string
import webbrowser
from random_username.generate import generate_username
from dataclasses import dataclass
from pathlib import Path
from tempfile import NamedTemporaryFile
from time import sleep
from typing import Dict
class Account:
"""Representing a temprary mailbox."""
def __init__(self, id, address, password):
self.id_ = id
self.address = address
self.password = password
# Set the JWT
jwt = MailTm._make_account_request("token",
self.address, self.password)
self.auth_headers = {
"accept": "application/ld+json",
"Content-Type": "application/json",
"Authorization": "Bearer {}".format(jwt["token"])
}
self.api_address = MailTm.api_address
def get_messages(self, page=1):
"""Download a list of messages currently in the account."""
r = requests.get("{}/messages?page={}".format(self.api_address, page),
headers=self.auth_headers, verify=False)
messages = []
for message_data in r.json()["hydra:member"]:
# recover full message
r = requests.get(
f"{self.api_address}/messages/{message_data['id']}", headers=self.auth_headers, verify=False)
text = r.json()["text"]
html = r.json()["html"]
# prepare the mssage object
messages.append(Message(
message_data["id"],
message_data["from"],
message_data["to"],
message_data["subject"],
message_data["intro"],
text,
html,
message_data))
return messages
def delete_account(self):
"""Try to delete the account. Returns True if it succeeds."""
r = requests.delete("{}/accounts/{}".format(self.api_address,
self.id_), headers=self.auth_headers, verify=False)
return r.status_code == 204
def monitor_account(self):
"""Keep waiting for new messages and open them in the browser."""
while True:
print("\nWaiting for new messages...")
start = len(self.get_messages())
while len(self.get_messages()) == start:
sleep(1)
print("New message arrived!")
self.get_messages()[0].open_web()
@dataclass
class Message:
"""Simple data class that holds a message information."""
id_: str
from_: Dict
to: Dict
subject: str
intro: str
text: str
html: str
data: Dict
def open_web(self):
"""Open a temporary html file with the mail inside in the browser."""
with NamedTemporaryFile(mode="w", delete=False, suffix=".html") as f:
html = self.html[0].replace("\n", "<br>").replace("\r", "")
message = """<html>
<head></head>
<body>
<b>from:</b> {}<br>
<b>to:</b> {}<br>
<b>subject:</b> {}<br><br>
{}</body>
</html>""".format(self.from_, self.to, self.subject, html)
f.write(message)
f.flush()
file_name = f.name
open_webbrowser("file://{}".format(file_name))
# Wait a second before deleting the tempfile, so that the
# browser can load it safely
sleep(1)
# os.remove(file_name)
def open_webbrowser(link: str) -> None:
"""Open a url in the browser ignoring error messages."""
saverr = os.dup(2)
os.close(2)
os.open(os.devnull, os.O_RDWR)
try:
webbrowser.open(link)
finally:
os.dup2(saverr, 2)
class CouldNotGetAccountException(Exception):
"""Raised if a POST on /accounts or /authorization_token return a failed status code."""
class InvalidDbAccountException(Exception):
"""Raised if an account could not be recovered from the db file."""
class MailTm:
"""A python wrapper for mail.tm web api, which is documented here:
https://api.mail.tm/"""
api_address = "https://api.mail.tm"
db_file = os.path.join(Path.home(), ".pymailtm")
def _get_domains_list(self):
r = requests.get("{}/domains".format(self.api_address), verify=False)
response = r.json()
domains = list(map(lambda x: x["domain"], response["hydra:member"]))
return domains
def get_account(self, password=None):
"""Create and return a new account."""
username = (generate_username(1)[0]).lower()
domain = random.choice(self._get_domains_list())
address = "{}@{}".format(username, domain)
if not password:
password = self._generate_password(6)
response = self._make_account_request("accounts", address, password)
account = Account(response["id"], response["address"], password)
self._save_account(account)
return account
def _generate_password(self, length):
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for i in range(length))
@staticmethod
def _make_account_request(endpoint, address, password):
account = {"address": address, "password": password}
headers = {
"accept": "application/ld+json",
"Content-Type": "application/json"
}
r = requests.post("{}/{}".format(MailTm.api_address, endpoint),
data=json.dumps(account), headers=headers, verify=False)
if r.status_code not in [200, 201]:
raise CouldNotGetAccountException()
return r.json()
def monitor_new_account(self, force_new=False):
"""Create a new account and monitor it for new messages."""
account = self._open_account(new=force_new)
account.monitor_account()
def _save_account(self, account: Account):
"""Save the account data for later use."""
data = {
"id": account.id_,
"address": account.address,
"password": account.password
}
with open(self.db_file, "w+") as db:
json.dump(data, db)
def _load_account(self):
"""Return the last used account."""
with open(self.db_file, "r") as db:
data = json.load(db)
# send a /me request to ensure the account is there
if "address" not in data or "password" not in data or "id" not in data:
# No valid db file was found, raise
raise InvalidDbAccountException()
else:
return Account(data["id"], data["address"], data["password"])
def _open_account(self, new=False):
"""Recover a saved account data, check if it's still there and return that one; otherwise create a new one and
return it.
:param new: bool - force the creation of a new account"""
def _new():
account = self.get_account()
print("New account created and copied to clipboard: {}".format(account.address), flush=True)
return account
if new:
account = _new()
else:
try:
account = self._load_account()
print("Account recovered and copied to clipboard: {}".format(account.address), flush=True)
except Exception:
account = _new()
pyperclip.copy(account.address)
print("")
return account
def browser_login(self, new=False):
"""Print login credentials and open the login page in the browser."""
account = self._open_account(new=new)
print("\nAccount credentials:")
print("\nEmail: {}".format(account.address))
print("Password: {}\n".format(account.password))
open_webbrowser("https://mail.tm/")
sleep(1) # Allow for the output of webbrowser to arrive

View File

@@ -0,0 +1,196 @@
# coding=utf-8
import configparser
import os
import codecs
from base_framework.base_config.current_pth import *
from base_framework.public_tools import log
obj_log = log.get_logger()
class ReadConfig:
"""
专门读取配置文件的,.ini文件格式
"""
def __init__(self, filename=config_file_path):
self.config_path = filename
fd = open(self.config_path, encoding='utf-8')
data = fd.read()
if data[:3] == codecs.BOM_UTF8:
data = data[3:]
files = codecs.open(self.config_path, "w")
files.write(data)
files.close()
fd.close()
self.cf = configparser.SafeConfigParser(allow_no_value=True)
self.cf.read(self.config_path, encoding='utf-8')
def get_value(self, sections, options):
"""
获取config文件数据
"""
return self.cf.get(sections, options)
def read_cfg(self):
"""
读取配置文件路径
"""
return self.cf.read(filenames=self.config_path, encoding='utf-8')
def get_sections(self):
"""
读取配置文件中所有的section可以理解为组名)
"""
return self.cf.sections()
def get_options(self, section):
"""
读取该section下所有的option可以理解成读取该组下的所有key
"""
return self.cf.options(section)
def get_items(self, section):
"""
读取该section下的所有值并以键值对形式输出
"""
return self.cf.items(section)
def add_section(self, section):
"""
添加一个section参数为section的名称
"""
self.cf.add_section(section)
with open(self.config_path, 'w') as fw: # 循环写入
self.cf.write(fw)
def set_section(self, section, option, value):
"""
在section下面添加一条数据key=value需要调用write()将内容写入文件
"""
self.cf.set(section, option, value)
with open(self.config_path, 'w') as fw: # 循环写入
self.cf.write(fw)
def get_all_cfg(self, section=None):
all_config = {}
for key in self.get_sections():
all_values = {}
for value_key in self.get_options(key):
all_values[value_key] = self.get_value(key, value_key)
all_config[key] = all_values
if section:
if section in all_config:
return all_config[section]
else:
raise Exception("配置节点:{}在文件中不存在,请检查....".format(section))
else:
return all_config
def get_current_config(file_path=env_choose_path, section=None, key=None):
"""
功能读取env_choose.ini中的配置并返回
"""
rd = ReadConfig(file_path)
if key in rd.get_options(section=section):
return rd.get_value(sections=section, options=key)
else:
return "server not exist"
def get_zhyy_config(file_path=config_choose_path, section=None, key=None):
"""
功能读取env_choose.ini中的配置并返回
"""
rd = ReadConfig(file_path)
if key in rd.get_options(section=section):
return rd.get_value(sections=section, options=key)
else:
return "server not exist"
def get_current_env():
"""
功能读取env_choose.ini中的当前环境配置并返回
"""
rd = ReadConfig(env_choose_path)
env = rd.get_value(sections="run_jira_id", options="huohua-podenv")
if not env:
env = rd.get_value(sections="run_evn_name", options="current_evn")
return env
def get_uat_config(website, page, key=None, section='page_element'):
"""
| 功能说明: | 从uat_config文件夹下的对应文件中读取key |
| 输入参数: | website | 站点名public_business/uat/uat_config目录下的文件名 |
| | page | web页面名称website文件夹下的文件名不用.ini的文件后缀名 |
| | section | 配置文件中的节点名,默认读取页面元素 |
| | key | 配置文件中的key如果不输入将以当前构建环境为key如qasim |
| 作者信息: | 吴勇刚 | 2022.06.22 |
"""
config_path = os.path.join(uat_config_path, website, "{}.ini".format(page))
if not key:
key = get_current_config(section="run_evn_name", key="current_evn").lower()
rd = ReadConfig(config_path)
return rd.get_value(sections=section, options=key)
class InitConfig:
def __init__(self, run_user_name=None, current_evn=None):
self.cfg_rtn = ReadConfig()
self.evn_rtn = ReadConfig(env_choose_path)
self.db_rtn = ReadConfig(db_config_path)
self.config_rtn = ReadConfig(config_choose_path)
self.all_cfg = self.cfg_rtn.get_all_cfg()
self.config_cfg = self.config_rtn.get_all_cfg()
self.evn_cfg = self.evn_rtn.get_all_cfg()
self.db_cfg = self.db_rtn.get_all_cfg()
try:
self.astwb_cfg = ReadConfig(astwb_config)
self.astwb_all_cfg = self.astwb_cfg.get_all_cfg()
# 合并配置文件
self.all_cfg.update(self.astwb_all_cfg)
except Exception as e:
pass
if current_evn is None:
self.current_evn = self.evn_cfg['run_evn_name']['current_evn'] if self.evn_cfg['run_evn_name'][
'current_evn'] != '' else 'QA'
else:
self.current_evn = current_evn
if run_user_name:
self.current_user = run_user_name
else:
self.current_user = self.evn_cfg['run_user_name']['default_user']
self.zhyy_host = self.get_current_env_info(self.config_cfg, self.current_evn, 'zhyy_login')
def get_current_env_info(self, cfg_obj, current_env, key):
"""
config.ini中环境处理专用
:param cfg_obj: self.cfg_rtn = ReadConfig() self.all_cfg = self.cfg_rtn.get_all_cfg() cfg_obj = self.all_cfg
:param current_env: QA SIM PRODUCT
:param key: options
:return:
"""
try:
value = cfg_obj[current_env][key]
except KeyError:
value = cfg_obj['QA'][key]
if current_env == 'SIM':
value = value.replace('.qa.', '.sim.')
return value
elif current_env == 'PRODUCT':
value = value.replace('.qa.', '.')
return value
return value
if __name__ == '__main__':
pass
# init_cfg = InitConfig(curt_evn='QA', curt_user='lrq')
print(ReadConfig().add_section(section='temp_teacher'))
print(ReadConfig().set_section(section='temp_teacher', option='show_username', value=11))

View File

@@ -0,0 +1,247 @@
# coding: utf-8
from redis import ConnectionPool, StrictRedis
import time
from base_framework.public_tools.read_config import ReadConfig
from base_framework.base_config.current_pth import env_choose_path
class RedisApi:
"""
| 功能说明: | 操作redis |
| 作者信息: | 作者 qiaoxinjiu |
| 修改时间: | 2021-09-03 |
"""
def __init__(self):
self.redis_con = None
self.pool = None
self.host = None
self.pwd = ""
self.port = 6379
self.evn_cfg = ReadConfig(env_choose_path)
self.current_business = self.evn_cfg.get_value(sections="run_evn_name", options="current_business")
def kw_conn_redis_service(self, redis_dbname, is_as=True):
"""
| 功能说明: | 与redis server 建立连接 |
| 输入参数: |
| | redis_dbname | redis数据库名默认为5 |
| | redis_ip_addr | redis服务器地址 |
| | password | redis密码 |
| | redis_port | redis端口号默认为6379 |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | 无 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
说明:初始化 pool和redis_con
"""
if not self.host: # 如果没有指定redis域名
if is_as:
self.host = 'redis-qa2.redis.rds.aliyuncs.com'
self.pwd = 'AcUVeRb8lN'
else:
if self.current_business == "hh":
self.host = 'redis.qa.huohua.cn'
self.pwd = 'AcUVeRb8lN'
elif self.current_business == "hhi":
self.host = 'redis.qa.visparklearning.com'
self.pwd = 'hxTjlWBYdK6UpAGF'
try:
self.pool = ConnectionPool(host=self.host, port=self.port,
db=redis_dbname, password=self.pwd, decode_responses=True)
self.redis_con = StrictRedis(connection_pool=self.pool)
except RuntimeError as e:
raise RuntimeError('Failed to connect redis service. error: %s' % e)
def kw_get_redis_message(self, redis_dbname, redis_key, timeout=5, is_as=True):
"""
| 功能说明: | 获取redis_key对应的value |
| 输入参数: | redis_key |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | redis_key对应的value |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
"""
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
try:
while timeout > 0:
if not self.redis_con.exists(redis_key):
time.sleep(1)
timeout -= 1
continue
else:
resp = self.redis_con.get(redis_key)
return resp
raise RuntimeError('not have redis_key, please check')
except RuntimeError as e:
raise RuntimeError('get redis value. error: %s' % e)
finally:
self.kw_disconnect_redis()
def kw_verify_redis_have_key(self, redis_dbname, key, timeout=5, is_as=True):
"""
| 功能说明: | 验证缓存中存在KEY |
| 输入参数: | key | key值 |
| | timeout | 超时时间默认为5s |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | True/False存在返回true否则失败 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
"""
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
try:
while timeout > 0:
if self.redis_con.exists(key):
return True
else:
time.sleep(1)
timeout -= 1
continue
raise RuntimeError('not have this key: %s' % key)
except RuntimeError as e:
raise RuntimeError('verify_redis_have_key. error: %s' % e)
finally:
self.kw_disconnect_redis()
def kw_verify_redis_not_have_key(self, redis_dbname, key, timeout=5, is_as=True):
"""
| 功能说明: | 验证缓存中不存在KEY |
| 输入参数: | key | key值 |
| | timeout | 超时时间默认为5s |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | True/False不存在返回true否则失败 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
"""
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
try:
while timeout > 0:
if not self.redis_con.exists(key):
return True
else:
time.sleep(1)
timeout -= 1
continue
raise RuntimeError('have this key: %s' % key)
except RuntimeError as e:
raise RuntimeError('verify_redis_not_have_key. error: %s' % e)
finally:
self.kw_disconnect_redis()
def kw_disconnect_redis(self):
"""
| 功能说明: | 断开rebids服务 |
| 输入参数: | 无 |
| 返回参数: | 无 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
备注: 断开连接池
"""
try:
self.redis_con.close()
self.pool.disconnect()
except RuntimeError as e:
raise RuntimeError('disconnect redis service failed. error: %s' % e)
def kw_del_key_by_key(self, redis_dbname, redis_key, is_as=True, is_check=True, host=None, pwd=None):
"""
| 功能说明: | 删掉redis中的 redis_key |
| 输入参数: | host | redis域名可以不传按环境走默认配置 |
| | pwd | redis密码可以不传按环境走默认配置 |
| | redis_dbname | redis数据库编号必传 |
| | redis_key | 关键key必传 |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | 无 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
"""
if host and pwd: # 当指定了域名,则按入参查询
self.host = host
self.pwd = pwd
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
try:
resp = self.redis_con.delete(redis_key)
if not resp and is_check:
raise RuntimeError('del key %s failed. error: %s' % (redis_key, resp))
except Exception as e:
print(e)
self.kw_disconnect_redis()
# def kw_del_key_by_key_pre(self, redis_dbname, key_pre, is_as=True):
# """
# | 功能说明: | 根据前缀删除|
# | 输入参数: | key_pre |
# | | is_as | 是否为ALLSchool业务 |
# | 返回参数: | 无 |
# | 作者信息: | 作者 huaxuemin | 修改时间 2021-09-03 |
# """
# self.kw_conn_redis_service(redis_dbname, is_as=is_as)
# try:
# key_pre = key_pre + "*"
# res = self.redis_con.scan(match=key_pre, count=9999999999)
# if len(res[1]) > 0:
# for key in res[1]:
# resp = self.redis_con.delete(key)
# if not resp:
# raise RuntimeError('del key %s failed. error: %s' % (key, resp))
# except Exception as e:
# print(e)
# self.kw_disconnect_redis()
def kw_get_key_by_key_pre(self, redis_dbname, key_pre, is_as=True):
"""
| 功能说明: | 根据前缀删除|
| 输入参数: | key_pre |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | 无 |
| 作者信息: | 作者 huaxuemin | 修改时间 2021-10-07 |
"""
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
key_pre = key_pre + "*"
# for key in self.redis_con.scan_iter(match=key_pre):
# self.redis_con.delete(key)
for key in self.redis_con.keys(key_pre):
return key
self.kw_disconnect_redis()
return ""
def kw_set_redis_by_db_key_value(self, db_num, key, value, is_as=False):
self.kw_conn_redis_service(db_num, is_as=is_as)
self.redis_con.set(key, value)
if not self.redis_con.exists(key):
raise ValueError("设置redis失败。")
def kw_get_zset_message(self, redis_dbname, redis_key, timeout=5, is_as=True):
"""
| 功能说明: | 获取redis_key 有序集合对应的value |
| 输入参数: | redis_key |
| | is_as | 是否为ALLSchool业务 |
| 返回参数: | redis_key 有序集合对应的value |
| 作者信息: | 作者 huaxuemin | 修改时间 2022-07-27 |
"""
self.kw_conn_redis_service(redis_dbname, is_as=is_as)
try:
while timeout > 0:
if not self.redis_con.exists(redis_key):
time.sleep(1)
timeout -= 1
continue
else:
resp = self.redis_con.zrange(redis_key, 0, -1)
return resp
print('not have redis_key: {}, please check'.format(redis_key))
return []
except RuntimeError as e:
print('get redis value. error: %s' % e)
return []
finally:
self.kw_disconnect_redis()
if __name__ == '__main__':
test = RedisApi()
# res = test.kw_get_redis_message(9, "peppa:ticket:call:pcsm:13708231975", is_as=False)
res = test.kw_del_key_by_key(host='rediscourse.qa.huohua.cn',
pwd='Bkl6LvqfzFCzYPAh',
redis_dbname='9',
redis_key="public-holiday-teacher-v1:1_82908",
is_as=False)
print(res)
# test.kw_conn_redis_service(0)
# test.kw_del_key_by_key(9, "REVISIT_TASK:REVISITING_IDS", is_as=False)
# print(test.kw_get_key_by_key_pre(0, "HULK-ORG-API:RESET-TOKEN:huaxuemin@huohua.cn#"))

View File

@@ -0,0 +1,138 @@
# coding=utf-8
# 用于MQ的各类操作
import time
import os
from base_framework.public_business.common.UBRD.kw.promotion_keyword import obj_log
from base_framework.public_tools.read_config import ReadConfig, get_current_env, get_current_config
from base_framework.public_tools.runner import Runner
from base_framework.public_tools.custom_error import BusinessError
obj_runner = Runner()
class RocketMQ:
def __init__(self, env=''):
self.need_login = True # 是否需要登录
self.team = get_current_config(section="run_evn_name", key="current_team")
if not env:
env = get_current_env().lower()
if env.lower() == 'sim':
if self.team in ['XUEDAU']:
self.mq_host = ""
self.need_login = False
else:
self.mq_host = "https://mq-console.sim.huohua.cn"
else:
if self.team in ['XUEDAU']:
self.mq_host = "http://10.250.200.3:19876"
self.need_login = False
else:
self.mq_host = "https://mq-console.qa.huohua.cn"
def mq_query_msg_info(self, topic, begin_time, end_time):
"""查询mq消息"""
msg_id_list = self.__query_msg_id_list(topic=topic, begin_time=begin_time, end_time=end_time)
msg_info = []
for msg_id in msg_id_list:
m_info = self.__query_msg_detail_by_id(topic=topic, msg_id=msg_id)
msg_info.append(m_info)
return msg_info
def mq_query_msg_info_by_sub_str(self, topic, begin_time, end_time, sub_str=None):
"""查询mq消息并返回匹配的消息内容"""
msg_info = self.mq_query_msg_info(topic=topic, begin_time=begin_time, end_time=end_time)
if sub_str:
sub_msg = []
for msg in msg_info:
if str(sub_str) in str(msg["msg_body"]):
sub_msg.append(msg)
return sub_msg
else:
return msg_info
def __query_msg_id_list(self, topic, begin_time, end_time):
"""查询mq的id列表"""
mq_url = "{}/message/queryMessageByTopic.query".format(self.mq_host)
post_data = {"topic": topic,
"begin": str(time.mktime(time.strptime(str(begin_time), '%Y-%m-%d %H:%M'))).split('.')[0] + '000',
"end": str(time.mktime(time.strptime(str(end_time), '%Y-%m-%d %H:%M'))).split('.')[0] + '000'}
if self.need_login:
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="GET", params=post_data)
else:
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="GET", token=False, params=post_data)
if resp['status'] == 0:
msg_id_list = []
for item in resp['data']:
msg_id_list.append(item['msgId'])
return msg_id_list
else:
raise Exception("查询MQ消息列表失败{}".format(resp))
def __query_msg_detail_by_id(self, topic, msg_id):
"""查询mq消息明细"""
mq_url = "{}/message/viewMessage.query".format(self.mq_host)
post_data = {"topic": topic, "msgId": msg_id}
if self.need_login:
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="GET", params=post_data)
else:
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="GET", token=False, params=post_data)
if resp['status'] == 0:
msg_tag = resp['data']['messageView']['properties'].get('TAGS', None)
msg_key = resp['data']['messageView']['properties'].get('KEYS', None)
msg_env = resp['data']['messageView']['properties'].get('podenv', None)
msg_body = resp['data']['messageView'].get('messageBody', None)
return {"msg_id": msg_id, "msg_tag": msg_tag, "msg_key": msg_key, "msg_env": msg_env, "msg_body": msg_body}
else:
raise Exception("查询MQ消息明细失败{}".format(resp))
def mq_send_msg(self, topic, message_body, tag=None, key=None, pro=None, **kwargs):
"""mq消息发送"""
mq_url = "{}/topic/sendTopicMessage.do".format(self.mq_host)
post_data = {'topic': topic,
'tag': tag,
'key': key,
'messageBody': str(message_body).replace("'", "\"")
}
if not pro:
# 添加启动文件中的独立环境编号
cfg_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
ini_path = cfg_path + '/base_framework/base_config/env_choose.ini'
jira_id = ReadConfig(ini_path).get_value(sections='run_jira_id', options='huohua-podenv')
if jira_id:
post_data['pro'] = jira_id
else:
post_data['pro'] = 'qa'
else:
post_data['pro'] = pro
if self.team.lower() == "xuedau":
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="POST", json=post_data, **kwargs)
else:
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="POST", json=post_data)
try:
obj_log.info("发送mq消息返回{}".format(resp))
if resp and 'errMsg' in resp and 'timeout' in str(resp['errMsg']):
time.sleep(1) # 如果接口返回超时,就再发送一次
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="POST", json=post_data)
elif resp and 'message' in resp and '无效的token' in str(resp['message']):
time.sleep(1) # 如果token过期就再发送一次
resp = obj_runner.call_rest_api(user=None, API_URL=mq_url, req_type="POST", json=post_data)
elif resp and resp['status'] == 0 and resp['data']['sendStatus'] == 'SEND_OK':
return True
else:
raise Exception("发送mq消息失败{}".format(resp))
except Exception as e:
obj_log.info("发送mq消息体{}".format(post_data))
raise Exception("发送mq消息失败{}|{}".format(e, resp))
if __name__ == '__main__':
mq = RocketMQ()
mb = {"platformId": 2, "eventType": "user.join", "roleType": 200, "roleId": 48711, "appId": "", "source": "SparkEnglish", "classroomCode": "", "classroomType": 1, "scheduleCode": "ECR666358307917090884", "joinTime": 1747189786903, "classSizeType": 1, "isFirst": "", "subChannelCodeList": "", "deviceId": "20200830-9CFC-E8BE-8D68-9CFCE8BE8D6C", "classroomVersion": "25.5.3-qa.2212381", "appVersion": "25.4.7-stable.2172220"}
rsp = mq.mq_send_msg(topic='CP_CLASSROOM_STATUS_SYNC', message_body=mb, tag='TAG_ROLE_JOIN_TIME', pro='QA', xuedau="")
print(rsp)

View File

@@ -0,0 +1,365 @@
# -*- coding: utf-8 -*-
import requests
import json
import time
from urllib import parse
from base_framework.public_tools.get_token import LoginSys, SparkleLogin, AgentApiLogin, MarketApiLogin, token_dict, \
AllSchool, \
ParentLogin, student_token_cache, StudentLogin, SparkEduLogin, SparkSaasLogin, SparkSaasTeacherLogin
from base_framework.public_tools import log
from base_framework.public_tools.read_config import ReadConfig, get_current_env
from base_framework.base_config.current_pth import *
from base_framework.public_tools.read_config import get_current_config
from base_framework.public_tools.sqlhelper import MySqLHelper
obj_log = log.get_logger()
evn_cfg = ReadConfig(env_choose_path)
base_cfg = ReadConfig(config_file_path)
all_school_api_host = ['api.qa.allschool.com', 'api.sim.allschool.com', 'api.allschool.com']
class Runner(LoginSys):
def __init__(self):
super().__init__()
# self.env = get_current_config(section="run_evn_name", key="current_evn")
self.env = get_current_env()
self.is_need_sso = ["teach-api"]
self.team = get_current_config(section="run_evn_name", key="current_team")
self.db_con = MySqLHelper()
self.sql_data = [] # 存放接口响应时间
self.week = time.strftime("%w", time.localtime())
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super().__new__(cls)
return cls.instance
def __del__(self):
if self.sql_data: # 如果有数据未插入
# sql_str = ("insert into sparkatp.interface_response_time(team, in_type, rp_time, in_url,in_id) "
# "values (%s,%s,%s,%s,%s);")
# self.db_con.insert_many(sql=sql_str, param=self.sql_data)
# self.sql_data.clear()
pass
def __call_api(self, session, api_url, req_type, **kwargs):
try:
if req_type == "GET":
req = session.get(api_url, verify=False, **kwargs)
elif req_type == "POST":
req = session.post(api_url, verify=False, **kwargs)
elif req_type == "PUT":
req = session.put(api_url, verify=False, **kwargs)
elif req_type == "DELETE":
req = session.delete(api_url, verify=False, **kwargs)
elif req_type == "PATCH":
req = session.patch(api_url, verify=False, **kwargs)
else:
obj_log.info(f'req_type,输入错误!不支持请求方法{req_type}')
raise Exception(f'req_type,输入错误!不支持请求方法{req_type}')
except Exception as e:
obj_log.info('返回数据:{}'.format(e))
return e
if self.env.lower() != "sim": # 非sim环境才记录接口响应时间
# 每周六,记录一次所有接口的响应时间
if self.week in ['6']:
current_time = int(time.strftime("%H", time.localtime()))
if current_time >= 5: # 5点之前的404校验脚本不统计因为都是非正常参数详见http://10.250.200.1:8080/jenkins/view/2-%E8%BE%85%E5%8A%A9%E7%A8%8B%E5%BA%8F/job/%E6%8E%A5%E5%8F%A3404%E6%A3%80%E6%9F%A5/
run_time = int(req.elapsed.total_seconds() * 1000)
if run_time > 200: # 超过200ms的接口记录到数据库
in_data = (self.team, req_type, run_time, api_url, 0)
self.sql_data.append(in_data)
if len(self.sql_data) >= 10: # 每10条数据插入一次, 最后一次不足100条时在__del__中插入
sql_str = (
"insert into sparkatp.interface_response_time(team, in_type, rp_time, in_url,in_id) "
"values (%s,%s,%s,%s,%s);")
self.db_con.insert_many(sql=sql_str, param=self.sql_data)
self.sql_data.clear()
return req
def call_rest_api(self, API_URL, req_type, user=None,
token=None, current_evn=None, is_file=False, **kwargs):
'''
功能:所有接口访问入口
'''
if self.env.lower() == "sim":
API_URL = API_URL.replace(".qa.", ".sim.")
return self._call_zhyy_api(API_URL, req_type, user,
token, current_evn, is_file, **kwargs)
def _call_zhyy_api(self, api_url, req_type, user=None, token=None, current_evn=None, is_file=False, **kwargs):
"""
功能模拟走ruoyi登录模式带token访问接口
:param API_URL:
:param req_type:
:param user: 使用的自定义用户登录
:param token: 从SSO获取的token此字段使用默认值None不是的情况使用False
:param current_evn: QA or SIM
:param is_file:
:param kwargs:
:return:
"""
req_type = req_type.upper()
retry_num = 4
cnt = 0
if 'as_login_type' in kwargs.keys():
kwargs.pop('as_login_type')
options_dict = dict(**kwargs)
result = parse.urlparse(url=api_url, allow_fragments=True)
host = result.hostname
if not user:
user = self.curt_user
obj_log.info('登录系统为{},用户名为手动输入:{}'.format(host, user))
obj_log.info('请求地址:{}'.format(api_url))
obj_log.info('请求数据:{}'.format(options_dict))
header = {"Authorization": "Basic c3BhcmtsZS13ZWI6c3BhcmtsZS13ZWI=", "tenant-id": "1"}
session = requests.session()
session.headers.update(header)
while cnt < retry_num:
if token is None:
if not user:
user = evn_cfg.get_value(sections='run_user_name', options='default_user')
tenant_name = base_cfg.get_value(sections=user, options='tenant')
usr_name = base_cfg.get_value(sections=user, options='username')
usr_pwd = base_cfg.get_value(sections=user, options='password')
# 检查 kwargs 中是否包含登录信息tenantName/username 和 password
# 如果包含,使用传入的参数;否则使用配置文件中的登录信息
json_data = kwargs.get('json', {})
is_login_params = isinstance(json_data, dict) and 'password' in json_data and (
'tenantName' in json_data or 'username' in json_data)
if is_login_params:
# 使用传入的登录参数
obj_log.info('检测到 kwargs 中包含登录信息,使用传入的参数进行登录')
login_req = self.__call_api(session, api_url, req_type='POST', **kwargs)
else:
# 使用配置文件中的登录信息
obj_log.info('使用配置文件中的登录信息进行登录')
request_body = {'json': {'tenantName': tenant_name, 'username': usr_name, 'password': usr_pwd,
'rememberMe': 'true'}}
api_login_url = self.zhyy_host
login_req = self.__call_api(session, api_login_url, req_type='POST', **request_body)
try:
req_json = login_req.json()
except Exception:
# 如果 json() 失败,尝试将 text 转成 JSON
try:
if isinstance(login_req, Exception):
obj_log.warning("登录请求失败: {}".format(login_req))
req_json = {}
else:
req_json = json.loads(login_req.text)
except Exception:
if isinstance(login_req, Exception):
obj_log.warning("登录请求失败: {}".format(login_req))
else:
obj_log.warning("登录响应无法解析为JSON: {}".format(login_req.text))
req_json = {}
# 判断登录是否成功code为0或200表示成功或者data中包含accessToken
login_code = req_json.get("code")
if login_code not in [0, 200] and not req_json.get("data", {}).get('accessToken'):
raise Exception("SSO登录失败: {}".format(req_json))
user_token = req_json.get("data", {}).get('accessToken')
# 如果 api_url 本身就是登录接口,登录成功后直接返回登录响应
if is_login_params:
obj_log.info('api_url 是登录接口,直接返回登录响应')
if not is_file:
return req_json
else:
return login_req.content
else:
user_token = token
if user_token:
session.headers.update({'ssotoken': user_token})
session.headers.update({'sso-token': user_token})
session.headers.update({'accesstoken': user_token})
session.headers.update({'Accesstoken': user_token})
session.headers.update({'access-token': user_token})
session.headers.update({'Authorization': user_token})
session.headers.update({'token': user_token})
obj_log.info(f'请求头headers{session.headers}')
req = self.__call_api(session, api_url, req_type, **kwargs)
if isinstance(req, Exception):
obj_log.error('请求失败:{}'.format(req))
return req
if not is_file:
try:
rtn_temp = req.json()
except Exception as e:
# 如果 json() 失败,尝试将 text 转成 JSON
try:
rtn_temp = json.loads(req.text)
except Exception:
# 如果都失败,保持为 text 字符串
rtn_temp = req.text
else:
rtn_temp = req.content
# SSO登录过期处理开始
status_code = str(req.status_code)
obj_log.info('------状态码:{} 返回信息:{}'.format(status_code, rtn_temp))
try:
# 如果 rtn_temp 是字典,才能使用 .get() 方法
if isinstance(rtn_temp, dict):
resp_code = str(rtn_temp.get('code', '200'))
else:
resp_code = '200'
except:
resp_code = '200'
if not is_file:
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("--缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
if status_code == "401" or resp_code == '401':
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
if status_code == "200" and "<!DOCTYPE html>" in str(rtn_temp):
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("--缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
# SSO登录过期处理完成
req.close()
if not is_file:
obj_log.info('返回数据:{}'.format(rtn_temp))
else:
obj_log.info('返回数据:文件字节流')
return rtn_temp
return False
def _call_sso_api(self, api_url, req_type, user=None, token=None, current_evn=None, is_file=False, **kwargs):
"""
功能模拟走sso登录模式带token访问接口
:param API_URL:
:param req_type:
:param user: 使用的自定义用户登录
:param token: 从SSO获取的token此字段使用默认值None不是的情况使用False
:param current_evn: QA or SIM
:param is_file:
:param kwargs:
:return:
"""
req_type = req_type.upper()
retry_num = 4
cnt = 0
if 'as_login_type' in kwargs.keys():
kwargs.pop('as_login_type')
options_dict = dict(**kwargs)
result = parse.urlparse(url=api_url, allow_fragments=True)
host = result.hostname
if not user:
user = self.curt_user
obj_log.info('登录系统为{},用户名为手动输入:{}'.format(host, user))
obj_log.info('请求地址:{}'.format(api_url))
obj_log.info('请求数据:{}'.format(options_dict))
header = {"Authorization": "Basic c3BhcmtsZS13ZWI6c3BhcmtsZS13ZWI=", "tenant-id": "1"}
session = requests.session()
session.headers.update(header)
while cnt < retry_num:
if token is None:
if not user:
user = evn_cfg.get_value(sections='run_user_name', options='default_user')
usr_name = base_cfg.get_value(sections=user, options='show_username')
usr_pwd = base_cfg.get_value(sections=user, options='password')
sso_url = "{}?username={}&password={}".format(self.sparkle_pc_token_url, usr_name, usr_pwd)
sso_rsp = session.post(sso_url).json()
if sso_rsp.get("code") != 200:
raise Exception("SSO登录失败: {}".format(sso_rsp))
user_token = sso_rsp.get("data").get('accessToken')
else:
user_token = token
if user_token:
session.headers.update({'ssotoken': user_token})
session.headers.update({'sso-token': user_token})
session.headers.update({'accesstoken': user_token})
session.headers.update({'Accesstoken': user_token})
session.headers.update({'access-token': user_token})
session.headers.update({'token': user_token})
obj_log.info(f'请求头headers{session.headers}')
req = self.__call_api(session, api_url, req_type, **kwargs)
if not is_file:
try:
rtn_temp = req.json()
except Exception as e:
# 如果 json() 失败,尝试将 text 转成 JSON
try:
rtn_temp = json.loads(req.text)
except Exception:
# 如果都失败,保持为 text 字符串
rtn_temp = req.text
else:
rtn_temp = req.content
# SSO登录过期处理开始
status_code = str(req.status_code)
obj_log.info('------状态码:{} 返回信息:{}'.format(status_code, rtn_temp))
try:
# 如果 rtn_temp 是字典,才能使用 .get() 方法
if isinstance(rtn_temp, dict):
resp_code = str(rtn_temp.get('code', '200'))
else:
resp_code = '200'
except:
resp_code = '200'
if not is_file:
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("--缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
if status_code == "401" or resp_code == '401':
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
if status_code == "200" and "<!DOCTYPE html>" in str(rtn_temp) and "https://sso.qa.huohua.cn" in str(
rtn_temp):
token_dict.clear() # 缓存session过期清理
student_token_cache.clear() # 缓存session过期清理
obj_log.warning("--缓存session过期清理缓存")
cnt += 1
if cnt <= 1:
continue
# SSO登录过期处理完成
req.close()
if not is_file:
obj_log.info('返回数据:{}'.format(rtn_temp))
else:
obj_log.info('返回数据:文件字节流')
return rtn_temp
return False
if __name__ == '__main__':
obj_runner = Runner()
# url = "https://api.qa.sparkedu.com/user_language/"
# url = "https://api.qa.sparkedu.com/third/student"
url = "https://api.qa.sparkedu.com/user/appoint/add"
phone = '13716640630'
# post_data = {"avatar":"https://stalegacy.huohua.cn/image/huohua/avatar/default/default_avatar1.png",
# "birthday":"2021-01-03 00:00:00",
# "id":21434807,
# "nickname":"hute222",
# "sex":0}
post_data = {"subjectType": 1}
# resp = obj_runner.call_rest_api(API_URL=url, phone=phone, req_type="POST", json=post_data)
resp = obj_runner.week
print(resp)

View File

@@ -0,0 +1,504 @@
# -*- coding: UTF-8 -*-
import os
import time
import platform
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium.webdriver.support.select import Select
from base_framework.public_tools.read_config import get_current_config
cull_path = os.path.dirname(os.path.abspath(__file__))
plugs = os.path.abspath(os.path.join(cull_path, '../{}'.format('/platform_tools/plugins/headerenv.crx')))
DEFAULT_TIMEOUT = 5
class SeleniumWebUI:
def __init__(self):
self.driver = None
self.action = None # 用于鼠标类事件
self.business = get_current_config(section="run_evn_name", key="current_business")
self.jira_id = get_current_config(section="run_jira_id", key="huohua-podenv")
self.feature = get_current_config(section="run_jira_id", key="huohua-feature")
# print(self.jira_id)
def web_open_browser(self, url="", browser_type="chrome", enable_image=True, station='web'):
"""
| 功能说明: | 打开浏览器默认chrome |
| 输入参数: | url | 网址,默认为空:仅打开浏览器 |
| | browser_type | 浏览器类型chrome,firefox,edge |
| | enable_image | 浏览器是否加载图片 |
| | station | 浏览器模式webweb模式m手机模式 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
if browser_type == "chrome":
options = webdriver.ChromeOptions()
if platform.system() == 'Linux':
default_driver_path = r'/home/chrome_home/chromedriver'
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument('--disable-gpu')
options.add_argument('--disable-dev-shm-usage')
self.driver = webdriver.Chrome(executable_path=default_driver_path, options=options)
else:
# 将正确的chrome driver版本放在python根目录下
options.add_extension(plugs)
if not enable_image:
options.add_argument('blink-settings=imagesEnabled=false')
print('当前为无图片模式')
if station == 'm':
options.add_experimental_option('mobileEmulation', {'deviceName': 'iPhone 12 Pro'})
self.driver = webdriver.Chrome(options=options)
elif browser_type == "firefox":
self.driver = webdriver.Firefox()
elif browser_type == "edge":
self.driver = webdriver.Edge()
else:
raise Exception("传入的浏览器类型不正确,正确的浏览器类型(chrome, firefox, edge)")
if len(url) > 0:
self.web_open_url(url=url)
self.action = ActionChains(self.driver)
def web_open_url(self, url):
"""
| 功能说明: | 打开链接并最大化浏览器 |
| 输入参数: | url | 访问地址 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
if not self.driver:
self.web_open_browser()
if self.jira_id.lower() not in ('', 'qa'):
# 判断并添加独立环境
self.web_set_independent_environment()
self.driver.maximize_window()
self.driver.get(url)
def web_close_browser(self):
"""
| 功能说明: | 关闭浏览器但注意并没有销毁driver对象 |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
举例说明:
"""
self.driver.quit()
def web_refresh_page(self):
"""
| 功能说明: | 刷新浏览器当前页面 |
| 输入参数: | |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
self.driver.refresh()
def web_switch_handle(self, index):
"""
| 功能说明: | 切换浏览器页面标签,适用于新开页面,或多页面操作 |
| 输入参数: | index | 标签索引 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
handles = self.driver.window_handles
self.driver.switch_to_window(handles[index])
def web_get_cookie(self):
"""
| 功能说明: | 返回浏览器当前application的cookie |
| 输入参数: | |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
return self.driver.get_cookies()
def web_find_element(self, locator, timeout=DEFAULT_TIMEOUT, raise_exception=True):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回第一个元素 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
if not isinstance(locator, tuple):
locator = (By.XPATH, locator)
element = WebDriverWait(self.driver, timeout).until(ec.presence_of_element_located(locator),
message=['The element is not exist,please check....\n'
'element xpath:{}'.format(locator)
if raise_exception else None])
return element
def web_find_elements(self, locator, timeout=DEFAULT_TIMEOUT, raise_exception=True):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回所有元素 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| | raise_exception | 当找不到元素时True-阻断报错(默认)False-返回空list |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
if not isinstance(locator, tuple):
locator = (By.XPATH, locator)
element = []
try:
element = WebDriverWait(self.driver, timeout).until(ec.presence_of_all_elements_located(locator))
except:
if raise_exception:
raise Exception("The element is not exist,please check....\n element xpath:{}".format(locator))
return element
def web_click_element(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入元素定位器,定位到该元素,普通方法点击 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
element = self.web_find_element(locator, timeout)
element.click()
def web_click_elements(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 点击所有元素 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
elements = self.web_find_elements(locator, timeout)
for element in elements:
element.click()
def web_click_element_by_js(self, locator=None, element=None, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入元素定位器定位到该元素以JS的方式点击 |
| 输入参数: | locator | 元素路径当没有element时输入 |
| | element | 元素对象当没有locator时输入 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
if not element and locator:
obj = self.web_find_element(locator, timeout)
self.driver.execute_script("arguments[0].click();", obj)
elif element and not locator:
self.driver.execute_script("arguments[0].click();", element)
else:
raise Exception('不支持的传参方式locator和element必须且只能传一个')
def web_move_element_to_top(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 将指定元素滑动到当前页面顶部 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.08.12 |
"""
element = self.web_find_element(locator, timeout)
self.driver.execute_script("arguments[0].scrollIntoView();", element)
def web_move_element_to_bottom(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 将指定元素滑动到当前页面底部 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.08.12 |
"""
element = self.web_find_element(locator, timeout)
self.driver.execute_script("arguments[0].scrollIntoView(false);", element)
def web_move_to_top(self):
"""
| 功能说明: | 滑动到当前页面顶部 |
| 作者信息: | 谢祥益 | 2022.08.12 |
"""
self.driver.execute_script("window.scrollTo(document.body.scrollHeight,0)")
def web_move_to_bottom(self):
"""
| 功能说明: | 滑动到当前页面底部 |
| 作者信息: | 谢祥益 | 2022.08.12 |
"""
self.driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")
def web_send_keys(self, locator, text, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入元素定位器定位到该元素清空输入框写入text |
| 输入参数: | locator | 元素路径 |
| | text | 输入内容 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
element = self.web_find_element(locator, timeout)
element.send_keys(Keys.CONTROL, 'a')
element.send_keys(text)
def web_get_element_text(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回该元素的文本值 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
element = self.web_find_element(locator, timeout)
return element.text
def web_click_single_box(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入单选框元素定位器,定位到该元素,依次点击单选框 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
elements = self.web_find_elements(locator, timeout)
for element in elements:
self.driver.execute_script("arguments[0].click();", element)
time.sleep(1.5)
def web_select_drop_down_box(self, locator, index=0, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入下拉框定位器定位到该元素选择下拉框的第index个选项 |
| 输入参数: | locator | 元素路径 |
| | index | 第几个元素的下标 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
select = Select(self.web_find_element(locator, timeout))
select.select_by_index(index)
def web_click_checkbox(self, locator, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入多选框元素定位器,定位到该元素,全选 |
| 输入参数: | locator | 元素路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
checkbox = self.web_find_elements(locator, timeout)
for i in checkbox:
if not i.is_selected():
i.click()
def web_upload_file(self, locator, path, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 传入元素定位器定位到该元素传入文件路径只适用于input标签 |
| 输入参数: | locator | 元素路径 |
| | path | 文件完整路径 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
element = self.web_find_element(locator, timeout)
element.send_keys(path)
def web_move_windows(self, x=0, y=0):
"""
| 功能说明: | 传滑动窗口 |
| 输入参数: | x | x轴距离 |
| | y | y轴距离 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
self.driver.execute_script("window.scrollBy({},{})".format(x, y))
def web_go_to_previous_page(self):
"""
| 功能说明: | 返回上一页(浏览器工具栏向左箭头) |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
self.driver.back()
def web_go_to_next_page(self):
"""
| 功能说明: | 前进一页(浏览器工具栏向右箭头) |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
self.driver.forward()
def web_close_current_page(self):
"""
| 功能说明: | 关闭当前窗口页面 |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
self.driver.close()
def web_get_window_title(self):
"""
| 功能说明: | 获取当前浏览器标题 |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
return self.driver.title
def web_get_element_attribute_value(self, locator, attr: str, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 获取元素属性值 |
| 输入参数: | locator | 元素路径 |
| | attr | 属性名 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
obj = self.web_find_element(locator, timeout=timeout)
attr_value = obj.get_attribute(attr)
return attr_value
def web_modify_tag_attribution(self, locator, attr, value, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 修改页签属性 |
| 输入参数: | locator | 元素路径 |
| | attr | 修改属性名 |
| | value | 修改后的值 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
obj = self.web_find_element(locator, timeout)
self.driver.execute_script("arguments[0].{attr}={value};".format(attr=attr, value=value), obj)
def web_input_to_readonly_tag(self, locator, text, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 带只读属性的标签输入 |
| 输入参数: | locator | 元素路径 |
| | text | 输入内容 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
obj = self.web_find_element(locator, timeout)
self.driver.execute_script("arguments[0].removeAttribute('readonly');", obj)
self.web_send_keys(locator=locator, text=text)
def web_get_css_property_value(self, locator, css_property, timeout=DEFAULT_TIMEOUT):
"""
| 功能说明: | 获取css属性值 |
| 输入参数: | locator | 元素路径 |
| | text | 输入内容 |
| | timeout | 超时时间默认10秒 |
| 作者信息: | 谢祥益 | 2022.07.08 |
"""
obj = self.web_find_element(locator, timeout)
value = obj.value_of_css_property(css_property)
return value
def web_take_screenshot(self, save_path, picture_name):
"""
| 功能说明: | 截屏浏览器当前页面 |
| 输入参数: | save_path | 保存地址 |
| | picture_name | 图片名称 |
| 作者信息: | 谢祥益 | 2022.06.01 |
"""
picture_path = os.path.join(save_path, picture_name + '.png')
self.driver.get_screenshot_as_file(picture_path)
def web_set_independent_environment(self):
"""
| 功能说明: | 设置独立环境 |
| 输入参数: | 无 |
| 作者信息: | 谢祥益 at 2022.06.01 |
"""
# 新增独立环境
new_loc = ('xpath', '/html/body/div/div[3]/button/div/div/i')
# 独立环境名称
rule_name_loc = ('id', 'rule-name')
# 规则类型
rule_type_loc = ('xpath', '//*[@id="edit-page"]/div[2]/div[1]/div/div[2]/div[2]/div[2]/div[3]/div')
# 头名称
rule_header_name_loc = ('id', 'rule-headerName')
# 头内容
rule_header_value_loc = ('id', 'rule-headerValue')
# 保存
create_loc = ('xpath', '//*[@id="edit-page"]/div[2]/div[2]/div[2]/div[2]/button/div/div')
self.driver.get("chrome-extension://eningockdidmgiojffjmkdblpjocbhgh/options/options.html")
if self.jira_id and self.jira_id.lower()!='qa':
rule_header_name = 'huohua-podenv'
rule_header_value = self.jira_id
self.web_click_element(new_loc)
self.web_send_keys(rule_name_loc, self.jira_id)
self.web_click_element(rule_type_loc)
self.web_send_keys(rule_header_name_loc, rule_header_name)
self.web_send_keys(rule_header_value_loc, rule_header_value)
self.web_click_element(create_loc)
rule_header_name = 'huohua-feature'
rule_header_value = self.feature
self.web_click_element(new_loc)
self.web_send_keys(rule_name_loc, "front")
self.web_click_element(rule_type_loc)
self.web_send_keys(rule_header_name_loc, rule_header_name)
self.web_send_keys(rule_header_value_loc, rule_header_value)
self.web_click_element(create_loc)
else:
print("无独立环境!")
def web_mouse_move_to_element(self, locator):
"""
| 功能 | 移动鼠标到元素位置 |
| 入参 | locator | 元素xpath路径 |
| 说明 | 多次刷新页面 | 容易导致此方法失效,慎用... |
| | 必要时 | 可以尝试直接点击对应控件:web_click_element |
"""
self.action.reset_actions()
obj = self.web_find_element(locator=locator)
self.action.move_to_element(obj)
self.action.perform()
def web_mouse_click(self):
"""功能:模拟鼠标点击"""
self.action.click().perform()
# self.action.perform()
def web_check_element_exist(self, locator, quantity=1):
"""
| 功能说明: | 检查页面元素是否存在0-不存在1-存在,>1-存在多少个,-1-仅判断存在不判断数量 |
| 输入参数: | locator | 元素路径 |
| | quantity | 对应locator的元素有多少个, 传入-1则仅检查存在不判断数量 |
| 作者信息: | 吴勇刚 | 2022.08.01 |
"""
if int(quantity) == 0: # 当检查元素不存在时设计设置为2秒
time_out = 2
else:
time_out = DEFAULT_TIMEOUT
elements = self.web_find_elements(locator=locator, raise_exception=False, timeout=time_out)
if quantity != -1:
if len(elements) != int(quantity):
raise Exception("the element [{}] expect exist {},but current is {}..."
.format(locator, quantity, len(elements)))
else:
if len(elements) == 0:
raise Exception("the element is not exist{}...".format(locator))
def web_check_element_text(self, locator, exp_text):
"""
| 功能说明: | 检查源文本是否正确 | 转为字符串比较 |
| 输入参数: | locator | 元素路径 |
| | exp_text | 元素预期文本 |
| 作者信息: | 吴勇刚 | 2022.08.01 |
"""
act_text = self.web_get_element_text(locator=locator)
if act_text != str(exp_text):
raise Exception("The element [{}]'s text expect [{}],bug now it's [{}] "
.format(locator, exp_text, act_text))
def web_check_element_attribute(self, locator, attr_name, exp_value):
"""
| 功能说明: | 检查元素属性值是否正确 |
| 输入参数: | locator | 元素路径 |
| | attr_name | 元素属性名称 |
| | exp_value | 元素预期属性值 |
| 作者信息: | 吴勇刚 | 2022.08.01 |
"""
act_value = self.web_get_element_attribute_value(locator=locator, attr=attr_name)
if act_value != exp_value:
raise Exception("The element [{}] attribute [{}]'s value expect [{}],bug now it's [{}] "
.format(locator, attr_name, exp_value, act_value))
def web_get_current_url(self):
"""
| 功能说明: | 检查源文本是否正确 | 转为字符串比较 |
| 输入参数: | locator | 元素路径 |
| | exp_text | 元素预期文本 |
| 作者信息: | 吴勇刚 | 2022.08.01 |
"""
current_url = self.driver.current_url
return current_url
if __name__ == '__main__':
web_driver = SeleniumWebUI()
vispark_url = "https://sparkle.qa.huohua.cn/intl/schedule"
web_driver.web_open_url(url=vispark_url)
url = web_driver.web_get_current_url()
print(url)
web_driver.web_close_browser()

View File

@@ -0,0 +1,727 @@
# -*- coding:utf-8 -*-
"""
Author: qiaoxinjiu
Create Data: 2020/11/6 17:30
"""
import time
import re
from retrying import retry
from base_framework.public_tools import log
from base_framework.public_tools.db_dbutils_init import get_my_connection,get_pg_connection
from base_framework.public_tools.read_config import get_current_config, get_current_env
from base_framework.public_tools.huohua_dbs import HuoHuaDBS
obj_log = log.get_logger()
"""执行语句查询有结果返回结果没有返回0增/删/改返回变更数据条数没有返回0"""
# retry 参数说明
"""
stop_max_attempt_number 指定重试的次数默认是5
stop_max_delay 指定重试超时时间默认是100单位是ms
wait_fixed 指定每次重试的时间间隔默认是1000单位是ms
wait_random_min=None 指定每次重试最小时间默认为0
wait_random_max=None, 指定每次重试最大时间默认为1000
wait_incrementing_start 指定每次重试递增的开始时间
wait_incrementing_increment 指定每次重试时间的递增幅度
wait_exponential_multiplier 指定每次重试时间的递增幅度,跟上面的参数算法差异
wait_exponential_max 指定每次重试时间的递增幅度的最大值
retry_on_exception 指定每次出现异常执行的函数
retry_on_result 指定程序运行正常执行的函数
wrap_exception 出现异常后返回的异常类型True是返回原异常False返回RetryError
stop_func 自定义停止重试的条件,传入参数是一个函数
wait_func 自定义每次重试的时间间隔,传入参数是一个函数
wait_jitter_max 指定每次重试时间抖动值
stop 指定重试停止后执行Retrying对象的成员函数
wait 指定重试间隔执行Retrying对象的成员函数
"""
class MySqLHelper:
"""
mysql数据库操作
"""
def __init__(self):
self.db = get_my_connection() # 从数据池中获取连接
self.current_business = get_current_config(section='run_evn_name', key='current_business')
self.current_evn = get_current_env()
self.qa_db_to_sim_instance_name = {}
self.sim_dbs = HuoHuaDBS()
# def __new__(cls, *args, **kwargs):
# if not hasattr(cls, 'inst'): # 单例
# cls.inst = super(MySqLHelper, cls).__new__(cls, *args, **kwargs)
# return cls.inst
# 封装执行命令
def execute(self, sql, param=None, auto_close=False, choose_db=None):
"""
| 功能说明: | 执行具体的sql语句 |
| 输入参数: | sql | 待执行的sql语句 |
| | param=None | sql语句中where后跟的参数也可直接写在sql语句中 |
| | auto_close=True | 是否自动关闭数据库连接,默认:自动关闭 |
| 返回参数: | conncursorcount | 连接,游标,行数 |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
if self.current_evn.lower() == "sim":
raise Exception("SIM环境请直接使用huohua_dbs.py中的函数")
# if 'sparkatp' in sql or 'push_service' in sql : # 自动化和信息化的数据都走huohua
# choose_db = 'hh.qa'
# if "account." in sql or "emp." in sql or "sso." in sql:
# choose_db = 'hh.qa'
cursor, conn = self.db.getconn(choose_db=choose_db) # 从连接池获取连接
try:
# count : 为改变的数据条数
if param:
count = cursor.execute(sql, param)
else:
count = cursor.execute(sql)
conn.commit()
if auto_close:
self.close(cursor, conn)
except Exception as e:
obj_log.error(e)
raise ValueError("数据库操作失败SQL语句{}".format(sql))
return cursor, conn, count
# 释放连接
@staticmethod
def close(cursor, conn):
"""
| 功能说明: | 关闭数据库连接 |
| 输入参数: | cursor | 游标 |
| | conn | 连接 |
| 返回参数: | 无 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
函数位置Public/Common/mysql_api.py
"""
cursor.close()
conn.close()
# 查询所有
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def select_all(self, sql, param=None, choose_db=None, show_log=True):
"""
| 功能说明: | 查询数据库 | 并返回所有结果 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | 查询语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | 所有查询结果 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
函数位置Public/Common/mysql_api.py
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_select(sql_content=sql)
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
if show_log:
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
res = cursor.fetchall()
if show_log:
obj_log.info('数据库查询结果:{}'.format(res))
return res
except Exception as e:
self.close(cursor, conn)
raise RuntimeError(e.args)
def select_all_as_list(self, sql, choose_db=None):
"""
| 功能说明: | 查询数据库 | 功能同select_all,仅返回结果为list |
| 输入参数: | sql | 待执行的查询语句 |
| 返回参数: | 所有查询结果类型list | |
| 作者信息: | 吴勇刚 | 2021/11/22 |
函数位置Public/Common/mysql_api.py
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_select(sql_content=sql, r_type='list')
try:
cursor, conn, count = self.execute(sql, choose_db=choose_db)
res = cursor.fetchall()
has_data = False # 以下代码是防止返回空值列表添加的
for item in res:
for key in item:
if item[key]:
has_data = True # 有一个值非空即可
break
if not has_data:
return [] # 以上代码是防止返回空值列表添加的
res_list = []
for index in range(len(res)):
if len(res[index]) > 1:
res_row = []
for key in res[index]:
res_row.append(res[index][key])
res_list.append(res_row)
else:
for key in res[index]:
res_list.append(res[index][key])
return res_list
except Exception as e:
self.close(cursor, conn)
raise RuntimeError(e.args)
# 查询单条
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def select_one(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 查询数据库,并返第一行 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | 查询语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | 返回第一条查询结果 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
函数位置Public/Common/mysql_api.py
"""
if self.current_evn.lower() == "sim":
if 'limit' not in sql.lower():
sql = sql.split(';')[0] + ' limit 1;'
sim_data = self.sim_dbs.dbs_select(sql_content=sql)
if sim_data:
return sim_data[0]
else:
return {}
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
res = cursor.fetchone()
return res
except Exception as e:
self.close(cursor, conn)
raise RuntimeError(e.args)
# 增加
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def insert_one(self, sql, param=None, choose_db=None, log_level='info'):
"""
| 功能说明: | insert一行数据 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数也可直接写入sql语句中 |
| | log_level='info' | 日志级别info-全量日志,否则,仅输入报错日志 |
| 返回参数: | 无 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if log_level.lower() == 'info':
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
# _id = cursor.lastrowid() # 获取当前插入数据的主键id该id应该为自动生成为好
conn.commit()
self.close(cursor, conn)
if log_level.lower() == 'info':
obj_log.info('插入数据库条数:{}'.format(count))
return count
# 防止表中没有id返回0
# if _id == 0:
# return True
# return _id
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
# 插入后返回插入ID
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def insert_many_extension(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert一行数据并返回插入行的主键ID |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | insert_count | 插入的条数 |
| last_id | 插入最近一条的ID |
| insert_id | 插入的第一条数据的主键ID参数param的的第一条 |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
return_dict = dict()
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = self.db.getconn(choose_db=choose_db)
try:
count = cursor.executemany(sql, eval(str(param)))
# last_id = cursor.lastrowid # 获取最新插入数据的主键id
insert_id = cursor._result.insert_id # 获取本次插入数据的主键id
# insert_id_list = list(range(insert_id, insert_id+count)) # 获取最新插入数据的主键id_list
conn.commit()
self.close(cursor, conn)
obj_log.info('插入数据库条数:{}'.format(count))
return_dict['insert_count'] = count
# return_dict['last_id'] = last_id
return_dict['insert_id'] = insert_id
return return_dict
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
# 插入后返回插入ID
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def insert_one_extension(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert一行数据并返回插入行的ID |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | insert_count | 插入的条数 |
| last_id | 插入最近一条的ID |
| insert_id | 插入数据的ID |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
return_dict = dict()
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
# last_id = cursor.lastrowid # 获取最新插入数据的主键id
insert_id = cursor._result.insert_id # 获取本次插入数据的主键id
# insert_id_list = list(range(insert_id, insert_id+count)) # 获取最新插入数据的主键id_list
conn.commit()
self.close(cursor, conn)
obj_log.info('插入数据库条数:{}'.format(count))
return_dict['insert_count'] = count
# return_dict['last_id'] = last_id
return_dict['insert_id'] = insert_id
return return_dict
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
# 增加多行
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def insert_many(self, sql, param=None, choose_db=None):
"""
| 功能说明: | insert多行数据 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数必须是元组或列表[(),()]或((),()) |
| 返回参数: | 无 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
cursor, conn = self.db.getconn(choose_db=choose_db)
try:
count = cursor.executemany(sql, eval(str(param)))
conn.commit()
self.close(cursor, conn)
obj_log.info('插入数据库条数:{}'.format(count))
return count
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
# 删除
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def delete(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 删除数据库记录 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | 无 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
self.close(cursor, conn)
obj_log.info('删除数据库条数:{}'.format(count))
return count
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
# 更新
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def update(self, sql, param=None, choose_db=None):
"""
| 功能说明: | 更新数据库记录 |
| 输入参数: | sql | 待执行的查询语句 |
| | param=None | sql语句中where条件后的参数也可直接写入sql语句中 |
| 返回参数: | 无 | |
| 作者信息: | 林于棚 | 2020/11/26 21:11 |
| 函数位置: | Public/Common/mysql_api.py ||
"""
if self.current_evn.lower() == "sim":
return self.sim_dbs.dbs_execute_sql(sql_content=sql)
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, count = self.execute(sql, param, choose_db=choose_db)
conn.commit()
self.close(cursor, conn)
obj_log.info('更新数据库条数:{}'.format(count))
return count
except Exception as e:
conn.rollback()
self.close(cursor, conn)
raise RuntimeError(e.args)
def check_result_exist(self, *select_statement, retry_count=3):
"""
| 功能说明: | 验证select语句查询的结果存在 |
| 输入参数: | select_statement | select查询语句可以多个 |
| | retry_count | 重试次数默认3次 |
| 返回参数: | 无 | 结果存在则pass否则failed |
| 作者信息: | 吴勇刚 | 2020.12.16 |
函数位置Public/Common/mysql_api.py
举例说明:
| check_result_exist | [sql1sql2] |
"""
# retry_count = 3
start = 0
flag = 0
while start <= retry_count:
for sql in select_statement:
res = self.select_all(sql=sql)
if not res:
if start == retry_count:
obj_log.info('the result is not exist,but expect at least one')
raise RuntimeError(u'No results, when exec sql: %s' % sql)
else:
obj_log.info('the result is not exist,retry {}'.format(start+1))
start += 1
time.sleep(1)
else:
obj_log.info('find [{0}] results when exec {1}'.format(len(res), sql))
flag += 1
break
if flag > 0:
break
def check_result_not_exist(self, *select_statement):
"""
| 功能说明: | 验证select语句查询的结果不存在 |
| 输入参数: | select_statement | select查询语句列表 |
| 返回参数: | 无 | 结果不存在则pass否则failed |
| 作者信息: | 吴勇刚 | 2020.12.16 |
函数位置Public/Common/mysql_api.py
举例说明:
| check_result_not_exist | [sql1sql2] |
"""
for sql in select_statement:
res = self.select_all(sql=sql)
if res:
obj_log.info('find [{0}] results, but expect 0'.format(len(res)))
raise RuntimeError(u'the result exist, when exec sql: %s' % sql)
else:
obj_log.info('the result is not exist, this step pass...')
def row_count(self, selectStatement, param=None):
"""
Uses the input `selectStatement` to query the database and returns the number of rows from the query.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
| 2 | Jerry | Schneider |
When you do the following:
| ${rowCount} | Row Count | SELECT * FROM person |
| Log | ${rowCount} |
You will get the following:
2
Also, you can do something like this:
| ${rowCount} | Row Count | SELECT * FROM person WHERE id = 2 |
| Log | ${rowCount} |
And get the following
1
"""
try:
cursor, conn, count = self.execute(selectStatement, param)
self.close(cursor, conn)
return count
except Exception as e:
print("error_msg:", e.args)
self.close(cursor, conn)
# 数据库断言
def kw_check_if_exists_in_database(self, selectStatement, choose_db=None):
"""
Check if any row would be returned by given the input `selectStatement`. If there are no results, then this will
throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit transaction
commit or rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
When you have the following assertions in your robot
| Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' |
| Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' |
Then you will get the following:
| Check If Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | # PASS |
| Check If Exists In Database | SELECT id FROM person WHERE first_name = 'John' | # FAIL |
"""
obj_log.info(
'Executing : Check If Exists In Database | %s ' %
selectStatement)
if not self.select_one(selectStatement, choose_db=choose_db):
raise AssertionError("Expected to have have at least one row from '%s' "
"but got 0 rows." % selectStatement)
else:
return True
def kw_check_if_not_exists_in_database(self, selectStatement, choose_db=None):
"""
This is the negation of `check_if_exists_in_database`.
Check if no rows would be returned by given the input `selectStatement`. If there are any results, then this
will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
transaction commit or rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
When you have the following assertions in your robot
| Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' |
| Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' |
Then you will get the following:
| Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'John' | # PASS |
| Check If Not Exists In Database | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL |
"""
obj_log.info(
'Executing : Check If Not Exists In Database | %s ' %
selectStatement)
queryResults = self.select_one(selectStatement, choose_db=choose_db)
if queryResults:
raise AssertionError("Expected to have have no rows from '%s' "
"but got some rows : %s." % (selectStatement, queryResults))
else:
return True
def kw_row_count_is_0(self, selectStatement):
"""
Check if any rows are returned from the submitted `selectStatement`. If there are, then this will throw an
AssertionError. Set optional input `sansTran` to True to run command without an explicit transaction commit or
rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
When you have the following assertions in your robot
| Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' |
| Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' |
Then you will get the following:
| Row Count is 0 | SELECT id FROM person WHERE first_name = 'Franz Allan' | # FAIL |
| Row Count is 0 | SELECT id FROM person WHERE first_name = 'John' | # PASS |
"""
obj_log.info('Executing : Row Count Is 0 | %s ' % selectStatement)
num_rows = self.row_count(selectStatement)
if num_rows > 0:
raise AssertionError("Expected zero rows to be returned from '%s' "
"but got rows back. Number of rows returned was %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_equal_to_x(self, selectStatement, numRows):
"""
Check if the number of rows returned from `selectStatement` is equal to the value submitted. If not, then this
will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
transaction commit or rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
| 2 | Jerry | Schneider |
When you have the following assertions in your robot
| Row Count Is Equal To X | SELECT id FROM person | 1 |
| Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 |
Then you will get the following:
| Row Count Is Equal To X | SELECT id FROM person | 1 | # FAIL |
| Row Count Is Equal To X | SELECT id FROM person WHERE first_name = 'John' | 0 | # PASS |
"""
obj_log.info(
'Executing : Row Count Is Equal To X | %s | %s ' %
(selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows != int(str(numRows).encode('ascii')):
raise AssertionError("Expected same number of rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_greater_than_x(self, selectStatement, numRows):
"""
Check if the number of rows returned from `selectStatement` is greater than the value submitted. If not, then
this will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
transaction commit or rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
| 2 | Jerry | Schneider |
When you have the following assertions in your robot
| Row Count Is Greater Than X | SELECT id FROM person | 1 |
| Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 |
Then you will get the following:
| Row Count Is Greater Than X | SELECT id FROM person | 1 | # PASS |
| Row Count Is Greater Than X | SELECT id FROM person WHERE first_name = 'John' | 0 | # FAIL |
"""
obj_log.info(
'Executing : Row Count Is Greater Than X | %s | %s ' %
(selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows <= int(numRows.encode('ascii')):
raise AssertionError("Expected more rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_row_count_is_less_than_x(self, selectStatement, numRows):
"""
Check if the number of rows returned from `selectStatement` is less than the value submitted. If not, then this
will throw an AssertionError. Set optional input `sansTran` to True to run command without an explicit
transaction commit or rollback.
For example, given we have a table `person` with the following data:
| id | first_name | last_name |
| 1 | Franz Allan | See |
| 2 | Jerry | Schneider |
When you have the following assertions in your robot
| Row Count Is Less Than X | SELECT id FROM person | 3 |
| Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 |
Then you will get the following:
| Row Count Is Less Than X | SELECT id FROM person | 3 | # PASS |
| Row Count Is Less Than X | SELECT id FROM person WHERE first_name = 'John' | 1 | # FAIL |
"""
obj_log.info(
'Executing : Row Count Is Less Than X | %s | %s ' %
(selectStatement, numRows))
num_rows = self.row_count(selectStatement)
if num_rows >= int(numRows.encode('ascii')):
raise AssertionError("Expected less rows to be returned from '%s' "
"than the returned rows of %s" % (selectStatement, num_rows))
else:
return True
def kw_table_must_exist(self, tableName):
"""
Check if the table given exists in the database. Set optional input `sansTran` to True to run command without an
explicit transaction commit or rollback.
For example, given we have a table `person` in a database
When you do the following:
| Table Must Exist | person |
Then you will get the following:
| Table Must Exist | person | # PASS |
| Table Must Exist | first_name | # FAIL |
"""
obj_log.info('Executing : Table Must Exist | %s ' % tableName)
if self.db_api_module_name in ["cx_Oracle"]:
selectStatement = (
"SELECT * FROM all_objects WHERE object_type IN ('TABLE','VIEW') AND owner = SYS_CONTEXT('USERENV', 'SESSION_USER') AND object_name = UPPER('%s')" %
tableName)
elif self.db_api_module_name in ["sqlite3"]:
selectStatement = (
"SELECT name FROM sqlite_master WHERE type='table' AND name='%s' COLLATE NOCASE" %
tableName)
elif self.db_api_module_name in ["ibm_db", "ibm_db_dbi"]:
selectStatement = (
"SELECT name FROM SYSIBM.SYSTABLES WHERE type='T' AND name=UPPER('%s')" %
tableName)
else:
selectStatement = (
"SELECT * FROM information_schema.tables WHERE table_name='%s'" %
tableName)
num_rows = self.row_count(selectStatement)
if num_rows == 0:
raise AssertionError(
"Table '%s' does not exist in the db" %
tableName)
# @retry(stop_max_attempt_number=5, wait_fixed=3000)
def select_all_as_generator(self, sql, param=None, choose_db=None):
if param is not None:
obj_log.info('SQL语句{}|{}'.format(sql, param))
else:
obj_log.info('SQL语句{}'.format(sql))
try:
cursor, conn, counts = self.execute(sql, param, choose_db=choose_db)
for count in range(counts):
item = cursor.fetchone()
obj_log.info(item)
yield item
except Exception as e:
self.close(cursor, conn)
raise RuntimeError(e.args)
def get_qa_to_sim_dbs(self):
get_sql = "select qa_db,sim_instance from sparkatp.qa_db_mapping_sim_dbs where status=1 and is_delete=0"
res_info = self.sim_dbs.dbs_query_by_db_name("qadb-slave", "sparkatp", get_sql, limit_num=1000)
print(res_info)
for item in res_info:
self.qa_db_to_sim_instance_name.update({item[0]: item[1]})
return self.qa_db_to_sim_instance_name
def get_instance_name(self, sql_str):
db_name = sql_str.split(".")[0].split(" ")[-1].replace("`", "").replace(" ", "")
return self.qa_db_to_sim_instance_name.get(db_name)
if __name__ == '__main__':
db = MySqLHelper()
# sql = "select id,name,code from peppa.grade_group where course_level in (0, 1, 2, 3) order by id asc;"
# sql = " DELETE FROM classes.timetable_plan_teacher WHERE teacher_id in (SELECT id FROM `teach_teacher`.`teacher_profile` WHERE name='666000排课' AND nickname='666000排课');"
# print("【原始sql】{}".format(sql))
# res = db.update_db_name(sql=sql)
# print(res)
sql = "SELECT * FROM account.account;"
res = db.select_one(sql=sql)
print(res)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,283 @@
# -*- coding: UTF-8 -*-
import os
import time
import platform
from seleniumwire import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.select import Select
from base_framework.public_tools.read_config import get_current_config, ReadConfig, InitConfig
from base_framework.base_config.current_pth import env_choose_path
from copy import copy, deepcopy
from selenium.webdriver.common.action_chains import ActionChains
cull_path = os.path.dirname(os.path.abspath(__file__))
plugs = os.path.abspath(os.path.join(cull_path, '../{}'.format('/platform_tools/plugins/headerenv.crx')))
cc_driver = os.path.abspath(os.path.join(cull_path, '../{}'.format('/public_business/CC/webdriver/chromedriver.exe')))
DEFAULT_TIMEOUT = 10
class DriverInit(InitConfig):
def __init__(self, driver_path=None):
# super(DriverInit, self).__init__()
InitConfig.__init__(self, run_user_name=None, current_evn=None)
self.driver_path = cc_driver if not driver_path else driver_path
self.config = ReadConfig(env_choose_path)
self.jira_id = ReadConfig(env_choose_path).get_value('run_jira_id', 'huohua-podenv')
self.option = webdriver.ChromeOptions()
self.option.add_argument('--no-sandbox')
self.option.add_argument('--disable-gpu')
self.option.add_argument('--disable-dev-shm-usage')
# self.option.headless = True
self.option.add_extension(plugs)
# self._init_driver()
def _init_driver(self):
self.driver = webdriver.Chrome(executable_path=self.driver_path, options=self.option)
self.driver.scopes = [
'.*huohua.cn.*', '.*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+.*', '.*allschool.com', '.*sparkedu.com'
]
self.driver.implicitly_wait(30)
self.driver.maximize_window()
self.actions = ActionChains(self.driver)
self.wait = WebDriverWait(self.driver, timeout=60)
if self.jira_id:
self._set_independent_environment()
def _set_independent_environment(self):
backend = self.jira_id
frontend = 'feature/%s' % self.jira_id
new_loc = ('xpath', '/html/body/div/div[3]/button/div/div/i')
# 独立环境名称
rule_name_loc = ('id', 'rule-name')
# 规则类型
rule_type_loc = ('xpath', '//*[@id="edit-page"]/div[2]/div[1]/div/div[2]/div[2]/div[2]/div[3]/div')
# 头名称
rule_headerName_loc = ('id', 'rule-headerName')
# 头内容
rule_headerValue_loc = ('id', 'rule-headerValue')
# 保存
create_loc = ('xpath', '//*[@id="edit-page"]/div[2]/div[2]/div[2]/div[2]/button/div/div')
# js = 'window.open("chrome-extension://eningockdidmgiojffjmkdblpjocbhgh/options/options.html");'
# self.driver.execute_script(js)
self.driver.get("chrome-extension://eningockdidmgiojffjmkdblpjocbhgh/options/options.html")
rule_headerName = 'huohua-podenv'
rule_headerValue = backend
self.clickElement(new_loc)
self.sendKeysElement(rule_name_loc, "back")
self.clickElement(rule_type_loc)
self.sendKeysElement(rule_headerName_loc, rule_headerName)
self.sendKeysElement(rule_headerValue_loc, rule_headerValue)
self.clickElement(create_loc)
rule_headerName = 'huohua-feature'
rule_headerValue = frontend
self.clickElement(new_loc)
self.sendKeysElement(rule_name_loc, "front")
self.clickElement(rule_type_loc)
self.sendKeysElement(rule_headerName_loc, rule_headerName)
self.sendKeysElement(rule_headerValue_loc, rule_headerValue)
self.clickElement(create_loc)
def waitEleDisappear(self, locator, timeout=10):
count = 0
while count <= timeout:
try:
self.wait.until(ec.invisibility_of_element_located(locator=locator))
break
except Exception as error:
time.sleep(1)
count += 1
def elementScroolToView(self, locator):
ele = self.findElement(locator=locator, timeout=10)
self.driver.execute_script("arguments[0].focus()", ele)
self.driver.execute_script("arguments[0].scrollIntoView();", ele)
def findElement(self, locator, timeout=10):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回第一个元素|
| 传入参数: | locator |
举例说明:
"""
element = WebDriverWait(self.driver, timeout).until(ec.presence_of_element_located(locator))
return element
def findElements(self, locator, timeout=10):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回所有元素|
| 传入参数: | locator |
举例说明:
"""
element = WebDriverWait(self.driver, timeout).until(ec.presence_of_all_elements_located(locator))
return element
def clickElement(self, locator, timeout=10):
"""
| 功能说明: | 传入元素定位器,定位到该元素,普通方法点击|
| 传入参数: | locator |
举例说明:
"""
element = self.findElement(locator, timeout)
element.click()
def clickElement_by_JS(self, locator=None, element=None, timeout=10):
"""
| 功能说明: | 传入元素定位器定位到该元素以JS的方式点击|
| 传入参数: | locator | 元素定位器 |
| element | 页面对象 |
举例说明:
"""
if not element and locator:
obj = self.findElement(locator, timeout)
self.driver.execute_script("arguments[0].click();", obj)
elif element and not locator:
self.driver.execute_script("arguments[0].click();", element)
else:
raise Exception('不支持的传参方式locator和element必须且只能传一个')
def sendKeysElement(self, locator, text, timeout=10):
"""
| 功能说明: | 传入元素定位器定位到该元素清空输入框写入text|
| 传入参数: | locatortext |
举例说明:
"""
element = self.findElement(locator, timeout)
element.send_keys(Keys.CONTROL, 'a')
element.send_keys(text)
def getElementText(self, locator, timeout=10):
"""
| 功能说明: | 传入元素定位器,定位到该元素,返回该元素的文本值|
| 传入参数: | locator |
举例说明:
"""
element = self.findElement(locator, timeout)
return element.text
def clickSingleBox(self, locator, timeout=10):
"""
| 功能说明: | 传入单选框元素定位器,定位到该元素,依次点击单选框|
| 传入参数: | locator |
举例说明:
"""
elements = self.findElements(locator, timeout)
for element in elements:
self.driver.execute_script("arguments[0].click();", element)
time.sleep(1.5)
def selectDropDownBox(self, locator, index=0, timeout=10):
"""
| 功能说明: | 传入下拉框定位器定位到该元素选择下拉框的第index个选项|
| 传入参数: | locatorindex |
举例说明:
"""
select = Select(self.findElement(locator, timeout))
select.select_by_index(index)
# 选择多选框-->全选
def clickCheckbox(self, locator, timeout=10):
"""
| 功能说明: | 传入多选框元素定位器,定位到该元素,全选|
| 传入参数: | locator |
举例说明:
"""
checkbox = self.findElements(locator, timeout)
for i in checkbox:
if not i.is_selected():
i.click()
def uploadFile(self, locator, path, timeout=10):
"""
| 功能说明: | 传入元素定位器定位到该元素传入文件路径只适用于input标签|
| 传入参数: | locator |
举例说明:
"""
element = self.findElement(locator, timeout)
element.send_keys(path)
def roll_windows(self, x=0, y=0):
"""
滑动窗口
:param x: x轴距离
:param y: y轴距离
:return: 无
"""
self.driver.execute_script("window.scrollBy({},{})".format(x, y))
def back_window(self):
"""
返回上一页(浏览器工具栏向左箭头)
:return: 无
"""
self.driver.back()
def forward_window(self):
"""
前进一页(浏览器工具栏向右箭头)
:return: 无
"""
self.driver.forward()
def close_current_window(self):
"""
关闭当前窗口
:return: 无
"""
self.driver.close()
def get_window_title(self):
"""
关闭当前窗口
:return: 无
"""
current_title = self.driver.title
return current_title
def get_element_attribute_value(self, locator, attr: str, timeout=10):
"""
关闭当前窗口
:param locator 定位器
:param attr 属性名
:param timeout 元素查找超时时间
:return: 无
"""
obj = self.findElement(locator, timeout=timeout)
attr_value = obj.get_attribute(attr)
return attr_value
def modify_tag_attribution(self, locator, attr, value, timeout=10):
"""
修改页签属性
:param locator: 需要修改的元素定位器
:param attr: 修改属性名
:param value: 修改后的值
:param timeout: 超时时间
:return:
"""
obj = self.findElement(locator, timeout)
self.driver.execute_script("arguments[0].{attr}={value};".format(attr=attr, value=value), obj)
def input_to_readonly_tag(self, locator, text, timeout=10):
"""
带只读属性的标签输入
:param locator: 元素定位器
:param text: 输入内容
:param timeout: 超时时间
:return:无
"""
obj = self.findElement(locator, timeout)
self.driver.execute_script("arguments[0].removeAttribute('readonly');", obj)
self.sendKeysElement(locator=locator, text=text)
def takeScreenshot(self, savePath, pictureName):
"""
| 功能说明: | 截取浏览器当前页面|
| 传入参数: | savePath保存地址 |
| | pictureName图片保存名称
举例说明:
"""
picturePath = os.path.join(savePath, pictureName + '.png')
self.driver.get_screenshot_as_file(picturePath)

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author : qiaoxinjiu
# @Time : 2021/07/13
# @File : websocket_api.py
import time
import json
from functools import wraps
import requests
from websocket import create_connection
from base_framework.public_tools.log import get_logger
from base_framework.public_tools.sqlhelper import MySqLHelper
from base_framework.public_tools.read_config import InitConfig
def check_conn(func):
@wraps(func)
def wrap_check_conn(self, *args, **kwargs):
self.ws = self.get_ws_conn()
self.logger.info('%s connected websocket server' % func.__name__)
return func(self, *args, **kwargs)
return wrap_check_conn
class WebSocketAPI(InitConfig):
def __init__(self, user_name=None, env_name=None, timeout=30):
# super().__init__(run_user_name=user_name, current_evn=env_name)
InitConfig.__init__(self, run_user_name=user_name, current_evn=env_name)
self.sso_url = self.all_cfg[self.current_evn]['sso_url']
self.code_url = self.all_cfg[self.current_evn]['code_url']
self.token_url = self.all_cfg[self.current_evn]['token_url']
self.redirect_url = self.all_cfg[self.current_evn]['teach_opt_url']
self.auth_code = self.all_cfg['Authorization']['sparkle-manage']
self.logger = get_logger()
self.db_conn = MySqLHelper()
def get_session(self):
session = requests.Session()
session.headers.update({'Authorization': self.auth_code})
if not hasattr(self, 'access_token'):
self.access_token = self._get_access_token()
session.headers.update({'accesstoken': self.access_token})
return session
def _get_access_token(self):
token_session = requests.Session()
post_data = {'showUsername': self.show_username, 'username': self.username, 'password': self.password}
resp1 = token_session.post(self.sso_url, data=post_data, allow_redirects=False)
assert resp1.status_code == 302, 'incorrect response code %s' % resp1.status_code
get_data = {'client_id': 'tm-manage', 'response_type': 'code',
'redirect_uri': self.redirect_url}
resp2 = token_session.get(self.code_url, params=get_data, allow_redirects=False)
assert resp2.status_code == 302, 'incorrect response code %s' % resp2.status_code
tmp = resp2.headers
code = tmp.get('Location').split('=')[1]
post_data2 = {'grant_type': 'authorization_code', 'code': code, 'redirect_uri': self.redirect_url}
token_session.headers.update({'Authorization': self.auth_code})
resp3 = token_session.post(self.token_url, data=post_data2)
token = json.loads(resp3.text).get('access_token')
return token
def get_ws_conn(self, timeout=60):
self.logger.info('websocket connecting...')
self.access_token = self._get_access_token()
uri = self.all_cfg[self.current_evn]['websocket_server']
if 'accesstoken' in uri:
self.uri = uri
else:
self.uri = uri + '/?accesstoken=%s' % self.access_token if uri.endswith(
'/') else uri + '/?accesstoken=%s' % self.access_token
ws_conn = create_connection(self.uri, timeout=timeout)
if ws_conn.getstatus() == 101:
return ws_conn
else:
raise Exception('connect websocket server failed!')
def _close_ws(self):
if hasattr(self, 'ws'):
self.ws.close()
self.logger.info('connection closed success!')
@check_conn
def send_msg_and_get_response(self, send_data: [dict, str], timeout=3):
if isinstance(send_data, dict):
data = json.dumps(send_data)
self.ws.send(data)
self.logger.info(">>> heart beat!")
self.logger.info(">>> send message:%s" % data)
return self.get_response(ws_conn=self.ws, tm_stamp=send_data.get("timestamp"), timeout=timeout)
elif isinstance(send_data, str):
data = send_data
self.ws.send(data)
self.logger.info(">>> heart beat!")
self.logger.info(">>> send message:%s" % data)
return self.get_response(ws_conn=self.ws, tm_stamp=json.loads(send_data).get("timestamp"), timeout=timeout)
else:
raise Exception('send_data can only support dict or str type')
def get_response(self, ws_conn, tm_stamp=None, timeout=3):
t0 = time.time()
while time.time() - t0 <= int(timeout):
time.sleep(0.2)
out_put = ws_conn.recv()
if tm_stamp:
if str(tm_stamp) in out_put:
self.logger.info(">>> received message:%s" % out_put)
return json.loads(out_put)
elif out_put:
return json.loads(out_put)
else:
continue
else:
self._close_ws()
raise Exception('>>> received no response, please check ws connect!')

View File

@@ -0,0 +1,39 @@
# -*- coding:utf-8 -*-
# 功能对xml文件的操作函数
import os
import sys
import xml.etree.ElementTree as ET
class XmlFileApi:
def __init__(self, file_path):
self.file_path = file_path
self.root = ET.parse(file_path).getroot()
def get_contain_by_tag_names(self, tag_names):
"""
功能根据多级标签名查询xml文件中的内容
Args:
tag_names: 多级标签名,从根目录开始,中间用/分隔例如statistics/total/stat
Returns:
对应标签名的内容,格式:[{"text": xxx, "attrib": yyy}]
"""
# for child in self.root:
# print(child.tag, child.attrib)
elements = self.root.findall('.//' + tag_names)
if len(elements) == 0:
print("根据标签:{},未找到对应的内容,请检查标签名是否正确".format(tag_names))
return
return_data = []
for element in elements:
return_data.append({"text": element.text, "attrib": element.attrib})
return return_data
if __name__ == '__main__':
xf = XmlFileApi(file_path="C:/Users/17447/Downloads/output_gue_it.xml")
xf.get_contain_by_tag_names(tag_names='statistics/total/stat')
# C:/Users/17447/Downloads/output_gue_it.xml
# C:/Users/17447/Downloads/output_gue_it.xml

70
base_framework/startup.py Normal file
View File

@@ -0,0 +1,70 @@
# coding: utf-8
import argparse
import os
import sys
import configparser
# 这几项添加路径必须放前面否则后续import会报错
# 获取组名
input_team_name = sys.argv
try:
team_index = input_team_name.index('-t')
except ValueError:
raise Exception("组名是必填参数,请设置....")
else:
TEAM_NAME = input_team_name[team_index+1]
BASIC_PATH = os.path.dirname(os.path.abspath(__file__))
TEAM_PATH = os.path.abspath(os.path.join(BASIC_PATH, '../{}/'.format(TEAM_NAME)))
sys.path.append(TEAM_PATH)
PROJECT_PATH = os.path.abspath(os.path.join(BASIC_PATH, '../'))
sys.path.append(PROJECT_PATH)
env_choose_path = os.path.join(BASIC_PATH, 'base_config', 'env_choose.ini')
# 获取业务标示hh or hhi
try:
current_business_index = input_team_name.index('-b')
current_business = input_team_name[current_business_index + 1]
except ValueError:
current_business = "hh"
# 获取环境 QA or SIM
try:
current_evn_index = input_team_name.index('-e')
current_evn = input_team_name[current_evn_index + 1]
except ValueError:
current_evn = "QA"
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Choose current environment")
parser.add_argument("-b", dest='business', help="hh or hhi", default='hh')
parser.add_argument("-e", dest='env', help="QA or SIM", default='QA')
parser.add_argument("-u", dest='user', help="user", default='lrq')
parser.add_argument("-j", dest='jira_id', help="jira_id", default='')
parser.add_argument("-t", dest='team_name', help="team_name", default='')
args = parser.parse_args()
# choose_env = args.env
choose_user = args.user
# jira_id = args.jira_id
business = args.business
jira_id = args.jira_id
if jira_id.lower() == 'sim':
args.env = 'SIM'
choose_env = args.env
# 将命令行参数写入env_choose.ini文件中
cof = configparser.ConfigParser()
cof.read(env_choose_path, encoding='utf-8')
cof.set(section="run_evn_name", option="current_business", value=args.business.lower())
cof.set(section="run_evn_name", option="current_evn", value=args.env.upper())
cof.set(section="run_user_name", option="default_user", value=args.user.lower())
cof.set(section="run_jira_id", option="huohua-podenv", value=args.jira_id.upper())
cof.set(section="run_evn_name", option="current_team", value=args.team_name.upper())
with open(env_choose_path, 'w') as fw: # 循环写入
cof.write(fw)
if jira_id:
print("running jira_id is:【{}】【{}】【{}".format(business, TEAM_NAME, jira_id))
else:
print("running environment is: 【{}】【{}】【{}".format(business, TEAM_NAME, choose_env))
from base_framework.main import main, kill_pid
kill_pid()
main()