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

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