python API自动化(Pytest+Excel+Allure完整框架集成+yaml入门+大量响应报文处理及加解密、签名处理)
1.pytest数据参数化
假设你需要测试一个登录功能,输入用户名和密码后验证登录结果。可以使用参数化实现多组输入数据的测试:
测试正确的用户名和密码登录成功
测试正确的用户名和错误的密码登录失败
测试错误的用户名和正确的密码登录失败
测试错误的用户名和密码登录失败
在参数化中我们可以单参数、多参数、函数数据参数化
不管哪一种场景,它们都是数据不一样而产生的问题。核心步骤其实都是一样的---发送请求。
我们采取的思想就是进行数据分离--DDT数据驱动
# 1. 第一个情况:单数据
data = [值1,值2,值3...]
data = (值1,值2,值3...)单数据,通过对应的下标去进行获取【pytest会自己进行处理】# 2. 第一个情况:多数据,列表和元组嵌套
data = [("admin","123456"),("admin","123456"),("admin","123456"),...]
data = (["admin","123456"],["admin","123456"],...)
多数据,通过对应的下标去进行获取# 3.【重要】:列表嵌套字典
data = [{“name”:"admin","password":"123456"},
{“name”:"admin","password":"123456"},
{“name”:"admin","password":"123456"}
...]
多数据,通过对应的KEY去进行获取2. 引用对应的数据
在对应的方法上去加上一个装饰器即可引用:
@pytest.mark.parametrize(argnames,argvalues)
@pytest.mark.parametrize("变量名",引用的变量的值) # 引用上面的数据,并且取名加:变量名
2. Pytest+Excel接口自动化框架
我们如何将对应的数据信息进行提取出来,我们如何使用python来读取Excel
读取excel的方法如下
import openpyxl
from collectionsFramework.P02_pytest_excel_allure.config import *class FileReader:"""专门用来读取和写入yaml、excel文件"""# 读取excel--openpyxl -- 文件格式:.xlsx@staticmethod # 直接通过类名进行调用def read_excel(file_path=CASEDATAURL, sheet_name=SHEETNAME):"""读取Excel文件,只支持 .xlsx文件:param file_path: 文件路径:return: excel文件数据,元组的格式"""# 打开现有的Excel文件或创建新的文件try:# 正常情况下直接打开workbook = openpyxl.load_workbook(file_path)except FileNotFoundError:workbook = openpyxl.Workbook()# 选择或创建指定的工作表if sheet_name in workbook.sheetnames:# 【正常】 判断有没有对应的shtttname ,有的话把对应的数据给我加载出来worksheet = workbook[sheet_name]else:# 没有的话,给我新建一个worksheet = workbook.create_sheet(sheet_name)# 获取列名 --- 把第2行的数据拿出来作为我们的key值headers = [cell.value for cell in worksheet[2]]# print("所有的key", headers)# 将数据存储为字典,并且放在我们data当中data = [] # 所有的数据# 把小的数据从第三行开始for row in worksheet.iter_rows(min_row=3, values_only=True):data.append(dict(zip(headers, row)))# data.append(dict(zip(headers, row)))# data.append()# dict(zip(headers, row)) # key 和 value 一一对应起来 ==={}workbook.close()# 所有的数据return dataif __name__ == '__main__':CaseData = FileReader.read_excel()print(CaseData)
EXCEL读取的方法封装
# -*- coding: utf-8 -*-
# @Time : 2023/11/8 20:55
# @Author : Hami
import json
import pytest
from collectionsFramework.P02_pytest_excel_allure.common.FileDataDriver import FileReader
from collectionsFramework.P02_pytest_excel_allure.api_keyword.api_key import ApiKeyclass TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取CaseData = FileReader.read_excel()ak = ApiKey()@pytest.mark.parametrize("CaseData", CaseData)def testCase(self, CaseData):# 没一个对应的数据都来临,获取对应的接口请求的四要素# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言# 1. 字典获取的方式依次拿到# url = CaseData["url"]+CaseData["path"]# params = eval(CaseData["params"])# headers = eval(CaseData["headers"])# data = eval(CaseData["data"])dict_data = {"url":CaseData["url"]+CaseData["path"],"params": eval(CaseData["params"]),"headers": eval(CaseData["headers"]),"data": eval(CaseData["data"])}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])# self.ak.post(url=url,....) # 不建议res = self.ak.post(**dict_data) # 不定长参数传值方式print(res)
eval
eval() 函数是 Python 内置的一个函数,用于将字符串作为代码执行,并返回结果。
def add(a, b):
return a + b
result = eval("add(2, 3)")
print(result) # 输出: 5
getattr()函数的应用
getattr() 函数是 Python 内置的一个函数,用于获取对象的属性或方法
getattr(object, name, default)
object : 表示要获取属性或方法的对象。
name : 表示要获取的属性或方法的名称。
default (可选): 表示当属性或方法不存在时的默认值
例子如下
"""
`getattr()` 函数是 Python 内置的一个函数,用于获取对象的属性或方法。
语法结构:
getattr(object, name, default)
- `object`: 表示要获取属性或方法的对象。
- `name`: 表示要获取的属性或方法的名称。
- `default` (可选): 表示当属性或方法不存在时的默认值。
"""
# 案例一:类当中只有属性
class MyClass:
name = "hami"
age = 18
obj = MyClass()
value = getattr(obj, "name")
print(value) # 输出: hami
# 案例二:类当中对应的属性不存在(有参数),如果直接参数不存在也没有给参数,报错
class MyClass:
name = "hami"
age = 18
obj = MyClass()
value = getattr(obj, "sex", "女")
print(value) # 输出: hami
# 案例三:类当中对应的方法,一定要记得调用(无参数)
class MyClass:
def greet(self):
print("Hello, world!")
obj = MyClass()
method = getattr(obj, "greet") # 返回对应的方法
method() # 输出: Hello, world!
# 案例四:类当中对应的方法,一定要记得调用(有参数)
class MyClass:
def greet(self,name,age):
print("您的姓名是: {},年龄是:{}".format(name,age))
obj = MyClass()
method = getattr(obj, "greet")("hami","25") # 您的姓名是hami,年龄是:25
封装主函数
import json
import pytest
from collectionsFramework.P02_pytest_excel_allure.common.FileDataDriver import FileReader
from collectionsFramework.P02_pytest_excel_allure.api_keyword.api_key import ApiKey
from collectionsFramework.P02_pytest_excel_allure.config import *class TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言CaseData = FileReader.read_excel()ak = ApiKey()@pytest.mark.parametrize("CaseData", CaseData)def testCase(self, CaseData):# -------------------------发送请求-------------------------------try:# 请求数据dict_data = {"url": CaseData["url"] + CaseData["path"],"params": eval(CaseData["params"]),"headers": eval(CaseData["headers"]),"data": eval(CaseData["data"])}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])except Exception:value = MSG_DATA_ERROR# ----待定----print("写入测试结果", value)else:# 得到对应的响应数据res = getattr(self.ak, CaseData["method"])(**dict_data)# -------------------------进行断言处理-------------------------------# 实际结果try:msg = self.ak.get_text(res.json(),CaseData["actualResult"])except Exception:value = MSG_EXDATA_ERROR# ----待定----print("写入测试结果", value)else:# 只会是一个分支语言,但是不会造成测试结果成功或者失败,所以必须无论如何都是需要断言if msg == CaseData["expectResult"]:value = MSG_ASSERT_OKelse:value = MSG_ASSERT_NO# ----待定----print("写入测试结果", value)finally:assert msg == CaseData["expectResult"],value
Allure报告日志及动态标题
import json
import pytest
from collectionsFramework.P02_pytest_excel_allure.common.FileDataDriver import FileReader
from collectionsFramework.P02_pytest_excel_allure.api_keyword.api_key import ApiKey
from collectionsFramework.P02_pytest_excel_allure.config import *import allureclass TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言CaseData = FileReader.read_excel()ak = ApiKey()def __dynamic_title(self, CaseData):# # 动态生成标题# allure.dynamic.title(data[11])# 如果存在自定义标题if CaseData["caseName"] is not None:# 动态生成标题allure.dynamic.title(CaseData["caseName"])if CaseData["storyName"] is not None:# 动态获取story模块名allure.dynamic.story(CaseData["storyName"])if CaseData["featureName"] is not None:# 动态获取feature模块名allure.dynamic.feature(CaseData["featureName"])if CaseData["remark"] is not None:# 动态获取备注信息allure.dynamic.description(CaseData["remark"])if CaseData["rank"] is not None:# 动态获取级别信息(blocker、critical、normal、minor、trivial)allure.dynamic.severity(CaseData["rank"])@pytest.mark.parametrize("CaseData", CaseData)def testCase(self, CaseData):self.__dynamic_title(CaseData)# -------------------------发送请求-------------------------------try:# 请求数据dict_data = {"url": CaseData["url"] + CaseData["path"],"params": eval(CaseData["params"]),"headers": eval(CaseData["headers"]),"data": eval(CaseData["data"])}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])except Exception:value = MSG_DATA_ERROR# ----待定----print("写入测试结果", value)else:# 得到对应的响应数据res = getattr(self.ak, CaseData["method"])(**dict_data)# -------------------------进行断言处理-------------------------------# 实际结果try:msg = self.ak.get_text(res.json(),CaseData["actualResult"])except Exception:value = MSG_EXDATA_ERROR# ----待定----print("写入测试结果", value)else:# 只会是一个分支语言,但是不会造成测试结果成功或者失败,所以必须无论如何都是需要断言if msg == CaseData["expectResult"]:value = MSG_ASSERT_OKelse:value = MSG_ASSERT_NO# ----待定----print("写入测试结果", value)finally:assert msg == CaseData["expectResult"],value
3.Excel框架优化及Yaml入门
1.写入excel框架
我们在FileDataDriver.py, 增加一个写入excel的方法
@staticmethoddef writeDataToExcel(file_path=CASEDATAURL, sheet_name=SHEETNAME, row=None, column=None, value=None):# 打开现有的Excel文件或创建新的文件try:workbook = openpyxl.load_workbook(file_path)except FileNotFoundError:workbook = openpyxl.Workbook()# 选择或创建指定的工作表if sheet_name in workbook.sheetnames:worksheet = workbook[sheet_name]else:worksheet = workbook.create_sheet(sheet_name)# 写入数据到指定行和列worksheet.cell(row=row, column=column).value = value# 保存修改后的文件--- 所以执行过程当中excel是要关闭的状态workbook.save(file_path)
在config.py中写入对应的常量,我们就可以在对应的测试用例中修改代码如下
import json
import pytest
from YamlOptimization.P02_pytest_excel_allure.common.FileDataDriver import FileReader
from YamlOptimization.P02_pytest_excel_allure.api_keyword.api_key import ApiKey
from YamlOptimization.P02_pytest_excel_allure.config import *
import allureclass TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言AllCaseData = FileReader.read_excel()ak = ApiKey()def __dynamic_title(self, CaseData):# # 动态生成标题# allure.dynamic.title(data[11])# 如果存在自定义标题if CaseData["caseName"] is not None:# 动态生成标题allure.dynamic.title(CaseData["caseName"])if CaseData["storyName"] is not None:# 动态获取story模块名allure.dynamic.story(CaseData["storyName"])if CaseData["featureName"] is not None:# 动态获取feature模块名allure.dynamic.feature(CaseData["featureName"])if CaseData["remark"] is not None:# 动态获取备注信息allure.dynamic.description(CaseData["remark"])if CaseData["rank"] is not None:# 动态获取级别信息(blocker、critical、normal、minor、trivial)allure.dynamic.severity(CaseData["rank"])@pytest.mark.parametrize("CaseData", AllCaseData)def testCase(self, CaseData):self.__dynamic_title(CaseData)# 写Excle的行和列row = CaseData["id"]column = 11# 初始化对应的值:res = Nonemsg = Nonevalue = None# -------------------------发送请求-------------------------------try:# 请求数据dict_data = {"url": CaseData["url"] + CaseData["path"],"params": eval(CaseData["params"]),"headers": eval(CaseData["headers"]),"data": eval(CaseData["data"])}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])except Exception:value = MSG_DATA_ERRORFileReader.writeDataToExcel(row=row,column=column,value=value)else:# 得到对应的响应数据res = getattr(self.ak, CaseData["method"])(**dict_data)# -------------------------进行断言处理-------------------------------# 实际结果try:msg = self.ak.get_text(res.json(), CaseData["actualResult"])except Exception:value = MSG_EXDATA_ERRORFileReader.writeDataToExcel(row=row,column=column,value=value)else:# 只会是一个分支语言,但是不会造成测试结果成功或者失败,所以必须无论如何都是需要断言if msg == CaseData["expectResult"]:value = MSG_ASSERT_OKelse:value = MSG_ASSERT_NOFileReader.writeDataToExcel(row=row,column=column,value=value)finally:assert msg == CaseData["expectResult"], value
2.实现优化接口关联
思路:
- excel ---字段 {"key":"value"} ----{"变量名":"jsonpath值","变量名":"jsonpath值"}
{"token":"$..token","name":"$..name"}
2. 写一个方法:
循环遍历这个值 :{"token":"$..token","name":"$..name"}
遍历的过程当中,通过"$..token"提取具体的值:56465456456313521456 ---new_value
3. 定义一个全局变量:专门用来存放提取之后的数据。
all_val={"token":"56465456456313521456","name":"hami"}
代码如下:
def __json_extraction(self, CaseData, res):"""提取响应之后的数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:jsonExData:param res:响应得到的对应的结果:return:"""try:if CaseData["jsonExData"]:Exdata = eval(CaseData["jsonExData"]) # {"VAR_TOKEN":"$..token","MSG":"$.msg"}print("需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的jsonpath获取具体的数据new_value = self.ak.get_text(res.json(), value)self.all_var.update({key: new_value})print("提取出来的数据:>>>", self.all_var)else:print("需要提取的数据为空")except Exception:print("请检查你需要提取数据数据格式的正确性。")
3.进行变量渲染
我们提取出来的casedata如下所示
我们可以看到此处的id和var_token没有具体的值,我们需要把具体的值渲染进去
all_val = {"VAR_TOKEN": "134324324324", "id": "158"}CaseData = {"id": 3,"url": "http://novel.hctestedu.com/book/queryBookDetail/{{id}}",'params': '{"application": "app",\n"application_client_type": "weixin",\n"token": "{{VAR_TOKEN}}"}'}CaseData = eval(Template(str(CaseData)).render(all_val))
就用template来进行渲染
3.数据库操作(提取数据)
我们的思路如下:
- 框架能够连接数据库
- 执行SQL
- 把数据返回
思路:同json提取器
从数据库提取数据:Excle进行维护 ;思路:同json提取器
1.游标对象
在数据库中,游标是一个十分重要的概念。游标提供了一种从表中检索出的数据进行操作的灵活手段,就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。游标总是与一条SQL 选择语句相关联因为游标由结果集(可以是零条,一条或由相关的选择语句检索出的多条记录)和结果集中指向特定记录的游标位置组成。当决定对结果进行处理时,必须声明一个指向该结果的游标
常用的方法:
cursor(): 创建游标对象
close(): 关闭游标对象
fetchone(): 得到结果集的下一行
fetchmany([size = cursor.arraysize]):得到结果集的下几行fetchall():得到结果集中剩下的所有行
excute(sql[,args]): 执行一个数据库查询或命令executemany(sql,args):执行多个数据库查询或命令
例子如下:
"""
在测试过程中偶然需要从数据库获取数据进行测试或者通过数据库的数据进行断言,这时候既要连接到数据库。python当中利用PySQL进行连接
安装:pip install pymysql
"""
import pymysql# 1. 配置数据库连接信息并连接
connection = pymysql.connect(host='shop-xo.hctestedu.com', # 数据库地址port=3306,user='api_test', # 数据库用户名password='Aa9999!', # 数据库密码db='shopxo_hctested', # 数据库名称
)# 2. 创建游标对象,使用它进行操作---人
cursor = connection.cursor()# 3. SQL语句---饭
sql = "SELECT username FROM sxo_user WHERE id = 75;"# 4. 使用游标对象去执行操作SQL
cursor.execute(sql)# 5. 得到结果集的下一行
result = cursor.fetchone()print(result) # 元组
# 6. 关闭数据库连接
cursor.close()
2.我们继续优化代码 将数据库提取的操作写入api_key和测试用例当中
api中
@allure.step(">>>>>>:开始提取数据库的数据")def get_sqlData(self, sqlValue):""":param sqlValue: SQL,返回的数据是一个元组:return:"""import pymysql# 1. 配置数据库连接信息并连接connection = pymysql.connect(host=DB_HOST, # 数据库地址port=DB_PORT,user=DB_USER, # 数据库用户名password=DB_PASSWORD, # 数据库密码db=DB_NAME, # 数据库名称)# 2. 创建游标对象,使用它进行操作cursor = connection.cursor()# 4. 使用游标对象去执行操作SQLcursor.execute(sqlValue)# 5. 得到结果集的下一行result = cursor.fetchone()# 6. 关闭数据库连接cursor.close()return result[0]
测试用例中
def __sql_extraction(self,CaseData):"""从数据库提取数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:sqlExData:return:"""try:if CaseData["sqlExData"]:Exdata = eval(CaseData["sqlExData"]) # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}print("SQL-需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)self.all_var.update({key: new_value})print("SQL-提取出来的数据:>>>", self.all_var)else:print("SQL-需要提取的数据为空")except Exception:print("SQL-请检查你需要提取数据数据格式的正确性。")
4.数据库操作-数据库断言【一般是极为重要的接口我们去做】
- 检查数据库是否有这个用户? --- 数据库断言
- 是否能够正确的登录
思路:excel
- 期望结果:{"username":"yeye"}
- 实际结果:{"username":"SELECT username FROM sxo_user WHERE id=75"}
代码如下
def __sql_assertData(self, CaseData):res = Trueif CaseData["sqlAssertData"] and CaseData["sqlExpectRe ult"]:# 实际结果:从数据库读取出来的数据--字典的格式 # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}realityData = eval(CaseData["sqlAssertData"])# 期望结果:{"name":"hami","id":75}expectData = json.loads(CaseData["sqlExpectResult"])realityDataDict = {}for key, value in realityData.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)realityDataDict.update({key: new_value})if expectData != realityDataDict:res = Falsereturn res
整个侧事故用例代码如下
# -*- coding: utf-8 -*-
# @Time : 2023/11/8 20:55
# @Author : Hami
import json
import pytest
from YamlOptimization.P02_pytest_excel_allure.common.FileDataDriver import FileReader
from YamlOptimization.P02_pytest_excel_allure.api_keyword.api_key import ApiKey
from YamlOptimization.P02_pytest_excel_allure.config import *
import allure
from jinja2 import Template # 变量渲染class TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言AllCaseData = FileReader.read_excel()ak = ApiKey()# 定义:all_val 存放提取出的数据all_var = {}def __dynamic_title(self, CaseData):# # 动态生成标题# allure.dynamic.title(data[11])# 如果存在自定义标题if CaseData["caseName"] is not None:# 动态生成标题allure.dynamic.title(CaseData["caseName"])if CaseData["storyName"] is not None:# 动态获取story模块名allure.dynamic.story(CaseData["storyName"])if CaseData["featureName"] is not None:# 动态获取feature模块名allure.dynamic.feature(CaseData["featureName"])if CaseData["remark"] is not None:# 动态获取备注信息allure.dynamic.description(CaseData["remark"])if CaseData["rank"] is not None:# 动态获取级别信息(blocker、critical、normal、minor、trivial)allure.dynamic.severity(CaseData["rank"])def __json_extraction(self, CaseData, res):"""提取响应之后的数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:jsonExData:param res:响应得到的对应的结果:return:"""try:if CaseData["jsonExData"]:Exdata = eval(CaseData["jsonExData"]) # {"VAR_TOKEN":"$..token","MSG":"$.msg"}print("需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的jsonpath获取具体的数据new_value = self.ak.get_text(res.json(), value)self.all_var.update({key: new_value})print("提取出来的数据:>>>", self.all_var)else:print("需要提取的数据为空")except Exception:print("请检查你需要提取数据数据格式的正确性。")def __sql_extraction(self, CaseData):"""从数据库提取数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:sqlExData:return:"""try:if CaseData["sqlExData"]:Exdata = eval(CaseData["sqlExData"]) # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}print("SQL-需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)self.all_var.update({key: new_value})print("SQL-提取出来的数据:>>>", self.all_var)else:print("SQL-需要提取的数据为空")except Exception:print("SQL-请检查你需要提取数据数据格式的正确性。")def __sql_assertData(self, CaseData):res = Trueif CaseData["sqlAssertData"] and CaseData["sqlExpectRe ult"]:# 实际结果:从数据库读取出来的数据--字典的格式 # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}realityData = eval(CaseData["sqlAssertData"])# 期望结果:{"name":"hami","id":75}expectData = json.loads(CaseData["sqlExpectResult"])realityDataDict = {}for key, value in realityData.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)realityDataDict.update({key: new_value})if expectData != realityDataDict:res = Falsereturn res@pytest.mark.parametrize("CaseData", AllCaseData)def testCase(self, CaseData):self.__dynamic_title(CaseData)CaseData = eval(Template(str(CaseData)).render(self.all_var))print(CaseData)# 写Excle的行和列row = CaseData["id"]column = 11# 初始化对应的值:res = Nonemsg = Nonevalue = None# -------------------------发送请求-------------------------------try:# 请求数据dict_data = {"url": CaseData["url"] + CaseData["path"],"params": eval(CaseData["params"]),"headers": eval(CaseData["headers"]),"data": eval(CaseData["data"])}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])except Exception:value = MSG_DATA_ERRORFileReader.writeDataToExcel(row=row, column=column, value=value)else:# 得到对应的响应数据res = getattr(self.ak, CaseData["method"])(**dict_data)# -------------------------提取数据库的操作---------------------------self.__sql_extraction(CaseData)# -------------------------进行断言处理-------------------------------# 实际结果try:msg = self.ak.get_text(res.json(), CaseData["actualResult"])except Exception:value = MSG_EXDATA_ERRORFileReader.writeDataToExcel(row=row, column=column, value=value)else:# 只会是一个分支语言,但是不会造成测试结果成功或者失败,所以必须无论如何都是需要断言if msg == CaseData["expectResult"]:value = MSG_ASSERT_OK# 成功之后进行数据提取self.__json_extraction(CaseData, res)else:value = MSG_ASSERT_NOFileReader.writeDataToExcel(row=row, column=column, value=value)finally:assert msg == CaseData["expectResult"], value# -------------------------进行数据库断言处理-------------------------------try:res = self.__sql_assertData(CaseData) # False Trueexcept:print("SQL断言出现问题")value = "SQL断言出现问题"assert res, valueelse:assert resfinally:FileReader.writeDataToExcel(row=row, column=column, value=value)
5.yaml应用
yaml的概念:
YAML 的语法和其他高级语言类似,并且可以简单表达清单、散列表,标量等数据形态。它使用空白符号缩进和大量依赖外观的特色,特别适合用来表达或编辑数据结构、各种配置文件、倾印调试内容、文件大纲(例如:许多电子邮件标题格式和YAML非常接近)
YAML 的配置文件后缀为 .yml或.yaml,如:huace.yml
基本语法:
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
'#' 表示注释
Yaml当中如果是数字字符的话,一定要加双引号(不加就是一个整型)
数据类型
纯量:字符串/数字 ,直接写就好了
列表:用符号去代替: -
字典:key : value
符号后面必须要有对应空格
我们的yaml中可以这样写
data:
-id: "0001"name: "qsdd"
-id: "0002"name: "yeyeyeye"
可以成功转换为json格式
也可以这样写:
data: [{ "id": "0001"},{ "id": "0002"}]
也可以转换为json格式
excel格式的转换
方法一:
- {"id":"0001","name":"hami"}
-{"id":"0002","name":"hami"}
方法二:
- id: "0001"name: hami1
-id: 0002name: hami2效果:[{"id":"0001","name":"hami"},{"id":"0002","name":"hami"}]
excel转换为yaml格式
代码如下
"""
YAML 是 "YAML Ain't a Markup Language" -- 数据格式
YAML 的配置文件后缀为 .yml或.yam,如:huace.yml 。1.1 基本语法
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#' 表示注释YAML 支持以下几种数据类型:
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
- 纯量(scalars):单个的、不可再分的值 -- 任意的数据类型对象格式: key: value
数组格式: -可以在在线网址先确定格式的正确性:https://tool.p2hp.com/tool-format-yaml/安装:pip install pyyaml
"""
# 1. 读取数据
import yaml# file_path = "yamlData/test_yaml_04.yaml"
file_path = "yamlData/x_testFavor.yaml"
with open(file_path, 'r', encoding="utf-8") as file:data = yaml.safe_load(file)
print(data)# # 2. 修改数据
# data[1]["res"] = "执行失败"
#
# # 3. 写入数据
# with open(file_path, 'w', encoding="utf-8") as file:
# # allow_unicode=True,避免将中文字符转换为 Unicode 转义序列
# yaml.dump(data, file, allow_unicode=True)
对yaml进行读写
我们在专门用来读写yaml和excel的py文件中新增一个方法
@staticmethoddef write_yaml(data, file_path=YAMLDATA):"""写入yaml文件,写入无序没有关系,通过key获取数据:param data: 需要写入的数据:param file_path: 文件路径:return:"""with open(file_path, 'w', encoding='utf-8') as file:# 可以结合异常处理进行封装try:yaml.dump(data, file, allow_unicode=True)print("YAML数据写入成功。")except yaml.YAMLError as e:print(f"YAML数据写入失败: {e}")
对框架进行修改
我们改成读取yaml文件
对应的读取读取数据源的方法需要修改成: AllCaseData = FileReader.read_yaml()
写入到Yaml文件,所以无需行号去进行记录,可删除变量: row、cell
因为通过Yaml读取出来的本身就是json,所以 dict_data 这个变量值,有可能
是字典,也有可能是字符串,所以为了灵活起见,统一改成如下,同时也可以直接去掉eval方法即可
# 扩展:不做强制要求
# 字符串
# input_string = "{'name':'Alice', 'age':'25', 'city':'New York'}"
# 字典
input_string = {'name':'Alice', 'age':'25', 'city':'New York'}print("转之前:", type(input_string)) # 字符串
# 如果input_string类型是str 则 转成字典eval(input_string); 否则的话:直接输出 ( else input_string)
result = eval(input_string) if isinstance(input_string, str) else input_string
print("转之后:", type(result)) # 字典
print(result)
就是判断值是字典还是字符串 是字符串就转为字典
我们只需要在测试用例中修改读写的数据
再根据情况修改上述的转换格式的代码 即可
回写数据到yaml
首先我们写入的时候是写入所有的数据到data 当中
如,我们所有读取出来的数据是AllCaseData,比如如下格式
[{id":0,"caseName":"登录接口","result":nall},
{id":1,"caseName":"加入购物车","result":nall}]
通过参数化方式我们每次能够拿到一条数据,比如你要修改第一个这条数据,可以
CaseData["result"] = value
重点:但是你修改的只是这一条数据,直接写入就会不正确(覆盖其它的数据)。
所以,正确的做法是:这里的CaseData["id"] 代表下标。
AllCaseData[CaseData["id"] ] = value
yaml+allure+pytest框架如下所示:
import json
import pytest
from YamlOptimization.pytest_yaml_allure.common.FileDataDriver import FileReader
from YamlOptimization.pytest_yaml_allure.api_keyword.api_key import ApiKey
from YamlOptimization.pytest_yaml_allure.config import *
import allure
from jinja2 import Template # 变量渲染class TestCase:# 获取对应的数据 CaseData 需要从文档当中去进行读取# 1. 获取数据(四要素) 2. 发送请求 3.获取响应数据 4.断言AllCaseData = FileReader.read_yaml()ak = ApiKey()# 定义:all_val 存放提取出的数据all_var = {}def __dynamic_title(self, CaseData):# # 动态生成标题# allure.dynamic.title(data[11])# 如果存在自定义标题if CaseData["caseName"] is not None:# 动态生成标题allure.dynamic.title(CaseData["caseName"])if CaseData["storyName"] is not None:# 动态获取story模块名allure.dynamic.story(CaseData["storyName"])if CaseData["featureName"] is not None:# 动态获取feature模块名allure.dynamic.feature(CaseData["featureName"])if CaseData["remark"] is not None:# 动态获取备注信息allure.dynamic.description(CaseData["remark"])if CaseData["rank"] is not None:# 动态获取级别信息(blocker、critical、normal、minor、trivial)allure.dynamic.severity(CaseData["rank"])def __json_extraction(self, CaseData, res):"""提取响应之后的数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:jsonExData:param res:响应得到的对应的结果:return:"""try:if CaseData["jsonExData"]:Exdata = CaseData["jsonExData"] # {"VAR_TOKEN":"$..token","MSG":"$.msg"}print("需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的jsonpath获取具体的数据new_value = self.ak.get_text(res.json(), value)self.all_var.update({key: new_value})print("提取出来的数据:>>>", self.all_var)else:print("需要提取的数据为空")except Exception:print("请检查你需要提取数据数据格式的正确性。")def __sql_extraction(self, CaseData):"""从数据库提取数据:param CaseData: 当前的Case,主要获取需要提取数据的字段:sqlExData:return:"""try:if CaseData["sqlExData"]:Exdata = CaseData["sqlExData"] # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}print("SQL-需要提取的数据:>>>", Exdata)for key, value in Exdata.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)self.all_var.update({key: new_value})print("SQL-提取出来的数据:>>>", self.all_var)else:print("SQL-需要提取的数据为空")except Exception:print("SQL-请检查你需要提取数据数据格式的正确性。")def __sql_assertData(self, CaseData):res = Trueif CaseData["sqlAssertData"] and CaseData["sqlExpectResult"]:# 实际结果:从数据库读取出来的数据--字典的格式 # {"name":"SELECT username FROM sxo_user WHERE username='hami'","id":"SELECT id FROM sxo_user WHERE username='hami'"}realityData = CaseData["sqlAssertData"]# 期望结果:{"name":"hami","id":75}expectData = CaseData["sqlExpectResult"]realityDataDict = {}for key, value in realityData.items():# 通过对应的sql获取具体的数据new_value = self.ak.get_sqlData(value)realityDataDict.update({key: new_value})if expectData != realityDataDict:res = Falsereturn res@pytest.mark.parametrize("CaseData", AllCaseData)def testCase(self, CaseData):self.__dynamic_title(CaseData)CaseData = eval(Template(str(CaseData)).render(self.all_var))print(CaseData)# 写Yaml的下标row = CaseData["id"]# 初始化对应的值:res = Nonemsg = Nonevalue = None# 知识:是否断言# is_assert = True# -------------------------发送请求-------------------------------try:# 请求数据params = eval(CaseData["params"]) if isinstance(CaseData["params"], str) else CaseData["params"]dict_data = {"url": CaseData["url"] + CaseData["path"],"params": params,"headers": CaseData["headers"],"data": CaseData["data"]}if CaseData["type"] == "json":dict_data["data"] = json.dumps(dict_data["data"])except Exception:value = MSG_DATA_ERRORCaseData["result"] = valueelse:# 得到对应的响应数据res = getattr(self.ak, CaseData["method"])(**dict_data)# -------------------------提取数据库的操作---------------------------self.__sql_extraction(CaseData)# -------------------------进行断言处理-------------------------------# 实际结果try:msg = self.ak.get_text(res.json(), CaseData["actualResult"])except Exception:value = MSG_EXDATA_ERRORCaseData["result"] = valueelse:# 只会是一个分支语言,但是不会造成测试结果成功或者失败,所以必须无论如何都是需要断言if msg == CaseData["expectResult"]:value = MSG_ASSERT_OK# 成功之后进行数据提取self.__json_extraction(CaseData, res)else:# is_assert = Falsevalue = MSG_ASSERT_NOCaseData["result"] = valuefinally:assert msg == CaseData["expectResult"], value# -------------------------进行数据库断言处理-------------------------------try:res = self.__sql_assertData(CaseData) # False Trueexcept:print("SQL断言出现问题")value = "SQL断言出现问题"assert res, valueelse:assert resfinally:CaseData["result"] = value# -------------------------把当前的CaseData值写入:可以通过后置处理方法去处理-------------------------------self.AllCaseData[row] = CaseDataFileReader.write_yaml(self.AllCaseData)
4.大量响应报文处理及加解密、签名处理
1.全字段断言-DeepDiff
deepdiff 是一个Python库,用于比较Python数据结构(例如字典、列表、JSON等)之间的差异。它不仅可以比较简单的Python数据类型,还可以比较任意深度或复杂度的数据结构。
在Python中,我们经常需要比较两个JSON对象的异同。例如测试中,我们需要比较预期输出和实际输出是否相同。而在开发中,我们也需要比较两个JSON对象的差异以便维护。使用 deepdiff 库,可以轻松处理这些场景
DeepDiff库的主要模块如下:
1. deepdiff.DeepDiff :这是DeepDiff库的核心模块,提供了比较两个对象之间差异的功能。它可以比较字典、列表、集合等复杂对象,并返回差异的详细信息。
2. deepdiff.DeepSearch :这是一个工具类,用于在复杂对象中搜索指定值。它可以深度遍历对象,并返回找到的匹配项的路径信息。
3. deepdiff.DeepHash :这个模块用于生成复杂对象的哈希值。它可以递归地计算对象的哈希值,并考虑对象中的差异
2.deepdiff常用操作
如果实际请求结果和预期值的json数据都一致,那么会返回 {} 空字典,否则会返回对比差异的结果,接口测试中我们也可以根据这个特点进行断言
如果对比结果不同,将会给出下面对应的返回
1. type_changes:类型改变的key=
2. values_changed:值发生变化的key
3. dictionary_item_added:字典key添加
4. dictionary_item_removed:字段key删除
案例如下
from deepdiff import DeepDiff
"""
总结:
1. 当数据没有差异的时候: 返回是一个空字典 {}
2. 当数据有差异的情况,会根据实际情况显示:1. type_changes:类型改变的key2. values_changed:值发生变化的key3. dictionary_item_added:字典key添加4. dictionary_item_removed:字段key删除
"""# 期望结果
exjson = {'code': 0,"message": "成功","data": {"total": 28,"id": 123}
}# 实际结果
sjjson= {'code': 0,"message": "成功","data": {"total": 28,"id": 123}
}# 1. 如果两个json 是一样的情况下,返回的值是一个空字典
res = DeepDiff(exjson,sjjson)
print(res)实际结果
sjjson = {'code': "0", # 类型不一样"message": "成功","data": {"total": 28,"id": 123}
}# 2. 类型错误会提示:{'type_changes':XXXXXXXXX},root代表的是根节点
res = DeepDiff(exjson, sjjson)
print(res) # {'type_changes':XXXXXXXXX}
sjjson = {'code': 100, # 类型一样,数据不一样"message": "成功","data": {"total": 28,"id": 123}
}
3. 当你的值有改变,会显示 :values_changed
res = DeepDiff(exjson, sjjson)
print(res) # 'values_changed': {"root['code']": {'new_value': 100, 'old_value': 0}}}sjjson = {'code': 0, # 类型一样,数据不一样"message": "成功","data": {"total": 28,"id": 123},"add": "长沙"
}
res = DeepDiff(exjson, sjjson)
print(res) # {'dictionary_item_added': [root['add']]}sjjson = {"message": "成功","data": {"total": 28,"id": 123}
}
res = DeepDiff(exjson, sjjson)
print(res) # {'dictionary_item_removed': [root['code']]}
忽略排序、大小写及某个字段\
其实,在实际接口断言中,可能需要校验的字段顺序不一样,又或者有一些字段值不需要,为了解决这类问题,Deepdiff也提供了相信的参数,只需要在比较的时候加入,传入对应参数即可
例如 我们遇到一个相应数据:
期望结果:
{"code":"200","ordeid":"OR567456415645646",data:"241523415361123132132"}
实际结果:【未定】--- ID 是不确定的,是后端自动生成的,过滤字段。
{"code":"200","ordeid":"OR567456415645646",data:"241523415361123132132"}
ignore_order(忽略排序)
ignore_string_case(忽略大小写)
exclude_paths排除指定的字段
from deepdiff import DeepDiff"""
总结:
1. 当数据没有差异的时候: 返回是一个空字典 {}
2. 当数据有差异的情况,会根据实际情况显示:1. type_changes:类型改变的key2. values_changed:值发生变化的key3. dictionary_item_added:字典key添加4. dictionary_item_removed:字段key删除
"""
from deepdiff import DeepDiff# 1. 字典是以key,顺序没有关系
# json1 = {"name": "hami", "age": 18}
# json2 = {"age": 18, "name": "hami"}# res = DeepDiff(json1,json2)
# print(res)# 2. 定义两个列表进行比较,列表有序的,按照顺序去进行对比
# list1 = [1, 2, 3]
# list2 = [3, 2, 1]# 有序列表,会返回对应的数据: {'values_changed': {'root[0]': {'new_value': 3, 'old_value': 1}, 'root[2]': {'new_value': 1, 'old_value': 3}}}
# res = DeepDiff(list1, list2)
# print(res)
# # 过滤对应的顺序: ignore_order=True ------{}
# res = DeepDiff(list1, list2, ignore_order=True)
# print(res)# 3. 过滤大小写的操作: ignore_string_case=True
# json1 = {"name": "hami", "age": 18}
# json2 = {"age": 18, "name": "Hami"}
#
# res = DeepDiff(json1, json2)
# print(res)
# # 过滤字符的大小写(不区分)
# res = DeepDiff(json1, json2, ignore_string_case=True)
# print(res)# 4. 忽略某个字段 exclude_paths={"age"} --- 用的最多
json1 = {"code": "200", "name": "hami", "usercode": "431123456789", "age": 18}
json2 = {"code": "200", "name": "hami", "usercode": "431123456789", "age": 20}
res = DeepDiff(json1, json2)
print(res)
res = DeepDiff(json1, json2, exclude_paths={"age"})
# res = DeepDiff(json1, json2, exclude_paths={"root['age']"})
print(res)
其他参数:
from deepdiff import DeepDiff"""
总结:
1. 当数据没有差异的时候: 返回是一个空字典 {}
2. 当数据有差异的情况,会根据实际情况显示:1. type_changes:类型改变的key2. values_changed:值发生变化的key3. dictionary_item_added:字典key添加4. dictionary_item_removed:字段key删除
"""
from deepdiff import DeepDiff# 忽略数字
from decimal import Decimal
# 高精度的十进制计算,避免了浮点数运算中的精度丢失问题。
t1 = Decimal('10.01') # 数字类型
t2 = 10.01# print(DeepDiff(t1, t2))
# print(DeepDiff(t1, t2, ignore_numeric_type_changes=True))# 忽略字符串
res = DeepDiff(b'hello', 'hello', ignore_string_type_changes=True)
# print(res)# 打印出来的格式可以指定:
# TREE_VIEW = 'tree'
# TEXT_VIEW = 'text' # 默认
t1 = {"name": "yanan", "pro": {"sh": "shandong", "city": ["zibo", "weifang"]}}
t2 = {"name": "yanan", "pro": {"sh": "shandong", "city": ["taian", "weifang"]}}
# 显示格式:tree
diff1 = DeepDiff(t1, t2, view='tree')
print(diff1)
# 默认为text # 可读性
diff2 = DeepDiff(t1, t2, view='text')
print(diff2)
搜索模块
我们使用deepdiff还可以用来搜索
from deepdiff import DeepSearchobj = ["long somewhere", "string", 0, "somewhere great!"]
item = 'some' # 大小写敏感的
ds = DeepSearch(obj, item, case_sensitive=True)
print(ds)
返回的结果就是其下标值
哈希模块-DeepHash
DeepHash模块是DeepDiff库中的一个模块,用于生成复杂对象的哈希值。它可以递归地计算对象的哈希值,并考虑对象中的差异。可以方便地计算复杂对象的哈希值,并在需要时用于对象的唯一标识或数据校验等用途
from deepdiff import DeepHash
# 可以把对应的Value 转成对应的Hasht1 = {"name": "yanan", "pro": {"sh": "shandong", "city": ["zibo", "weifang"]}}
res = DeepHash(t1)
print(res)
如图显示,输出的结果就是一串hash值
3.全字段对比
deepdiff的封装
from deepdiff import DeepDiffdef jsonDeepDiff(json1, json2, **other):"""对比json数据的一致性:param json1: 期望结果:param json2: 实际结果:param other: 你想要写的对应的规则:return:"""res = DeepDiff(json1, json2, **other)if res == {}:return Trueelse:return False
将deepdiff结果之前的框架
我们在deepdiff封装在关键字封装的类之中和对应的测试用例中
另外我们还要避免excel表中的值为空的情况,因此,我们需要再请求数据的地方进行修改
4.加密
核心思路:如果说对应的参数需要进行加密的话,在传参的时候在key当中通过 @ 符号进行标识
对我们的data对进行遍历,判断key的[0] == @
例如:{"@name":"tony","@password":"123456"}
我们先把加密方法进行封装
"""
对称加密:加密和解密使用的是同一把钥匙,即:使用相同的密匙对同一密码进行加密和解密。
常用的加密方法:DES、3DES、AES...(AES算法是目前最常用的对称加密算法)
"""
import base64
from Crypto.Cipher import AESclass EncryptDate:# 构造方法def __init__(self, key):# 类属性self.key = key.encode("utf-8") # 初始化密钥self.length = AES.block_size # 初始化数据块大小 :16位self.aes = AES.new(self.key, AES.MODE_ECB) # 初始化AES,ECB模式的实例# 截断函数,去除填充的字符self.unpad = lambda date: date[0:-ord(date[-1])]# 缺几位数据就补齐多少位数据:16的倍数def pad(self, text): # text == tony"""#填充函数,使被加密数据的字节码长度是block_size的整数倍"""count = len(text.encode('utf-8')) # count = 4add = self.length - (count % self.length) # 求它们相差的位数# add = 16- (4%16) === 16 - 4 == 12entext = text + (chr(add) * add)# entext = “tony” + (chr(add) * 12 ) === entext == tony# print("entext的数据是:",entext)return entext# 加密函数def encrypt(self, encrData): # 加密函数 encrData == tony (16位)res = self.aes.encrypt(self.pad(encrData).encode("utf8")) # self.aes.encrypt(tony)msg = str(base64.b64encode(res), encoding="utf8")return msg# 解密函数def decrypt(self, decrData): # 解密函数 XbXHJrNLwoTVcyfqM9eTgQ==# 从base64编码转回来res = base64.decodebytes(decrData.encode("utf8"))# 将数据进行对应的解密:XbXHJrNLwoTVcyfqM9eTgQ==msg = self.aes.decrypt(res).decode("utf8")# print("msg的值:",msg)# 把转回来的数据后面的字符去掉。return self.unpad(msg)key = "1234567812345678"
ENCRYPTAES = EncryptDate(key)
然后我们再进行加密处理
在excel和yaml的处理py文件中 写上对应的方法:
@staticmethoddef data_EncryptDateAES(data):newdata = {} # 去掉前面@符号同时数据进行加密for key in data:if key[0] == "@":# 需要加密处理newdata[key[1:]] = ENCRYPTAES.encrypt(data[key])else:# 不需要加密处理newdata[key] = data[key]return newdata
5.接口签名Sign封装
数字签名是一种用于验证数据完整性和身份认证的密码学技术。它通过使用私钥对数据进行加密来创建一个唯一的数字标识,该标识称为签名。验证者可以使用相应的公钥对签名进行解密和验证,以确认数据的完整性和真实性
token \ session \ cookie \ 签名(sign) --- 都是用来鉴权的
它们的区别是什么?
token \ session \ cookie
token 其实用来代表是系统当中的某个用户 --- 一般正常情况下是写在header
session \ cookie 是你发送请求的时候自带的.浏览器会自带这个对应的数据
session \ cookie 区别是什么?
存储位置不同: cookie -客户端 ; session--服务端(会话)
大小限制不同: cookie - 有大小限制; session 没有大小限制
安全隐患不同: cookie 存在安全隐患,可以拦截或找你本地文件得到你存储的信息
时效性不同: cookie 是时效性, session关闭浏览器时就消失
签名(sign) --- 是接口当中的一个字段. 发送消息\发送文档\保证安全性\需要在接口传一波数据.
通过算法得到一串数据(类似于token)
相关文章:
![](https://img-blog.csdnimg.cn/direct/7c1691689c5f47c997606a86f3814d47.png)
python API自动化(Pytest+Excel+Allure完整框架集成+yaml入门+大量响应报文处理及加解密、签名处理)
1.pytest数据参数化 假设你需要测试一个登录功能,输入用户名和密码后验证登录结果。可以使用参数化实现多组输入数据的测试: 测试正确的用户名和密码登录成功 测试正确的用户名和错误的密码登录失败 测试错误的用户名和正确的密码登录失败 测试错误的用户名和密码登…...
![](https://www.ngui.cc/images/no-images.jpg)
【Postman学习】
Postman是一个非常流行的API开发和测试工具,广泛用于Web服务的开发、测试和调试。它提供了一个图形界面,允许用户轻松地构建、发送和管理HTTP(S)请求,同时查看和分析响应。下面是对Postman接口测试工具的详细解释: 1. Postman简介…...
![](https://img-blog.csdnimg.cn/direct/2dc538ce75e545dabd246b369e40c065.png)
【Linux】IO多路复用——select,poll,epoll的概念和使用,三种模型的特点和优缺点,epoll的工作模式
文章目录 Linux多路复用1. select1.1 select的概念1.2 select的函数使用1.3 select的优缺点 2. poll2.1 poll的概念2.2 poll的函数使用2.3 poll的优缺点 3. epoll3.1 epoll的概念3.2 epoll的函数使用3.3 epoll的优点3.4 epoll工作模式 Linux多路复用 IO多路复用是一种操作系统的…...
![](https://img-blog.csdnimg.cn/direct/06cc671082884743af2e759e828eb389.png)
IBCS 虚拟专线——让企业用于独立IP
在当今竞争激烈的商业世界中,企业的数字化运营对网络和服务器的性能有着极高的要求。作为一家企业的 IT 主管,我深刻体会到了在网络和服务器配置方面所面临的种种挑战,以及 IBCS 虚拟专线带来的革命性改变。 我们企业在业务扩张的过程中&…...
![](https://www.ngui.cc/images/no-images.jpg)
驾驭巨龙:Perl中大型文本文件的处理艺术
驾驭巨龙:Perl中大型文本文件的处理艺术 Perl,这门被亲切称为“实用提取和报告语言”的编程语言,自从诞生之日起,就以其卓越的文本处理能力闻名于世。在面对庞大的文本文件时,Perl的强大功能更是得到了充分的体现。本…...
![](https://img-blog.csdnimg.cn/direct/1156447dff2040448e49e80c267558fa.png)
Kafka~特殊技术细节设计:分区机制、重平衡机制、Leader选举机制、高水位HW机制
分区机制 Kafka 的分区机制是其实现高吞吐和可扩展性的重要特性之一。 Kafka 中的数据具有三层结构,即主题(topic)-> 分区(partition)-> 消息(message)。一个 Kafka 主题可以包含多个分…...
![](https://img-blog.csdnimg.cn/direct/aaf25f899b244d348ad80a6c6a85fa01.png)
springcloud-config 客户端启用服务发现client的情况下使用metadata中的username和password
为了让spring admin 能正确获取到 spring config的actuator的信息,在eureka的metadata中添加了metadata.user.user metadata.user.password eureka.instance.metadata-map.user.name${spring.security.user.name} eureka.instance.metadata-map.user.password${spr…...
![](https://www.ngui.cc/images/no-images.jpg)
云计算 | 期末梳理(中)
1. 经典虚拟机的特点 多态(Polymorphism):支持多种类型的OS。重用(Manifolding):虚拟机的镜像可以被反复复制和使用。复用(Multiplexing):虚拟机能够对物理资源时分复用。2. 系统接口 最基本的接口是微处理器指令集架构(ISA)。应用程序二进制接口(ABI)给程序提供使用硬件资源…...
![](https://i-blog.csdnimg.cn/direct/1d422ed93fd04674847900328b015d11.jpeg)
pytest测试框架pytest-order插件自定义用例执行顺序
pytest提供了丰富的插件来扩展其功能,本章介绍插件pytest-order,用于自定义pytest测试用例的执行顺序。pytest-order是插件pytest-ordering的一个分支,但是pytest-ordering已经不再维护了,建议大家直接使用pytest-order。 官方文…...
![](https://img-blog.csdnimg.cn/direct/e133a036a8a444f5b511467da813529a.png)
吴恩达机器学习 第三课 week2 推荐算法(上)
目录 01 学习目标 02 推荐算法 2.1 定义 2.2 应用 2.3 算法 03 协同过滤推荐算法 04 电影推荐系统 4.1 问题描述 4.2 算法实现 05 总结 01 学习目标 (1)了解推荐算法 (2)掌握协同过滤推荐算法(Collabo…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL CASE 表达式
MySQL CASE表达式 一、CASE表达式的语法二、 常用场景1,按属性分组统计2,多条件统计3,按条件UPDATE4, 在CASE表达式中使用聚合函数 三、CASE表达式出现的位置 一、CASE表达式的语法 -- 简单CASE表达式 CASE sexWHEN 1 THEN 男WHEN 2 THEN 女…...
![](https://www.ngui.cc/images/no-images.jpg)
Unity3D 游戏数据本地化存储与管理详解
在Unity3D游戏开发中,数据的本地化存储与管理是一个重要的环节。这不仅涉及到游戏状态、玩家信息、游戏设置等关键数据的保存,还关系到游戏的稳定性和用户体验。本文将详细介绍Unity3D中游戏数据的本地化存储与管理的技术方法,并给出相应的代…...
![](https://img-blog.csdnimg.cn/direct/6f3f03176083420ba1618c6ee658b034.png)
昇思25天学习打卡营第1天|初学教程
文章目录 背景创建环境熟悉环境打卡记录学习总结展望未来 背景 参加了昇思的25天学习记录,这里给自己记录一下所学内容笔记。 创建环境 首先在平台注册账号,然后登录,按下图操作,创建环境即可 创建好环境后进入即可࿰…...
![](https://img-blog.csdnimg.cn/direct/364455e479c548838be188c02e76bffe.png)
ctfshow-web入门-命令执行(web59-web65)
目录 1、web59 2、web60 3、web61 4、web62 5、web63 6、web64 7、web65 都是使用 highlight_file 或者 show_source 1、web59 直接用上一题的 payload: cshow_source(flag.php); 拿到 flag:ctfshow{9e058a62-f37d-425e-9696-43387b0b3629} 2、w…...
![](https://img-blog.csdnimg.cn/direct/5d4cb3908e3a4a4a88808f24ce431a9c.png)
Websocket在Java中的实践——最小可行案例
大纲 最小可行案例依赖开启Websocket,绑定路由逻辑类 测试参考资料 WebSocket是一种先进的网络通信协议,它允许在单个TCP连接上进行全双工通信,即数据可以在同一时间双向流动。WebSocket由IETF标准化为RFC 6455,并且已被W3C定义为…...
![](https://www.ngui.cc/images/no-images.jpg)
python请求报错::requests.exceptions.ProxyError: HTTPSConnectionPool
在发送网页请求时,发现很久未响应,最后报错: requests.exceptions.ProxyError: HTTPSConnectionPool(hostsvr-6-9009.share.51env.net, port443): Max retries exceeded with url: /prod-api/getInfo (Caused by ProxyError(Unable to conne…...
![](https://img-blog.csdnimg.cn/direct/ba517ffa4fa146fca1981146a5b8c076.png)
【Unity】Excel配置工具
1、功能介绍 通过Excel表配置表数据,一键生成对应Excel配置表的数据结构类、数据容器类、已经二进制数据文件,加载二进制数据文件获取所有表数据 需要使用Excel读取的dll包 2、关键代码 2.1 ExcelTool类 实现一键生成Excel配置表的数据结构类、数据…...
![](https://www.ngui.cc/images/no-images.jpg)
001 线性查找(lua)
文章目录 迭代器主程序 迭代器 -- 定义一个名为 linearSearch 的函数,它接受两个参数:data(一个数组)和 target(一个目标值) function linearSearch(data, target) -- 使用 for 循环遍历数组 data&…...
![](https://img-blog.csdnimg.cn/direct/940c89c2f2304578bdc584cce1154d34.png)
数据结构之链表
储备知识: 线性表 :一对一的数据所组成的关系称为线性表。 线性表是一种数据内部的逻辑关系,与存储形式无关线性表既可以采用连续的顺序存储(数组),也可以采用离散的链式存储(链表)顺序表和链表都称为线性表 顺序存储就是将数据存…...
![](https://www.ngui.cc/images/no-images.jpg)
【小工具】 Unity相机宽度适配
相机默认是根据高度适配的,但是在部分游戏中需要根据宽度进行适配 实现步骤 定义标准屏幕宽、高判断标准屏幕宽高比与当前的是否相等通过**(标准宽度/当前宽度) (标准高度 / 当前高度)**计算缩放调整相机fieldOfView即…...
![](https://www.ngui.cc/images/no-images.jpg)
centos误删yum和python
在下载pkdg时,因为yum报错坏的解释器,然后误删了yum和python。 在下载各种版本,创建各种软连接,修改yum文件都不好使后,发现了这样一个方法:Centos: 完美解决python升级导致的yum报错问题(相信…...
![](https://img-blog.csdnimg.cn/img_convert/05560c7529fff7eef8cdd1dc5d0ec322.png)
WP黑格导航主题BlackCandy
BlackCandy-V2.0全新升级!首推专题区(推荐分类)更多自定义颜色!选择自己喜欢的色系,焕然一新的UI设计,更加扁平和现代化! WP黑格导航主题BlackCandy...
![](https://www.ngui.cc/images/no-images.jpg)
elasticsearch底层核心组件
Elasticsearch是一个高度可扩展的开源全文搜索和分析引擎,它基于Apache Lucene构建,并添加了分布式特性。以下是Elasticsearch的一些底层核心组件: 1. **Lucene**: - Elasticsearch基于Apache Lucene,一个高性能的…...
![](https://img-blog.csdnimg.cn/direct/197f291d3e704864b7fc1dae966e9282.png)
EasyExcel数据导入
前言: 我先讲一种网上信息的获取方式把,虽然我感觉和后面的EasyExcel没有什么关系,可能是因为这个项目这个操作很难实现,不过也可以在此记录一下,如果需要再拆出来也行。 看上了网页信息,怎么抓到&#x…...
![](https://img-blog.csdnimg.cn/direct/5390394e3dde4aa48b5d1e4aa3229d0f.png)
20240630 每日AI必读资讯
📚全美TOP 5机器学习博士发帖吐槽:实验室H100数量为0! - 普林斯顿、哈佛「GPU豪门」,手上的H100至少三四百块,然而绝大多数ML博士一块H100都用不上 - 年轻的研究者们纷纷自曝自己所在学校或公司的GPU情况:…...
![](https://img-blog.csdnimg.cn/direct/dfba3cfb6bcf4f869eb8489048db8357.png)
第十一章 Qt的模型视图
目录 一、模型/视图的原理 1、原理分析 2、模型(数据模型) 3、视图 4、代理 二、文件系统模型 1、项目练习 2、UI 设计 3、代码实现 三、字符串链表模型 QStringListModel 1、项目效果 2、项目实现 四、标准项模型(QStandardItemModel) 1、模型分析 2、项目效…...
![](https://img-blog.csdnimg.cn/direct/34d75ed3d2b6463ea829b93ceb216e2e.png)
力扣 单词规律
所用数据结构 哈希表 核心方法 判断字符串pattern 和字符串s 是否存在一对一的映射关系,按照题意,双向连接的对应规律。 思路以及实现步骤 1.字符串s带有空格,因此需要转换成字符数组进行更方便的操作,将字符串s拆分成单词列表…...
![](https://img-blog.csdnimg.cn/img_convert/85fb24fa701e3807fff7a6f6d1540083.jpeg)
10款好用不火的PC软件,真的超好用!
AI视频生成:小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/市场上有很多软件,除了那些常见的大众化软件,还有很多不为人知的小众软件,它们的作用非常强大,简洁…...
![](https://img-blog.csdnimg.cn/direct/21cf07b9284f499e95cbf0de666b9b05.png)
Windows怎么实现虚拟IP
在做高可用架构时,往往需要用到虚拟IP,在linux上面有keepalived来实现虚拟ip的设置。在windows上面该怎么弄,keepalived好像也没有windows版本,我推荐一款浮动IP软件PanguVip,它可以实现windows上面虚拟ip的漂移。设置…...
![](https://img-blog.csdnimg.cn/direct/b2671d0eae154299aa9bbedcd10f63b7.webp)
【计算机网络】HTTP——基于HTTP的功能追加协议(个人笔记)
学习日期:2024.6.29 内容摘要:基于HTTP的功能追加协议和HTTP/2.0 HTTP的瓶颈与各功能追加协议 需求的产生 在Facebook、推特、微博等平台,每分每秒都会有人更新内容,我们作为用户当然希望时刻都能收到最新的消息,为…...
![](https://img-blog.csdnimg.cn/direct/0b3ff8ed684844f48f28683dfdc6db0e.jpeg#pic_center)
【多媒体】Java实现MP4视频播放器【JavaFX】【音视频播放】
在Java中播放视频可以使用多种方案,最常见的是通过Swing组件JFrame和JLabel来嵌入JMF(Java Media Framework)或Xuggler。不过,JMF已经不再被推荐使用,而Xuggler是基于DirectX的,不适用于跨平台。而且上述方案都需要使用第三方库。…...
![](https://img-blog.csdnimg.cn/img_convert/7c1681fea47b5bf9dd47ecbc383373dc.jpeg)
2024 Parallels Desktop for Mac 功能介绍
Parallels Desktop的简介 Parallels Desktop是一款由Parallels公司开发的桌面虚拟化软件,它允许用户在Mac上运行Windows和其他操作系统。通过强大的技术支持,用户无需重新启动电脑即可在Mac上运行Windows应用程序,实现了真正的无缝切换。 二…...
![](https://img-blog.csdnimg.cn/direct/184ea7ac1c504142ae335bde9dca379e.jpeg)
颍川韩氏,来自战国七雄韩国的豪族
颍川是战国七雄韩国故土,韩国被秦国灭国后,王公贵族们除了坚决反秦的被杀了外,大部分都留存了下来。这些人在楚、汉反秦战争中,成为反秦统一战线的重要力量,其中两人先后被封为重新恢复的韩国的国王。 一个是横阳君韩…...
![](https://www.ngui.cc/images/no-images.jpg)
Spring boot中如何使用Thymeleaf模板
大家好,我是 网创有方。今天给大家分享下Spring boot中如何使用Thymeleaf模板。 在 IntelliJ IDEA 中使用 Thymeleaf 模板引擎来开发 Spring Boot 应用程序是相对简单的。以下是一些基本步骤,帮助你在 IDEA 中设置和使用 Thymeleaf: 创建一个…...
![](https://img-blog.csdnimg.cn/direct/40e433b4c9f54f33ac48cb64936eda6a.png)
单片机学习(14)--DS18B20温度传感器
DS18B20温度传感器 13.1DS18B20温度传感器基础知识1.DS18B20介绍2.引脚及应用电路3.内部结构框图4.存储器框图5.单总线介绍6.单总线电路规范7.单总线时序结构8.DS18B20操作流程9.DS18B20数据帧 13.2DS18B20温度读取和温度报警器代码1.DS18B20温度读取(1)…...
![](https://img-blog.csdnimg.cn/direct/ae36e0c54b5342479e76db18a421c45a.png)
ue 材质贴图Tiling repeat
材质问题,如下 贴图显然不符合逻辑,太大,并且是一次性贴图 换一个红砖纹理,就看清了,砖太大了 修改: 拖出一个TexCoord,代表坐标,拖出一个参数,代表次数,如…...
![](https://img-blog.csdnimg.cn/direct/3da70da84f1443cf8100e3ed516c3bf4.png)
【图像超分辨率】一个简单的总结
文章目录 图像超分辨率(Image Super-Resolution, ISR)1 什么是图像超分辨率?2 图像超分辨率通常有哪些方法?(1)基于插值的方法(2)基于重建的方法(3)基于学习的方法(LR im…...
![](https://img-blog.csdnimg.cn/direct/e534e070020a4f1f82a4a86fd00253b8.png)
WEB与低代码:B/S架构在开发中的应用与优势
在互联网迅猛发展的今天,WEB应用已经成为人们日常生活和工作中不可或缺的一部分。随着技术的进步和需求的多样化,开发高效、灵活且易于维护的WEB应用变得尤为重要。B/S架构(Browser/Server Architecture)作为一种常见的WEB应用架构…...
![](https://www.ngui.cc/images/no-images.jpg)
内容营销专家刘鑫炜揭秘:姜萍一夜暴红背后的品牌传播密码
在互联网的浪潮下,品牌传播的方式愈发多样和复杂。近日,江苏省涟水中等专业学校的十七岁中专生姜萍因在世界级数学竞赛中取得优异成绩而一夜暴红,成为网络上的热议焦点。 在这个充满变数的时代,谁也无法预测下一个网红会是谁。然…...
![](https://img-blog.csdnimg.cn/direct/5c8ae165820e4727b5bc556d72dacb9e.png)
安装VEX外部编辑器
Houdini20配置VEX外部编辑器方法_哔哩哔哩_bilibili 下载并安装Visual Studio Code软件:Download Visual Studio Code - Mac, Linux, Windows 在Visual Studio Code软件内,安装相关插件,如: 中文汉化插件vex插件 安装Houdini Expr…...
![](https://www.ngui.cc/images/no-images.jpg)
ISO 19110全局要求类/req/global/bound-association-role要求的详细解释
/req/global/bound-association-role 要求: 如果模型允许在一个关联角色(association role)中存在“rolePlayer”关联,并且该角色属于一个“globalProperty”角色,那么这种绑定必须通过一个绑定的关联角色实体(bound …...
![](https://www.ngui.cc/images/no-images.jpg)
武汉凯迪正大等简述电缆电容检测:原理、应用与重要性
为了确保电缆的安全稳定运行评估电缆绝缘质量以及检测潜在故障,需要对电缆做一些必要的检测。本文将依照凯迪正大的一些经验对电缆电容检测的原理、应用及其重要性进行简单的序述。 一、电缆电容检测的原理 电缆电容检测基于电容的基本特性,电容是指两个…...
![](https://www.ngui.cc/images/no-images.jpg)
python调用阿里云OSS对象存储
1)安装SDK import oss2 print(oss2.__version__) 如果能返回SDK版号,则安装成功 2)配置访问凭证 import oss2 from project1 import settings #以下参数在阿里云账号中可以查到 auth=oss2.Auth(settings.ACCESS_KEY_ID, settings.ACCESS_KEY_SECRET) endpoint=settings.E…...
![](https://img-blog.csdnimg.cn/direct/9e8cd86509a24a21b669e569c02c850f.png)
530、二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数,其数值等于两值之差的绝对值。 代码如下: class Solution { private: int result INT_MAX; TreeNode* pre NULL; void traversal(TreeNode…...
![](https://img-blog.csdnimg.cn/direct/6849283ccde3470f9d7a22a0d92c17a7.png)
docker配置redis主从复制
下载redis,复制redis.conf 主节点(6379) 修改redis.conf # bind 127.0.0.1 # 注释掉这里 protected-mode no # 改为no port 6379从节点(6380) 修改redis.conf bind 127.0.0.1 protected-mode no # 改为no port 6380 replicaof 172.17.0.2 6379 # 这里的ip为主节点容器的i…...
![](https://www.ngui.cc/images/no-images.jpg)
IPython调试秘籍:pdb调试器深度解析与实战
🐞 IPython调试秘籍:pdb调试器深度解析与实战 在Python编程中,调试是开发过程中不可或缺的一环。IPython,作为一个强大的交互式Python解释器,内置了pdb调试器,使得代码调试变得异常便捷。本文将深入探讨如…...
![](https://www.ngui.cc/images/no-images.jpg)
MySQL 死锁处理
查询是否锁表 SHOW OPEN TABLES WHERE In_use > 0; 查看正在锁的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 查看等待锁的事务 SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS; 查看进程信息 SHOW PROCESSLIST; 或者 SELECT * FROM INFORMATION_SCHE…...
![](https://img-blog.csdnimg.cn/direct/0cad6b3d1d5f4a85a9b7a67cb951e085.png)
scatterlist的相关概念与实例分析
概念 scatterlist scatterlist用来描述一块内存,sg_table一般用于将物理不同大小的物理内存链接起来,一次性送给DMA控制器搬运 struct scatterlist {unsigned long page_link; //指示该内存块所在的页面unsigned int offset; //指示该内存块在页面中的…...
![](https://img-blog.csdnimg.cn/direct/6dfb94c2f38441ed8d3e0d1bb309b1b6.png)
leetCode.97. 交错字符串
leetCode.97. 交错字符串 题目思路 代码 class Solution { public:bool isInterleave(string s1, string s2, string s3) {int n s1.size(), m s2.size();if ( s3.size() ! n m ) return false;vector<vector<bool>> f( n 1, vector<bool> (m 1));s1 …...
![](https://img-blog.csdnimg.cn/img_convert/cc3778c8e1999e967058799531b66600.jpeg)
算力时代,算能(SOPHGO)的算力芯片/智算板卡/服务器选型
数字经济时代,算力成为支撑经济社会发展新的关键生产力,全球主要经济体都在加快推进算力战略布局。随着大模型持续选代,模型能力不断增强,带来算力需求持续增长。算力对数字经济和GDP的提高有显著的带动作用,根据IDC、…...
![](https://img-blog.csdnimg.cn/img_convert/abd2f3f8c06551f49e3f610e3f7bbadb.png)
创维汽车开展年中总结会:创新创造·勇开拓 智慧经营·攀高峰
2024年7月3日,回顾上半年的工作成果,总结经验教训,明确下半年的发展方向和重点任务,创维汽车于山西省晋中市榆次区山西联合创维体验中心开展年中总结会。 创维集团、创维汽车创始人黄宏生;开沃集团联合创始人、首席执…...
![](https://img-blog.csdnimg.cn/direct/1286981d301c45ba9ed61c4e539e5283.png)
MyBatis1(JDBC编程和ORM模型 MyBatis简介 实现增删改查 MyBatis生命周期)
目录 一、JDBC编程和ORM模型 1. JDBC回顾 2. JDBC的弊端 3. ORM模型 Mybatis和hibernate 区别: 4. mybatis 解决了jdbc 的问题 二、MyBatis简介 1. MyBatis快速开始 1.1 导入jar包 1.2 引入 mybatis-config.xml 配置文件 1.3 引入 Mapper 映射文件 1.3 测试 …...
![](https://img-blog.csdnimg.cn/direct/307f0f30ad9d4a90b88b459e80183518.png)
如何在忘记密码的情况下重置Realme手机?
欢迎阅读我们关于如何在有或没有密码的情况下重置Realme手机的综合指南。无论您是忘记了密码,还是只是需要将设备恢复到出厂设置,我们都会为您提供所需的专业提示和技术专长。 发现分步说明、专家提示和行之有效的方法,轻松重新控制您的 Rea…...
![](https://www.ngui.cc/images/no-images.jpg)
c++【入门】加乘算式
限制 时间限制 : 1 秒 内存限制 : 128 MB 题目 我们现在要做一个非常简单的题目,实现一个完整的程序。当它运行起来后,我们希望通过键盘输入三个整数,我们先把它们称为 a 、 b 和 c 。然后把他们的代入 (a b) * c 作为结果给存到sum&…...
![](https://img-blog.csdnimg.cn/direct/60ffb5d2599e4dfbb1356dff6e71883c.png)
vscode jupyter选择Python环境时找不到我安装的Python
在一些情况下,我们需要自己安装一个Python,在选择内核是可能找不到指定的Python版本, 再次打开内核选择页面就能看到Python环境了 注意先到指定环境下安装依赖包: ./python3 pip install ipykernel notebook jupyter...
![](https://i-blog.csdnimg.cn/direct/ca76e0724ec246d7bf68aa0cb4012872.png)
Linux 端口
什么是虚拟端口 计算机程序之间的通讯,通过IP只能锁定计算机,但是无法锁定具体的程序。通过端口可以锁定计算机上具体的程序,确保程序之间进行沟通。 IP地址相当于小区地址,在小区内可以有许多用户(程序)&…...
![](https://www.ngui.cc/images/no-images.jpg)
JeepAvenger4xe,1.2T+双电机
近日,懂车之道获悉,Jeep品牌发布Avenger 4xe车型官图,据悉,新车预计今年晚些时候在欧洲上市。下面,和大家一起来看看新车的产品力如何?外观上,Avenger 4xe新车造型更为精致,封闭式七孔格栅装饰,前大灯组合,引擎上更是有多条筋线,下格栅造型硬朗,下包围采用大量黑色…...
![](https://www.ngui.cc/images/no-images.jpg)
宾利发布“Edition8”限量版庆祝“V8”时代的结束!
Hello,大家好,我是Fighting小马。宾利声称自2012年推出最新一代V8发动机以来,已将超过53,000台纯手工制造的V8发动机应用于各种车型。但一则最新消息让人略有伤感,这家英国汽车制造商表示,在非插电式混合动力的某些宾利车型上V8发动机很快将会消失。也许喜欢宾利的你听到这…...
![](https://mz.eastday.com/64138836.jpeg)
申城下周晴雨参半,高考期间多阴雨天气
东方网记者包永婷6月2日报道:这个双休日的天气很配合儿童节和出行,上海今天较昨天更加晴朗,蓝天白云的景象也赏心悦目。气温加快上升的步伐,徐家汇站最高气温止步28.2℃,午间有些热有些晒,早晚依旧延续着舒适的体感。明天会是近期最晴最热的一天,多云到晴为主,气温起步2…...
![](https://rmrbcmsonline.peopleapp.com/upload/rmh/voice/202405/cd312d3b8ab27281f36ec2d4e881cd72.jpg?x-oss-process=image/auto-orient,1)
《中国人大》2024年第9期目录
制图:李洪兴编校:宫宜希责编:张钰钗...
![](https://img-blog.csdnimg.cn/direct/a6997f8c5f7f43acb1598f2877e412e2.png)
C++笔试强训day36
目录 1.提取不重复的整数 2.【模板】哈夫曼编码 3.abb 1.提取不重复的整数 链接https://www.nowcoder.com/practice/253986e66d114d378ae8de2e6c4577c1?tpId37&tqId21232&ru/exam/oj 按照题意模拟就行,记得从右往左遍历 #include <iostream> usi…...
![](https://img-blog.csdnimg.cn/direct/7c13d1e0673641f68d0f073b68225f6a.png)
手机端如何访问本地vue+vite项目,实现实时调试?
一、应用场景 h5(vuevite)嵌入app后,出现某种问题时,需要每次发布坏境后,才能才看效果,这种来回很耗时间,本文章在于解决手机端直接访问本地启动应用项目,无需重复发布坏境 二、实…...