# -*- 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 "" 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 "" 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)