• 【Python+requests+unittest+excel】实现接口自动化测试框架


    一、框架结构:

     工程目录

    二、Case文件设计

    三、基础包 base3.1 封装get/post请求(runmethon.py)

    1. 1 import requests
    2. 2 import json
    3. 3 class RunMethod:
    4. 4 def post_main(self,url,data,header=None):
    5. 5 res = None
    6. 6 if header !=None:
    7. 7 res = requests.post(url=url,data=data,headers=header)
    8. 8 else:
    9. 9 res = requests.post(url=url,data=data)
    10. 10 return res.json()
    11. 11
    12. 12 def get_main(self,url,data=None,header=None):
    13. 13 res = None
    14. 14 if header !=None:
    15. 15 res = requests.get(url=url,data=data,headers=header,verify=False)
    16. 16 else:
    17. 17 res = requests.get(url=url,data=data,verify=False)
    18. 18 return res.json()
    19. 19
    20. 20 def run_main(self,method,url,data=None,header=None):
    21. 21 res = None
    22. 22 if method == 'Post':
    23. 23 res = self.post_main(url,data,header)
    24. 24 else:
    25. 25 res = self.get_main(url,data,header)
    26. 26 return json.dumps(res,ensure_ascii=False,sort_keys=True,indent=2)

    3.2 封装mock(mock.py)

    1. 1 from mock import mock
    2. 2 #模拟mock 封装
    3. 3 def mock_test(mock_method,request_data,url,method,response_data):
    4. 4 mock_method = mock.Mock(return_value=response_data)
    5. 5 res = mock_method(url,method,request_data)
    6. 6 return res

    四、数据操作包 operation_data4.1 获取excel单元格中的内容(get_data.py)  

    1. 1 #coding:utf-8
    2. 2 from tool.operation_excel import OperationExcel
    3. 3 import data_config
    4. 4 from tool.operation_json import OperetionJson
    5. 5 from tool.connect_db import OperationMysql
    6. 6 class GetData:
    7. 7 def __init__(self):
    8. 8 self.opera_excel = OperationExcel()
    9. 9
    10. 10 #去获取excel行数,就是case的个数
    11. 11 def get_case_lines(self):
    12. 12 return self.opera_excel.get_lines()
    13. 13
    14. 14 #获取是否执行
    15. 15 def get_is_run(self,row):
    16. 16 flag = None
    17. 17 col = int(data_config.get_run())
    18. 18 run_model = self.opera_excel.get_cell_value(row,col)
    19. 19 if run_model == 'yes':
    20. 20 flag = True
    21. 21 else:
    22. 22 flag = False
    23. 23 return flag
    24. 24
    25. 25 #是否携带header
    26. 26 def is_header(self,row):
    27. 27 col = int(data_config.get_header())
    28. 28 header = self.opera_excel.get_cell_value(row,col)
    29. 29 if header != '':
    30. 30 return header
    31. 31 else:
    32. 32 return None
    33. 33
    34. 34 #获取请求方式
    35. 35 def get_request_method(self,row):
    36. 36 col = int(data_config.get_run_way())
    37. 37 request_method = self.opera_excel.get_cell_value(row,col)
    38. 38 return request_method
    39. 39
    40. 40 #获取url
    41. 41 def get_request_url(self,row):
    42. 42 col = int(data_config.get_url())
    43. 43 url = self.opera_excel.get_cell_value(row,col)
    44. 44 return url
    45. 45
    46. 46 #获取请求数据
    47. 47 def get_request_data(self,row):
    48. 48 col = int(data_config.get_data())
    49. 49 data = self.opera_excel.get_cell_value(row,col)
    50. 50 if data == '':
    51. 51 return None
    52. 52 return data
    53. 53
    54. 54 #通过获取关键字拿到data数据
    55. 55 def get_data_for_json(self,row):
    56. 56 opera_json = OperetionJson()
    57. 57 request_data = opera_json.get_data(self.get_request_data(row))
    58. 58 return request_data
    59. 59
    60. 60 #获取预期结果
    61. 61 def get_expcet_data(self,row):
    62. 62 col = int(data_config.get_expect())
    63. 63 expect = self.opera_excel.get_cell_value(row,col)
    64. 64 if expect == '':
    65. 65 return None
    66. 66 return expect
    67. 67
    68. 68 #通过sql获取预期结果
    69. 69 def get_expcet_data_for_mysql(self,row):
    70. 70 op_mysql = OperationMysql()
    71. 71 sql = self.get_expcet_data(row)
    72. 72 res = op_mysql.search_one(sql)
    73. 73 return res.decode('unicode-escape')
    74. 74
    75. 75 def write_result(self,row,value):
    76. 76 col = int(data_config.get_result())
    77. 77 self.opera_excel.write_value(row,col,value)
    78. 78
    79. 79 #获取依赖数据的key
    80. 80 def get_depend_key(self,row):
    81. 81 col = int(data_config.get_data_depend())
    82. 82 depent_key = self.opera_excel.get_cell_value(row,col)
    83. 83 if depent_key == "":
    84. 84 return None
    85. 85 else:
    86. 86 return depent_key
    87. 87
    88. 88 #判断是否有case依赖
    89. 89 def is_depend(self,row):
    90. 90 col = int(data_config.get_case_depend())
    91. 91 depend_case_id = self.opera_excel.get_cell_value(row,col)
    92. 92 if depend_case_id == "":
    93. 93 return None
    94. 94 else:
    95. 95 return depend_case_id
    96. 96
    97. 97 #获取数据依赖字段
    98. 98 def get_depend_field(self,row):
    99. 99 col = int(data_config.get_field_depend())
    100. 100 data = self.opera_excel.get_cell_value(row,col)
    101. 101 if data == "":
    102. 102 return None
    103. 103 else:
    104. 104 return data

    4.2 获取excel中每个列(data_config.py)

    1. 1 #coding:utf-8
    2. 2 class global_var:
    3. 3 #case_id
    4. 4 Id = '0'
    5. 5 request_name = '1'
    6. 6 url = '2'
    7. 7 run = '3'
    8. 8 request_way = '4'
    9. 9 header = '5'
    10. 10 case_depend = '6'
    11. 11 data_depend = '7'
    12. 12 field_depend = '8'
    13. 13 data = '9'
    14. 14 expect = '10'
    15. 15 result = '11'
    16. 16 #获取caseid
    17. 17 def get_id():
    18. 18 return global_var.Id
    19. 19
    20. 20 #获取url
    21. 21 def get_url():
    22. 22 return global_var.url
    23. 23
    24. 24 def get_run():
    25. 25 return global_var.run
    26. 26
    27. 27 def get_run_way():
    28. 28 return global_var.request_way
    29. 29
    30. 30 def get_header():
    31. 31 return global_var.header
    32. 32
    33. 33 def get_case_depend():
    34. 34 return global_var.case_depend
    35. 35
    36. 36 def get_data_depend():
    37. 37 return global_var.data_depend
    38. 38
    39. 39 def get_field_depend():
    40. 40 return global_var.field_depend
    41. 41
    42. 42 def get_data():
    43. 43 return global_var.data
    44. 44
    45. 45 def get_expect():
    46. 46 return global_var.expect
    47. 47
    48. 48 def get_result():
    49. 49 return global_var.result
    50. 50
    51. 51 def get_header_value():
    52. 52 return global_var.header

    4.3 解决数据依赖(dependent.py )

    1. 1 #coding:utf-8
    2. 2 import sys
    3. 3 import json
    4. 4 sys.path.append('C:/Users/lxz/Desktop/InterFace_JIA')
    5. 5 from tool.operation_excel import OperationExcel
    6. 6 from base.runmethod import RunMethod
    7. 7 from operation_data.get_data import GetData
    8. 8 from jsonpath_rw import jsonpath,parse
    9. 9 class DependdentData:
    10. 10 def __init__(self,case_id):
    11. 11 self.case_id = case_id
    12. 12 self.opera_excel = OperationExcel()
    13. 13 self.data = GetData()
    14. 14
    15. 15 #通过case_id去获取该case_id的整行数据
    16. 16 def get_case_line_data(self):
    17. 17 rows_data = self.opera_excel.get_rows_data(self.case_id)
    18. 18 return rows_data
    19. 19
    20. 20 #执行依赖测试,获取结果
    21. 21 def run_dependent(self):
    22. 22 run_method = RunMethod()
    23. 23 row_num = self.opera_excel.get_row_num(self.case_id)
    24. 24 request_data = self.data.get_data_for_json(row_num)
    25. 25 #header = self.data.is_header(row_num)
    26. 26 method = self.data.get_request_method(row_num)
    27. 27 url = self.data.get_request_url(row_num)
    28. 28 res = run_method.run_main(method,url,request_data)
    29. 29 return json.loads(res)
    30. 30
    31. 31 #根据依赖的key去获取执行依赖测试case的响应,然后返回
    32. 32 def get_data_for_key(self,row):
    33. 33 depend_data = self.data.get_depend_key(row)
    34. 34 response_data = self.run_dependent()
    35. 35 json_exe = parse(depend_data)
    36. 36 madle = json_exe.find(response_data)
    37. 37 return [math.value for math in madle][0]
    38. 38
    39. 39 if __name__ == '__main__':
    40. 40 order = {
    41. 41 "data": {
    42. 42 "_input_charset": "utf-8",
    43. 43 "body": "京东订单-1710141907182334",
    44. 44 "it_b_pay": "1d",
    45. 45 "notify_url": "http://order.imooc.com/pay/notifyalipay",
    46. 46 "out_trade_no": "1710141907182334",
    47. 47 "partner": "2088002966755334",
    48. 48 "payment_type": "1",
    49. 49 "seller_id": "yangyan01@tcl.com",
    50. 50 "service": "mobile.securitypay.pay",
    51. 51 "sign": "kZBV53KuiUf5HIrVLBCcBpWDg%2FnzO%2BtyEnBqgVYwwBtDU66Xk8VQUTbVOqDjrNymCupkVhlI%2BkFZq1jOr8C554KsZ7Gk7orC9dDbQl
    52. pr%2BaMmdjO30JBgjqjj4mmM%2Flphy9Xwr0Xrv46uSkDKdlQqLDdGAOP7YwOM2dSLyUQX%2Bo4%3D",
    53. 52 "sign_type": "RSA",
    54. 53 "string": "_input_charset=utf-8&body=京东订单-1710141907182334&it_b_pay=1d¬ify_url=http://order.imooc.com/pay/
    55. notifyalipay&out_trade_no=1710141907182334&partner=2088002966755334&payment_type=1&seller_id=yangyan01@
    56. tcl.com&service=mobile.securitypay.pay&subject=京东订单-1710141907182334&total_fee=299&sign=kZBV53KuiUf5H
    57. IrVLBCcBpWDg%2FnzO%2BtyEnBqgVYwwBtDU66Xk8VQUTbVOqDjrNymCupkVhlI%2BkFZq1jOr8C554KsZ7Gk7orC9dDbQlpr%2BaMmdjO30
    58. JBgjqjj4mmM%2Flphy9Xwr0Xrv46uSkDKdlQqLDdGAOP7YwOM2dSLyUQX%2Bo4%3D&sign_type=RSA",
    59. 54 "subject": "京东订单-1710141907182334",
    60. 55 "total_fee": 299
    61. 56 },
    62. 57 "errorCode": 1000,
    63. 58 "errorDesc": "成功",
    64. 59 "status": 1,
    65. 60 "timestamp": 1507979239100
    66. 61 }
    67. 62 res = "data.out_trade_no"
    68. 63 json_exe = parse(res)
    69. 64 madle = json_exe.find(order)
    70. 65 print [math.value for math in madle][0]

    五、工具类包 tool5.1 操作excel (operation_excel.py)

    1. 1 #coding:utf-8
    2. 2 import xlrd
    3. 3 from xlutils.copy import copy
    4. 4 class OperationExcel:
    5. 5 def __init__(self,file_name=None,sheet_id=None):
    6. 6 if file_name:
    7. 7 self.file_name = file_name
    8. 8 self.sheet_id = sheet_id
    9. 9 else:
    10. 10 self.file_name = '../dataconfig/case1.xls'
    11. 11 self.sheet_id = 0
    12. 12 self.data = self.get_data()
    13. 13
    14. 14 #获取sheets的内容
    15. 15 def get_data(self):
    16. 16 data = xlrd.open_workbook(self.file_name)
    17. 17 tables = data.sheets()[self.sheet_id]
    18. 18 return tables
    19. 19
    20. 20 #获取单元格的行数
    21. 21 def get_lines(self):
    22. 22 tables = self.data
    23. 23 return tables.nrows
    24. 24
    25. 25 #获取某一个单元格的内容
    26. 26 def get_cell_value(self,row,col):
    27. 27 return self.data.cell_value(row,col)
    28. 28
    29. 29 #写入数据
    30. 30 def write_value(self,row,col,value):
    31. 31 '''
    32. 32 写入excel数据
    33. 33 row,col,value
    34. 34 '''
    35. 35 read_data = xlrd.open_workbook(self.file_name)
    36. 36 write_data = copy(read_data)
    37. 37 sheet_data = write_data.get_sheet(0)
    38. 38 sheet_data.write(row,col,value)
    39. 39 write_data.save(self.file_name)
    40. 40
    41. 41 #根据对应的caseid 找到对应行的内容
    42. 42 def get_rows_data(self,case_id):
    43. 43 row_num = self.get_row_num(case_id)
    44. 44 rows_data = self.get_row_values(row_num)
    45. 45 return rows_data
    46. 46
    47. 47 #根据对应的caseid找到对应的行号
    48. 48 def get_row_num(self,case_id):
    49. 49 num = 0
    50. 50 clols_data = self.get_cols_data()
    51. 51 for col_data in clols_data:
    52. 52 if case_id in col_data:
    53. 53 return num
    54. 54 num = num+1
    55. 55
    56. 56
    57. 57 #根据行号,找到该行的内容
    58. 58 def get_row_values(self,row):
    59. 59 tables = self.data
    60. 60 row_data = tables.row_values(row)
    61. 61 return row_data
    62. 62
    63. 63 #获取某一列的内容
    64. 64 def get_cols_data(self,col_id=None):
    65. 65 if col_id != None:
    66. 66 cols = self.data.col_values(col_id)
    67. 67 else:
    68. 68 cols = self.data.col_values(0)
    69. 69 return cols
    70. 70
    71. 71
    72. 72 if __name__ == '__main__':
    73. 73 opers = OperationExcel()
    74. 74 print opers.get_cell_value(1,2)

    5.2判断字符串包含,判断字典是否相等(common_util.py)

    1. 1 #coding:utf-8
    2. 2 import json
    3. 3 class CommonUtil:
    4. 4 def is_contain(self,str_one,str_two):
    5. 5 '''
    6. 6 判断一个字符串是否再另外一个字符串中
    7. 7 str_one:查找的字符串
    8. 8 str_two:被查找的字符串
    9. 9 '''
    10. 10 flag = None
    11. 11 if isinstance(str_one,unicode):
    12. 12 str_one = str_one.encode('unicode-escape').decode('string_escape')
    13. 13 return cmp(str_one,str_two)
    14. 14 if str_one in str_two:
    15. 15 flag = True
    16. 16 else:
    17. 17 flag = False
    18. 18 return flag
    19. 19
    20. 20
    21. 21 def is_equal_dict(self,dict_one,dict_two):
    22. 22 '''
    23. 23 判断两个字典是否相等
    24. 24 '''
    25. 25 if isinstance(dict_one,str):
    26. 26 dict_one = json.loads(dict_one)
    27. 27 if isinstance(dict_two,str):
    28. 28 dict_two = json.loads(dict_two)
    29. 29 return cmp(dict_one,dict_two)

    5.3 操作header(operation_herder.py)

    1. 1 #coding:utf-8
    2. 2 import requests
    3. 3 import json
    4. 4 from operation_json import OperetionJson
    5. 5
    6. 6
    7. 7 class OperationHeader:
    8. 8
    9. 9 def __init__(self,response):
    10. 10 self.response = json.loads(response)
    11. 11
    12. 12 def get_response_url(self):
    13. 13 '''
    14. 14 获取登录返回的token的url
    15. 15 '''
    16. 16 url = self.response['data']['url'][0]
    17. 17 return url
    18. 18
    19. 19 def get_cookie(self):
    20. 20 '''
    21. 21 获取cookie的jar文件
    22. 22 '''
    23. 23 url = self.get_response_url()+"&callback=jQuery21008240514814031887_1508666806688&_=1508666806689"
    24. 24 cookie = requests.get(url).cookies
    25. 25 return cookie
    26. 26
    27. 27 def write_cookie(self):
    28. 28 cookie = requests.utils.dict_from_cookiejar(self.get_cookie())
    29. 29 op_json = OperetionJson()
    30. 30 op_json.write_data(cookie)
    31. 31
    32. 32 if __name__ == '__main__':
    33. 33
    34. 34 url = "http://www.jd.com/passport/user/login"
    35. 35 data = {
    36. 36 "username":"18513199586",
    37. 37 "password":"111111",
    38. 38 "verify":"",
    39. 39 "referer":"https://www.jd.com"
    40. 40 }
    41. 41 res = json.dumps(requests.post(url,data).json())
    42. 42 op_header = OperationHeader(res)
    43. 43 op_header.write_cookie()

    5.4 操作json文件(operation_json.py)

    1. 1 #coding:utf-8
    2. 2 import json
    3. 3 class OperetionJson:
    4. 4
    5. 5 def __init__(self,file_path=None):
    6. 6 if file_path == None:
    7. 7 self.file_path = '../dataconfig/user.json'
    8. 8 else:
    9. 9 self.file_path = file_path
    10. 10 self.data = self.read_data()
    11. 11
    12. 12 #读取json文件
    13. 13 def read_data(self):
    14. 14 with open(self.file_path) as fp:
    15. 15 data = json.load(fp)
    16. 16 return data
    17. 17
    18. 18 #根据关键字获取数据
    19. 19 def get_data(self,id):
    20. 20 print type(self.data)
    21. 21 return self.data[id]
    22. 22
    23. 23 #写json
    24. 24 def write_data(self,data):
    25. 25 with open('../dataconfig/cookie.json','w') as fp:
    26. 26 fp.write(json.dumps(data))
    27. 27
    28. 28
    29. 29
    30. 30 if __name__ == '__main__':
    31. 31 opjson = OperetionJson()
    32. 32 print opjson.get_data('shop')

    5.5 操作数据库(connect_db.py)

    1. 1 #coding:utf-8
    2. 2 import MySQLdb.cursors
    3. 3 import json
    4. 4 class OperationMysql:
    5. 5 def __init__(self):
    6. 6 self.conn = MySQLdb.connect(
    7. 7 host='localhost',
    8. 8 port=3306,
    9. 9 user='root',
    10. 10 passwd='123456',
    11. 11 db='le_study',
    12. 12 charset='utf8',
    13. 13 cursorclass=MySQLdb.cursors.DictCursor
    14. 14 )
    15. 15 self.cur = self.conn.cursor()
    16. 16
    17. 17 #查询一条数据
    18. 18 def search_one(self,sql):
    19. 19 self.cur.execute(sql)
    20. 20 result = self.cur.fetchone()
    21. 21 result = json.dumps(result)
    22. 22 return result
    23. 23
    24. 24 if __name__ == '__main__':
    25. 25 op_mysql = OperationMysql()
    26. 26 res = op_mysql.search_one("select * from web_user where Name='ailiailan'")
    27. 27 print res

    5.6 发送报告邮件(send_email.py)

    1. 1 #coding:utf-8
    2. 2 import smtplib
    3. 3 from email.mime.text import MIMEText
    4. 4 class SendEmail:
    5. 5 global send_user
    6. 6 global email_host
    7. 7 global password
    8. 8 email_host = "smtp.163.com"
    9. 9 send_user = "jiaxiaonan666@163.com"
    10. 10 password = "jia_668"
    11. 11 def send_mail(self,user_list,sub,content):
    12. 12 user = "jiaxiaonan"+"<"+send_user+">"
    13. 13 message = MIMEText(content,_subtype='plain',_charset='utf-8')
    14. 14 message['Subject'] = sub
    15. 15 message['From'] = user
    16. 16 message['To'] = ";".join(user_list)
    17. 17 server = smtplib.SMTP()
    18. 18 server.connect(email_host)
    19. 19 server.login(send_user,password)
    20. 20 server.sendmail(user,user_list,message.as_string())
    21. 21 server.close()
    22. 22
    23. 23 def send_main(self,pass_list,fail_list):
    24. 24 pass_num = float(len(pass_list))
    25. 25 fail_num = float(len(fail_list))
    26. 26 count_num = pass_num+fail_num
    27. 27 #90%
    28. 28 pass_result = "%.2f%%" %(pass_num/count_num*100)
    29. 29 fail_result = "%.2f%%" %(fail_num/count_num*100)
    30. 30
    31. 31
    32. 32 user_list = ['609037724@qq.com']
    33. 33 sub = "接口自动化测试报告"
    34. 34 content = "此次一共运行接口个数为%s个,通过个数为%s个,失败个数为%s,通过率为%s,失败率为%s" %(count_num,pass_num,fail_num,pass_result,fail_result )
    35. 35 self.send_mail(user_list,sub,content)
    36. 36
    37. 37 if __name__ == '__main__':
    38. 38 sen = SendEmail()
    39. 39 sen.send_main([1,2,3,4],[2,3,4,5,6,7])

    六、主函数run_test.py

    1. 1 #coding:utf-8
    2. 2 import sys
    3. 3 sys.path.append("C:/Users/lxz/Desktop/InterFace_JIA")
    4. 4 from base.runmethod import RunMethod
    5. 5 from operation_data.get_data import GetData
    6. 6 from tool.common_util import CommonUtil
    7. 7 from operation_data.dependent_data import DependdentData
    8. 8 from tool.send_email import SendEmail
    9. 9 from tool.operation_header import OperationHeader
    10. 10 from tool.operation_json import OperetionJson
    11. 11 class RunTest:
    12. 12 def __init__(self):
    13. 13 self.run_method = RunMethod()
    14. 14 self.data = GetData()
    15. 15 self.com_util = CommonUtil()
    16. 16 self.send_mai = SendEmail()
    17. 17
    18. 18 #程序执行的
    19. 19 def go_on_run(self):
    20. 20 res = None
    21. 21 pass_count = []
    22. 22 fail_count = []
    23. 23 #10 0,1,2,3
    24. 24 rows_count = self.data.get_case_lines()
    25. 25 for i in range(1,rows_count):
    26. 26 is_run = self.data.get_is_run(i)
    27. 27 if is_run:
    28. 28 url = self.data.get_request_url(i)
    29. 29 method = self.data.get_request_method(i)
    30. 30 request_data = self.data.get_data_for_json(i)
    31. 31 expect = self.data.get_expcet_data_for_mysql(i)
    32. 32 header = self.data.is_header(i)
    33. 33 depend_case = self.data.is_depend(i)
    34. 34 if depend_case != None:
    35. 35 self.depend_data = DependdentData(depend_case)
    36. 36 #获取的依赖响应数据
    37. 37 depend_response_data = self.depend_data.get_data_for_key(i)
    38. 38 #获取依赖的key
    39. 39 depend_key = self.data.get_depend_field(i)
    40. 40 request_data[depend_key] = depend_response_data
    41. 41 if header == 'write':
    42. 42 res = self.run_method.run_main(method,url,request_data)
    43. 43 op_header = OperationHeader(res)
    44. 44 op_header.write_cookie()
    45. 45
    46. 46 elif header == 'yes':
    47. 47 op_json = OperetionJson('../dataconfig/cookie.json')
    48. 48 cookie = op_json.get_data('apsid')
    49. 49 cookies = {
    50. 50 'apsid':cookie
    51. 51 }
    52. 52 res = self.run_method.run_main(method,url,request_data,cookies)
    53. 53 else:
    54. 54 res = self.run_method.run_main(method,url,request_data)
    55. 55
    56. 56 if self.com_util.is_equal_dict(expect,res) == 0:
    57. 57 self.data.write_result(i,'pass')
    58. 58 pass_count.append(i)
    59. 59 else:
    60. 60 self.data.write_result(i,res)
    61. 61 fail_count.append(i)
    62. 62 self.send_mai.send_main(pass_count,fail_count)
    63. 63
    64. 64 #将执行判断封装
    65. 65 #def get_cookie_run(self,header):
    66. 66
    67. 67
    68. 68 if __name__ == '__main__':
    69. 69 run = RunTest()
    70. 70 run.go_on_run()

    最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

    这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你! 

  • 相关阅读:
    从尾到头打印链表
    一文带你深入浅出 MongoEngine 经典查询【内附详细案例】
    什么软件可以图片转文字?这几个软件值得收藏
    docker harbor 私有仓库
    损失函数和目标函数|知识补充
    注解(Annotation)
    考 PMP 证书真有用吗?
    idea中java版本设置
    自动翻译 android/res/values/strings.xml
    VUE DPlayer编译
  • 原文地址:https://blog.csdn.net/2301_78276982/article/details/134399311