51Testing软件测试论坛

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

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2139|回复: 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空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

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.         }

复制代码


回复 支持 反对

使用道具 举报

该用户从未签到

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. '''
复制代码
回复 支持 反对

使用道具 举报

  • TA的每日心情

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

    连续签到: 1 天

    [LV.10]测试总司令

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

    使用道具 举报

    本版积分规则

    关闭

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

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

    GMT+8, 2024-11-24 17:19 , Processed in 0.072929 second(s), 22 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

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