51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2129|回复: 5
打印 上一主题 下一主题

接口组合参数压力测试进阶篇

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-2-28 13:31:33 | 只看该作者 回帖奖励 |正序浏览 |阅读模式
对每组参数采用协程压测的方式进行压测
yaml管理用例
支持登陆成功后返回token或者user_id给其他接口使用,如果接参数需要多个加密参数,留了扩展,
自己去封装
检查点采用检查接口和访问数据库的方式进行检查
如果正常参数直接访问数据库,如果是异常参数直接读取接口返回值
注意此框架暂时还是探索阶段,有什么好想法欢迎提供
常用配置

全局变量
  1. PICT_PARAMS = "d:/params.txt" # 请求参数存放地址txt
  2. PICT_PARAMS_RESULT = "d:/t2.txt" # 参数配对后的路径excel
  3. # 数据库的常用字段
  4. FIND_BY_SQL = "findBySql" # 根据sql查找
  5. COUNT_BY_SQL = "countBySql" # 自定义sql 统计影响行数
  6. INSERT = "insert" # 插入
  7. UPDATE_BY_ATTR = "updateByAttr" # 更新数据
  8. DELETE_BY_ATTR = "deleteByAttr" # 删除数据
  9. FIND_BY_ATTR = "findByAttr" # 根据条件查询一条记录
  10. FIND_ALL_BY_ATTR = "findAllByAttr"  #根据条件查询多条记录
  11. COUNT = "count" # 统计行
  12. EXIST = "exist" # 是否存在该记录
  13. #接口简单点中的erro定义
  14. NORMAL = "0" # 正常参数,可以到表里面找到
  15. DEFAULT = "1" # 无此参数
  16. EMPTY = "2" # 参数为空值,如name=''
  17. DROP = "3" # 数据库中找不到此参数
  18. # 接口统计
  19. LOGIN_KEY = ""  # 登陆后返回的key
  20. LOGIN_VALUE = ""  # 登陆后返回的value
  21. RESULT = {"info": []} # 存最后结果
  22. api.yaml
  23. ---
  24. title: XXXX接口测试
  25. host: rap.taobao.org
  26. port: 80
  27. protocol: http://
  28. header: {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8","User-Agent":"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36"}
  29. database: {"databaseName":userinfo,"host":"127.0.0.1", "user": "root", "password": "", "port": 3306, "charset": "utf8"} #配置数据库
  30. api_list:
  31. - id: 1001
  32.   name: 登陆
  33.   method: post
  34.   url: /mockjs/11463/login
  35.   stress: 2
  36.   hope_sql: {"findKey": "findBySql", "sql": "select * from info", "params": { }} #注意findKey的值,要对应全局变量里面的值
  37.   params:
  38.   - "user_name:user_name:error:0:send_keys:333:type:str,user_name:error:1,user_name:error:2,user_name:error:3:send_keys:22222:type:str"
  39.   - "pwd:pwd:error:0:send_keys:111:type:str,pwd:error:1,pwd:error:2,pwd:error:3:send_keys:32321:type:str"
  40.   # 注意这里的error,对应全局变量里面的error
  41.   is_first: 1 # 预览的登陆接口
  42.   login_key: user_id # 返回给其他接口使用的key
  43. - id: 1002
  44.   ...
  45.   get_login_param: 1 # 需要登陆接口返回过来的参数
复制代码
核心代码分析

入口代码
  1. from DAL import httpConfig as hc
  2. from DAL import gevents, dbConnection, httpParams
  3. from common import operateYaml
  4. from DAL.pairs import *
  5. PATH = lambda p: os.path.abspath(
  6.     os.path.join(os.path.dirname(__file__), p)
  7. )
  8. def myRequest(**kwargs):
  9.   # {"appStatus": {"errorCode": 0,"message": "操作成功"},"content": {"nickname":"1212121","user_id": 30}} 接口定义的规则
  10.   # 现在只是考虑到了登陆后返回token,user_id这方面的需求

  11.     method = kwargs["param_req"]["method"]
  12.     get_login_params = 0 # 标识接受了返回多少个参数(user_id,token),用作后面拓展
  13.     param = httpParams.params_filter(kwargs["param_result"])  # 请求参数的处理,如果这里有各种加密可从此处扩展
  14.     # get_login_param表示此接口需要登陆返回来的id(token),一般登陆成功后返回的字段
  15.     if kwargs["param_req"].get("is_first", "false") == "false" and kwargs["param_req"].get("get_login_param", "false") != "false":
  16.         param[Const.LOGIN_KEY]= Const.LOGIN_VALUE
  17.         get_login_params += 1
  18.     if kwargs["param_req"]["method"] == Const.HTTP_POST:
  19.         really_result = kwargs["http_config"].post(dict_post=kwargs["param_req"], param=param) # 发送post请求
  20.     elif kwargs["param_req"]["method"] == Const.HTTP_GET:
  21.         really_result = kwargs["http_config"].get(dict_get=kwargs["param_req"], param=param)  # 发送get请求
  22.     if really_result.get("status_code") == 200:
  23.         print("请求%s成功鸟" %method)
  24.         if kwargs["param_req"].get("is_first", "false") != "false" :
  25.             # 需要接口返回过来的login_key,如token,user_id)等,此时就不用查数据库作为检查点,检查点为直接读取响应结果
  26.             if really_result["appStatus"]["errorCode"] == 0:
  27.                 Const.LOGIN_KEY = kwargs["param_req"]["login_key"]
  28.                 Const.LOGIN_VALUE = really_result["content"][Const.LOGIN_KEY]
  29.                 print("%s接口验证通过,不查数据库" %method)
  30.                 kwargs["result"]["success"] += 1
  31.             else:
  32.                 print("%s接口测试失败,不查数据库~" %method)
  33.                 kwargs["result"]["failed"] += 1

  34.         #如果实际的参数是异常,is_first表示是非登陆接口,就不查数据库.
  35.         elif len(kwargs["param_result"].keys()) != len(param) - get_login_params:
  36.             #根据接口返回的errorCode判断,假如errorCode=2表示参数异常
  37.             if really_result["appStatus"]["errorCode"] == 2:
  38.                 print("%s接口异常参数检测通过" % method)
  39.                 kwargs["result"]["success"] += 1
  40.             else:
  41.                 print("%s接口异常参数检测失败" % method)
  42.                 kwargs["result"]["failed"] += 1
  43.             return
  44.         else: #直接查询数据库作为检查点
  45.             check_sql_key = kwargs["param_req"]["hope_sql"]["findKey"]  # 根据这里的key,来跳转到不同的数据库查询语句
  46.             kwargs["param_req"]["hope_sql"]["params"] = param  # 已经处理好的请求参数传给数据库sql语句参数,结果为:params{"a":"b"}
  47.             for item in kwargs["param_result"]:
  48.                 #  error: 0正常,1无此参数,2参数的值为空,3在数据库中不存.0和3查数据库,1,2直接读取接口返回信息
  49.                 error = kwargs["param_result"][item]["error"]
  50.                 if error == Const.NORMAL or error == Const.DROP:
  51.                     if kwargs["check_sql"].findKeySql(check_sql_key, **kwargs["param_req"]["hope_sql"]):
  52.                         print("%s数据库接口验证成功" %method)
  53.                         kwargs["result"]["success"] += 1
  54.                     else:
  55.                         print("%s数据库接口验证失败" %method)
  56.                         kwargs["result"]["failed"] += 1
  57.                     return
  58.                 elif error == Const.DEFAULT or error == Const.EMPTY:
  59.                     if really_result["appStatus"]["errorCode"] == 2: # 接口返回的2为参数异常
  60.                         print("%s接口异常参数检测成功" %method)
  61.                         kwargs["result"]["success"] += 1
  62.                     else:
  63.                         print("%s接口异常参数检测失败" % method)
  64.                         kwargs["result"]["failed"] += 1
  65.                     return
  66.     else:
  67.         print("请求发送失败,状态码为:%s" % really_result.get("status_code"))
  68. def gevent_request(**kwargs):
  69.     for i in kwargs["api_config"]:  # 读取各个接口的配置,api.ymal
  70.         # 生成参数
  71.         pict_param(params=i["params"], pict_params=Const.PICT_PARAMS,
  72.                    pict_params_result=Const.PICT_PARAMS_RESULT)
  73.         # 读取参数
  74.         get_param = read_pict_param(Const.PICT_PARAMS_RESULT)
  75.         count = len(get_param) # 根据不同分组参数,循环请求
  76.         green_let = []
  77.         req = {}
  78.         for key in i:
  79.             if key != "params":  # 过滤请求参数,参数上面已经处理好了
  80.                 req[key] = i[key]
  81.         result = {}  # 统计数据
  82.         result["name"] = req["name"]  # 接口名字
  83.         result["method"] = req["method"]
  84.         result["url"] = req["url"]
  85.         result["sum"] = count
  86.         result["stress"] = req["stress"]
  87.         result["success"] = 0
  88.         result["failed"] = 0
  89.         kwargs["result"] = result
  90.         for k in range(0, count):
  91.             kwargs["param_result"] = get_param[k]  # 接口中不同的参数组合,是dict类型
  92.             kwargs["param_req"] = req  #每次请求除组合参数之外的参数,如逾期只,请求的url,method,结束等
  93.             for item in range(kwargs["param_req"]["stress"]):  # 压力测试,启动协程去压测
  94.                 green_let.append(gevents.requestGevent(myRequest(**kwargs)))
  95.             for k in range(0, kwargs["param_req"]["stress"]):
  96.                 green_let[k].start()
  97.             for k in range(0, kwargs["param_req"]["stress"]):
  98.                 green_let[k].join()
  99.         Const.RESULT["info"].append(kwargs["result"])
  100. def get_config(api_ymal):
  101.     '''
  102.     得到api.ymal中的设置的接口信息
  103.     :param api_ymal:
  104.     :return:
  105.     '''
  106.     http_config = {} # http信息的记录
  107.     api_config = [] # api的记录记录
  108.     get_api_list = operateYaml.getYam(api_ymal)
  109.     for key in get_api_list:
  110.         if type(get_api_list[key]) != list:
  111.             http_config[key] = get_api_list[key]
  112.         else:
  113.             api_config = get_api_list[key]
  114.     return http_config, api_config


复制代码


分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

  • TA的每日心情

    2024-7-8 09:00
  • 签到天数: 943 天

    连续签到: 1 天

    [LV.10]测试总司令

    4#
    发表于 2018-2-28 14:29:09 | 只看该作者
    赞一个辛苦了
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    3#
     楼主| 发表于 2018-2-28 13:32:47 | 只看该作者
    1. return sqlOperate[key]()

    2. if __name__ == "__main__":
    3. mysqlet = MySQLet(host="127.0.0.1", user="root", password="", charset="utf8", database="userinfo", port=3306)
    4. # 根据字段统计count, join>>AND,OR,可以不传,默认为AND
    5. # print(mysqlet.findKeySql(Const.COUNT, table="info", params={"id": "11", "name": "666"}, join="OR"))
    6. # # 自定义sql语句统计count
    7. # print(mysqlet.findKeySql(Const.COUNT_BY_SQL, sql="select * from info", params={"name": "666"}, join="AND"))
    8. # #插入数据
    9. # print(mysqlet.findKeySql(Const.INSERT, table="info", data={"name":"333", "pwd": "111"}))
    10. 测试结果分析
    11. 现在只是简单的记录下结果,后续优化
    12. #{'method': 'post', 'success': 32, 'stress': 2, 'failed': 0, 'url': '/mockjs/11463/login', 'name': '登陆', 'sum': 16}
    13. '''
    14. sum 表示此接口有16组参数
    15. stress: 表示每组参数压测两次
    16. method: 请求方法
    17. success: 成功请求次数
    18. failed:失败请求次数
    19. url:请求的网址
    20. name:接口名字
    21. '''
    复制代码
    回复 支持 反对

    使用道具 举报

    该用户从未签到

    2#
     楼主| 发表于 2018-2-28 13:32:29 | 只看该作者
    1. if __name__ == "__main__":
    2. start_time = time.time()
    3. get_api_config = get_config(PATH("api.ymal"))
    4. http_conf = hc.ConfigHttp(dict_http=get_api_config[0]) # http请求的设置
    5. apiConfigs = get_api_config[1]
    6. check_sql = dbConnection. MySQLet(host=get_api_config[0]["database"]["host"], user=get_api_config[0]["database"]["user"],
    7. password=get_api_config[0]["database"]["password"], charset=get_api_config[0]["database"]["charset"],
    8. database=get_api_config[0]["database"]["databaseName"], port=get_api_config[0]["database"]["port"])
    9. gevent_request(http_config=http_conf, api_config=get_api_config[1], check_sql=check_sql)
    10. check_sql.close()
    11. end_time = time.time()
    12. print("共花费:""%.2f" % (end_time - start_time))
    13. print(Const.RESULT)


    14. 封装好的访问数据库,查看主要代码来自这里,我修改了一些东西和bug
    15. import mysql.connector
    16. import mysql.connector.errors
    17. from common.customConst import Const
    18. class MySQLet:
    19. """Connection to a MySQL"""
    20. # def __init__(self,user='',password='',database='',charset=None,port=3306):
    21. def __init__(self,**kwargs):
    22. try:
    23. self._conn = mysql.connector.connect(host=kwargs["host"], user=kwargs["user"], password=kwargs["password"],
    24. charset=kwargs["charset"], database=kwargs["database"], port=kwargs["port"])
    25. self.__cursor = None
    26. print("连接数据库")
    27. #set charset charset = ('latin1','latin1_general_ci')
    28. except mysql.connector.errors.ProgrammingError as err:
    29. print('mysql连接错误:' + err.msg)

    30. # def findBySql(self, sql, params={}, limit=0, join='AND'):
    31. def findBySql(self, **kwargs):
    32. """
    33. 自定义sql语句查找
    34. limit = 是否需要返回多少行
    35. params = dict(field=value)
    36. join = 'AND | OR'
    37. """
    38. print("-----------findbysql-----")
    39. print(kwargs)
    40. cursor = self.__getCursor()
    41. # sql = self.__joinWhere(kwargs["sql"], kwargs["params"], kwargs["join"])
    42. if kwargs.get("join", 0) == 0: kwargs["join"] = "AND"
    43. if kwargs.get("limit", "0") == "0": kwargs["limit"] = 1
    44. sql = self.__joinWhere(**kwargs)
    45. cursor.execute(sql, tuple(kwargs["params"].values()))
    46. rows = cursor.fetchmany(size=kwargs["limit"]) if kwargs["limit"] > 0 else cursor.fetchall()
    47. result = [dict(zip(cursor.column_names,row)) for row in rows] if rows else None
    48. return result

    49. # def countBySql(self,sql,params = {},join = 'AND'):
    50. def countBySql(self, **kwargs):
    51. """自定义sql 统计影响行数"""
    52. if kwargs.get("join", 0) == 0: kwargs["join"] = "AND"
    53. cursor = self.__getCursor()
    54. # sql = self.__joinWhere(kwargs["sql"], kwargs["params"], kwargs["join"])
    55. sql = self.__joinWhere(**kwargs)
    56. cursor.execute(sql, tuple(kwargs["params"].values()))
    57. result = cursor.fetchall() # fetchone是一条记录, fetchall 所有记录
    58. return len(result) if result else 0

    59. # def insert(self,table,data):
    60. def insert(self, **kwargs):
    61. """新增一条记录
    62. table: 表名
    63. data: dict 插入的数据
    64. """
    65. fields = ','.join('`'+k+'`' for k in kwargs["data"].keys())
    66. values = ','.join(("%s", ) * len(kwargs["data"]))
    67. sql = 'INSERT INTO `%s` (%s) VALUES (%s)' % (kwargs["table"], fields, values)
    68. cursor = self.__getCursor()
    69. cursor.execute(sql, tuple(kwargs["data"].values()))
    70. insert_id = cursor.lastrowid
    71. self._conn.commit()
    72. return insert_id

    73. # def updateByAttr(self,table,data,params={},join='AND'):
    74. def updateByAttr(self, **kwargs):
    75. # """更新数据"""
    76. if kwargs.get("params", 0) == 0:
    77. kwargs["params"] = {}
    78. if kwargs.get("join", 0) == 0:
    79. kwargs["join"] = "AND"
    80. fields = ','.join('`' + k + '`=%s' for k in kwargs["data"].keys())
    81. values = list(kwargs["data"].values())


    82. values.extend(list(kwargs["params"].values()))
    83. sql = "UPDATE `%s` SET %s " % (kwargs["table"], fields)
    84. kwargs["sql"] = sql
    85. sql = self.__joinWhere(**kwargs)
    86. cursor = self.__getCursor()
    87. cursor.execute(sql, tuple(values))
    88. self._conn.commit()
    89. return cursor.rowcount


    90. # def updateByPk(self,table,data,id,pk='id'):
    91. def updateByPk(self, **kwargs):
    92. """根据主键更新,默认是id为主键"""
    93. return self.updateByAttr(**kwargs)

    94. # def deleteByAttr(self,table,params={},join='AND'):
    95. def deleteByAttr(self, **kwargs):
    96. """删除数据"""
    97. if kwargs.get("params", 0) == 0:
    98. kwargs["params"] = {}
    99. if kwargs.get("join", 0) == 0:
    100. kwargs["join"] = "AND"
    101. # fields = ','.join('`'+k+'`=%s' for k in kwargs["params"].keys())
    102. sql = "DELETE FROM `%s` " % kwargs["table"]
    103. kwargs["sql"] = sql
    104. # sql = self.__joinWhere(sql, kwargs["params"], kwargs["join"])
    105. sql = self.__joinWhere(**kwargs)
    106. cursor = self.__getCursor()
    107. cursor.execute(sql, tuple(kwargs["params"].values()))
    108. self._conn.commit()
    109. return cursor.rowcount
    复制代码
    1. # def deleteByPk(self,table,id,pk='id'):
    2.     def deleteByPk(self, **kwargs):
    3.         """根据主键删除,默认是id为主键"""
    4.         return self.deleteByAttr(**kwargs)

    5.     # def findByAttr(self,table,criteria = {}):
    6.     def findByAttr(self, **kwargs):
    7.         """根據條件查找一條記錄"""
    8.         return self.__query(**kwargs)

    9.     # def findByPk(self,table,id,pk='id'):
    10.     def findByPk(self, **kwargs):
    11.         return self.findByAttr(**kwargs)

    12.     # def findAllByAttr(self,table,criteria={}, whole=true):
    13.     def findAllByAttr(self, **kwargs):
    14.         """根據條件查找記錄"""
    15.         return self.__query(**kwargs)

    16.     # def count(self,table,params={},join='AND'):
    17.     def count(self, **kwargs):
    18.         """根据条件统计行数"""
    19.         if kwargs.get("join", 0) == 0: kwargs["join"] = "AND"
    20.         sql = 'SELECT COUNT(*) FROM `%s`' % kwargs["table"]
    21.         # sql = self.__joinWhere(sql, kwargs["params"], kwargs["join"])
    22.         kwargs["sql"] = sql
    23.         sql = self.__joinWhere(**kwargs)
    24.         cursor = self.__getCursor()
    25.         cursor.execute(sql, tuple(kwargs["params"].values()))
    26.         result = cursor.fetchone()
    27.         return result[0] if result else 0

    28.     # def exist(self,table,params={},join='AND'):
    29.     def exist(self, **kwargs):
    30.         """判断是否存在"""
    31.         return self.count(**kwargs) > 0

    32.     def close(self):
    33.         """关闭游标和数据库连接"""
    34.         if self.__cursor is not None:
    35.             self.__cursor.close()
    36.         self._conn.close()

    37.     def __getCursor(self):
    38.         """获取游标"""
    39.         if self.__cursor is None:
    40.             self.__cursor = self._conn.cursor()
    41.         return self.__cursor

    42.     # def __joinWhere(self,sql,params,join):
    43.     def __joinWhere(self, **kwargs):
    44.         """转换params为where连接语句"""
    45.         if kwargs["params"]:
    46.             keys,_keys = self.__tParams(**kwargs)
    47.             where = ' AND '.join(k+'='+_k for k,_k in zip(keys,_keys)) if kwargs["join"] == 'AND' else ' OR '.join(k+'='+_k for k,_k in zip(keys,_keys))
    48.             kwargs["sql"]+=' WHERE ' + where
    49.         return kwargs["sql"]

    50.     # def __tParams(self,params):
    51.     def __tParams(self, **kwargs):
    52.         keys = ['`'+k+'`' for k in kwargs["params"].keys()]
    53.         _keys = ['%s' for k in kwargs["params"].keys()]
    54.         return keys,_keys

    55.     # def __query(self,table,criteria,whole=False):
    56.     def __query(self, **kwargs):
    57.         if kwargs.get("whole", False) == False or kwargs["whole"] is not True:
    58.             kwargs["whole"] = False
    59.             kwargs["criteria"]['limit'] = 1
    60.         # sql = self.__contact_sql(kwargs["table"], kwargs["criteria"])
    61.         sql = self.__contact_sql(**kwargs)
    62.         cursor = self.__getCursor()
    63.         cursor.execute(sql)
    64.         rows = cursor.fetchall() if kwargs["whole"] else cursor.fetchone()
    65.         result = [dict(zip(cursor.column_names, row)) for row in rows] if kwargs["whole"] else dict(zip(cursor.column_names, rows)) if rows else None
    66.         return result

    67.     # def __contact_sql(self,table,criteria):
    68.     def __contact_sql(self, **kwargs):
    69.         sql = 'SELECT '
    70.         if kwargs["criteria"] and type(kwargs["criteria"]) is dict:
    71.             #select fields
    72.             if 'select' in kwargs["criteria"]:
    73.                 fields = kwargs["criteria"]['select'].split(',')
    74.                 sql+= ','.join('`'+field+'`' for field in fields)
    75.             else:
    76.                 sql+=' * '
    77.             #table
    78.             sql+=' FROM `%s`'% kwargs["table"]
    79.             #where
    80.             if 'where' in kwargs["criteria"]:
    81.                 sql+=' WHERE '+ kwargs["criteria"]['where']
    82.             #group by
    83.             if 'group' in kwargs["criteria"]:
    84.                 sql+=' GROUP BY '+ kwargs["criteria"]['group']
    85.             #having
    86.             if 'having' in kwargs["criteria"]:
    87.                 sql+=' HAVING '+ kwargs["criteria"]['having']
    88.             #order by
    89.             if 'order' in kwargs["criteria"]:
    90.                 sql+=' ORDER BY '+ kwargs["criteria"]['order']
    91.             #limit
    92.             if 'limit' in kwargs["criteria"]:
    93.                 sql+=' LIMIT '+ str(kwargs["criteria"]['limit'])
    94.             #offset
    95.             if 'offset' in kwargs["criteria"]:
    96.                 sql+=' OFFSET '+ str(kwargs["criteria"]['offset'])
    97.         else:
    98.             sql+=' * FROM `%s`'% kwargs["table"]
    99.         return sql
    100.     def findKeySql(self, key ,**kwargs):
    101.         print("-----------")
    102.         print(key)
    103.         sqlOperate = {
    104.         Const.COUNT: lambda: self.count(**kwargs),
    105.         Const.COUNT_BY_SQL: lambda: self.countBySql(**kwargs),
    106.         Const.DELETE_BY_ATTR: lambda: self.deleteByAttr(**kwargs),
    107.         Const.EXIST: lambda: self.exist(**kwargs),
    108.         Const.FIND_ALL_BY_ATTR: lambda: self.findAllByAttr(**kwargs),
    109.         Const.INSERT: lambda: self.insert(**kwargs),
    110.         Const.FIND_BY_ATTR: lambda: self.findByAttr(**kwargs),
    111.         Const.UPDATE_BY_ATTR: lambda: self.updateByAttr(**kwargs),
    112.         Const.FIND_BY_SQL: lambda: self.findBySql(**kwargs)

    113.         }

    复制代码


    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-12 22:51 , Processed in 0.066398 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表