【博客系統測試報告】---接口自動化測試

目錄

1、需求分析

2、挑選接口

3、設計博客系統的測試用例

4、設計自動化測試框架

test_add.py:

test_detail.py:

test_getAuthorInfo.py:

test_getUserInfo:

test_list.py:

test_login.py:

logger_util.py:

request_util.py:

yaml_util.py:


1、需求分析

根據業務需求,明確接口需要實現的具體功能,如數據的獲取、修改、刪除等操作,以及接口的輸入輸出要求。分析接口直接的依賴關系,確定接口的調用順序和依賴條件。

2、挑選接口

優先選擇核心業務接口、頻繁使用的接口以及容易出錯的接口進行自動化測試。

功能復雜度高、高風險功能、重復性高

我們對博客系統的每個接口都進行自動化測試

1、登錄接口:

2、博客列表頁

3、編輯博客頁

4、獲取用戶詳情

5、獲取博客詳情

6、獲取登錄用戶信息

3、設計博客系統的測試用例

請求方法、請求參數、需要用戶憑證就可以分為登錄狀態和未登錄狀態

針對接口設計測試用例,必須要按照接口文檔來進行設計,除此之外,最好能夠看到接口對應的代碼,查看接口存在的不同響應,針對不同的響應來設計測試用例。

4、設計自動化測試框架

語言選擇:Python

技術棧:pytest框架、request模塊、PyYAML模塊、jsonschema模塊、allure-pytest模塊、logging模塊

集成開發環境:pycharm

如果下一個項目要用和上一個相同的配置的話,可以在上一個項目中創建一個txt文件,將需要安裝的框架和模塊寫進入,然后將此文件在文件夾中復制到新的項目的文件夾里。

再使用就可以將框架和模塊配置好

pip install -r 文件名

我們對每個接口都設計一個測試文件,但是要注意文件名要符合pytest的收集規則。把通用的方法進行封裝

整個項目架構

test_add.py:

'''
添加博客接口
'''
import pytest
from jsonschema import validatefrom utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestAdd:url = host + "blog/add"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "boolean"}}}#未登錄狀態下請求add接口 狀態碼都是401def test_add_noLogin(self):r = Request().post(url=self.url)r.status_code == 401#添加博客--添加成功+添加失敗@pytest.mark.parametrize("add",[#添加成功{"title": "接口自動化標題","content": "接口自動化內容","data" : True #在參數化中不僅可以配置請求參數,響應數據中想要校驗的關鍵字段也可以在這配置},#標題為空{"title": "","content": "接口自動化內容","data": False},#內容為空{"title": "接口自動化標題","content": "","data": False},#標題和內容都為空{"title": "","content": "","data": False}])def test_add(self,add):#請求頭token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token}#請求參數json = {"title":add["title"],"content":add["content"]}#發送請求r = Request().get(url=self.url,headers=header,json=json)#驗證jsonschemavalidate(instance=r.json(),schema=self.schema)#instance是接口的返回值#assert關鍵字段的值assert r.json()['data'] == add['data']

test_detail.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yaml#博客詳情頁需要有效的blogId,這個id必須要從博客列表頁的返回值里面的id來獲取
class TestDetail:url = host + "blog/getBlogDetail" #注意blog前面不要加/schema = {"type": "object","additionalProperties": False,"required": [ "code","errMsg","data"],"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "object","required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],"additionalProperties": False,"properties": {"id": {"type": "number"},"title": {"type": "string"},"content": {"type": "string"},"userId": {"type": "number"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"},"loginUser": {"type": "boolean"}}}}}#未登錄狀態下訪問博客詳情頁def test_detail_noLogin(self):url = self.url + "?blogId=1234"r = Request().get(url=url)assert r.status_code == 401#登錄狀態下def test_detail_login(self):url = self.url + "?blogId=" + str(read_yaml("data.yml","blogId"))token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token}r = Request().get(url=url,headers=header)#先通過jsonschema進行校驗validate(instance=r.json(),schema=self.schema)#再進行assert關鍵字段的校驗assert r.json()["code"] == "SUCCESS"#博客詳情頁---blogId錯誤#為空、不存在的、非數字、負數、過長的數字@pytest.mark.parametrize("blogId",["",1234,"nihao",-100,99999999999999999999999999999])def test_detail_fail(self,blogId):# url = self.url + blogId  url本身就是字符串 要拼接blogId必須也是字符串的格式url = self.url#配置參數#request發送請求,參數有三種格式#params:一般是發送get請求可以拼接再url上#data:一般是發送一個post格式的表單的請求#json:發送post方法json格式的參數params = {"blogId":blogId}token = read_yaml("data.yml", "user_token_header")header = {"user_token_header": token}r = Request().get(url=url, headers=header)expect_json = {"code": "FAIL","errMsg": "內部錯誤, 請聯系管理員","data": None}assert r.json() == expect_json

test_getAuthorInfo.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestgetAuthorInfo:url = host + "user/getAuthorInfo"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": ["object","null"],"required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],"additionalProperties": False,"properties": {"id": {"type": "number"},"userName": {"type": "string"},"password": {"type": "string"},"githubUrl": {"type": "string"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"}}}}}#未登錄狀態下訪問接口def test_etAuthorInfo_nologin(self):url = self.url + "?blogId=162202"r = Request().get(url=url)assert r.status_code == 401#登錄狀態下正確請求#有效的blogIddef test_etAuthorInfo(self):blogId = read_yaml("data.yml","blogId")url = self.url + "?blogId=" + str(blogId)#讀取用戶登錄憑證token = read_yaml("data.yml","user_token_header")header = {"user_token_header" : token}#發起請求r = Request().get(url=url,headers = header)#校驗jsonschemavalidate(instance=r.json(),schema=self.schema)assert r.json()['code'] == "SUCCESS"#登錄狀態下異常請求#blogId異常@pytest.mark.parametrize("blogId,expected_code", [("","FAIL"),(1234,"FAIL"),("nihao","FAIL"),(-100,"SUCCESS"),(99999999999999999999999999999,"FAIL")])def test_etAuthorInfo_fail(self,blogId,expected_code):url = self.url#把blogId通過參數進行配置params = {"blogId":blogId}# 讀取用戶登錄憑證token = read_yaml("data.yml", "user_token_header")header = {"user_token_header": token}# 發起請求r = Request().get(url=url, headers=header,params = params)validate(instance=r.json(),schema=self.schema)assert r.json()["code"] == expected_code

test_getUserInfo:

from jsonschema import validatefrom utils.request_util import host, Request
from utils.yaml_util import read_yamlclass TestgetUserInfo:url = host + "user/getUserInfo"schema = {"type": "object","required": ["code","errMsg","data"],"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "object","required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],"properties": {"id": {"type": "number"},"userName": {"type": "string"},"password": {"type": "string"},"githubUrl": {"type": "string"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"}}}}}#未登錄狀態下請求接口def test_getUserInfo_nologin(self):r = Request().get(url = self.url)assert r.status_code == 401#登錄狀態下請求接口def test_getUserInfo_login(self):token = read_yaml("data.yml","user_token_header")header = {"user_token_header" : token}r = Request().get(url = self.url,headers = header)#校驗jsonschemavalidate(instance=r.json(),schema=self.schema)#通過斷言校驗關鍵數據assert r.json()["code"] == "SUCCESS"

test_list.py:

from jsonschema import validate
import pytest
from utils.request_util import host, Request
from utils.yaml_util import read_yaml, write_yaml@pytest.mark.order(2)
class TestList:url = host + "blog/getList"schema = {"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": "array","items": {"type": "object","required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],"additionalProperties": False,"properties": {"id": {"type": "number"},"title": {"type": "string"},"content": {"type": "string"},"userId": {"type": "number"},"deleteFlag": {"type": "number"},"createTime": {"type": "string"},"updateTime": {"type": "string"},"loginUser": {"type": "boolean"}}}}}}#未登錄狀態def test_list_nologin(self):r = Request().get(url=self.url)assert r.status_code == 401#請求列表頁---登錄狀態def test_list_login(self):token = read_yaml("data.yml","user_token_header")header = {"user_token_header":token#從yaml文件中讀取數據}r = Request().get(url=self.url,headers = header)#jsonschema校驗validate(instance=r.json(),schema=self.schema) #將http響應體的內容解析為json格式 第一個參數和第二個參數進行對比#關鍵值的校驗assert r.json()['code'] == "SUCCESS"#提權有效的blogid存儲在yaml文件中blogId = {"blogId":r.json()['data'][0]["id"]}write_yaml("data.yml",blogId)

test_login.py:

'''
登錄--接口自動化測試
url:http://8.137.19.140:9090/user/login   POST
form-data{"username":"zhangsan","password":"123456"}
'''
import re
from lib2to3.pygram import python_grammar_no_print_and_exec_statementfrom jsonschema import validate
from utils.request_util import host,Requestimport pytestfrom utils.yaml_util import write_yaml#參數化+jsonschema校驗+關鍵字段的嚴格校驗@pytest.mark.order(1)
class TestLogin:url = host + "user/login"schema = {  #schema是固定的所以可以定義成全局的"type": "object","required": ["code","errMsg","data"],"additionalProperties": False,   #不能有額外的屬性"properties": {"code": {"type": "string"},"errMsg": {"type": "string"},"data": {"type": ["string","null"]}}}
#我們將異常登錄放在正常登錄之前
#只為了避免正常登錄之后,再進行異常登錄,導致登錄態被清空# 異常登錄@pytest.mark.parametrize("login", [# 錯誤的賬號和密碼{"username": "zhang","password": "123","errMsg" : "用戶不存在"},#錯誤的賬號和正確的密碼{"username": "zhang","password": "123456","errMsg": "用戶不存在"},#正確的賬號和錯誤的密碼{"username": "zhangsan","password": "123","errMsg": "密碼錯誤"},#不存在的賬號{"username": "hhh","password": "333","errMsg": "用戶不存在"},#賬號和密碼都為空{"username": "","password": "","errMsg": "賬號或密碼不能為空"},#過長的賬號{"username": "zhehegdhiiwbcicbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","password": "123456","errMsg": "用戶不存在"},#過長的密碼{"username": "zhangsan","password": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222","errMsg": "密碼錯誤"}])def test_login_fail(self,login):data  = {"username": login["username"],"password": login["password"]}r = Request().post(url=self.url, data=data)validate(instance=r.json(), schema=self.schema)assert r.json()["code"] == "FAIL"#因為用戶會有很多,因此需要使用參數化@pytest.mark.parametrize("login",[{"username":"zhangsan","password":"123456",},{"username":"lisi","password":"123456"}])#成功請求登錄接口def test_login_success(self,login):data = {"username": login["username"],"password": login["password"]}r = Request().post(url=self.url,data = data)#校驗#返回的字段類型是否正確#我們可以進一步進行限制 code和data的值肯定是 SUCCESSvalidate(instance=r.json(),schema=self.schema)#斷言返回的接口assert r.json()["code"] == "SUCCESS"assert re.match('\S{100,}',r.json()['data'])#匹配的是返回值里面的data數據#接口返回的data就是其他接口的登錄憑證#將用戶的登錄憑證保存在yaml文件中token = {"user_token_header" : r.json()['data']}write_yaml("data.yml",token) #把接口返回的數據保存在yaml文件中

logger_util.py:

將日志進行分割

2026-01-01.log

2026-01-01-info.log

2026-01-01-err.log

#打印日志,輸出到日志文件中
import logging
import os.path
import time#繼承父類的logging.Filter 并重寫父類的方法
class infoFilter(logging.Filter):def filter(self, record):return record.levelno == logging.INFO
class errFilter(logging.Filter):def filter(self, record):return record.levelno == logging.ERRORclass logger:#1、獲取日志對象---定義類方法@classmethod@classmethoddef getlog(cls): #注意!!!cls.logger = logging.getLogger(__name__) #創建一個日志對象  再給一個模塊名cls.logger.setLevel(logging.DEBUG)#將日志輸出到日志文件中 --- 希望將日志進行分割,按照日期分開保存,避免日志文件過大不好管理#---將日志按照級別進行分割# 保證logs文件夾存在 必須創建好了才能往里面保存日志文件LOG_PATH = "../logs/"  #當前項目路徑下創建一個文件夾if not os.path.exists(LOG_PATH):  # 判斷這個路徑是否存在os.mkdir(LOG_PATH)  # 如果文件夾不存在就去創建文件夾'''logs./logs/2026-01-01.log./logs/2026-01-01-info.log./logs/2026-01-01-err.log'''now = time.strftime("%Y-%m-%d")log_name = LOG_PATH + now + ".log"info_log_name = LOG_PATH + now + "-info.log"err_log_name = LOG_PATH + now + "-err.log"#2、創建文件處理器all_handler = logging.FileHandler(log_name,encoding="utf-8")info_handler = logging.FileHandler(info_log_name,encoding="utf-8")err_handler = logging.FileHandler(err_log_name, encoding="utf-8")#3、設置日志格式formatter = logging.Formatter("%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s")all_handler.setFormatter(formatter)info_handler.setFormatter(formatter)err_handler.setFormatter(formatter)#添加文件過濾器info_handler.addFilter(infoFilter())err_handler.addFilter(errFilter())#4、將文件處理器添加到日志記錄器中cls.logger.addHandler(all_handler)cls.logger.addHandler(info_handler)cls.logger.addHandler(err_handler)return cls.logger #返回類方法里面的logger對象

request_util.py:

#封裝請求方法,是get還是post
import requests
from utils.logger_util import loggerhost = "http://8.137.19.140:9090/" #這些都是重復的 設置為全局變量class Request:log = logger.getlog() #通過類來獲得日志對象def get(self,url,**kwargs):self.log.info("準備發起get請求,url:"+url)self.log.info("接口信息:{}".format(kwargs))r = requests.get(url=url,**kwargs)self.log.info("接口響應狀態碼:{}".format(r.status_code))self.log.info("接口響應內容:{}".format(r.text))return rdef post(self, url, **kwargs):self.log.info("準備發起post請求,url:" + url)self.log.info("接口信息:{}".format(kwargs))r = requests.post(url=url, **kwargs)self.log.info("接口響應狀態碼:{}".format(r.status_code))self.log.info("接口響應內容:{}".format(r.text))return r

yaml_util.py:

#數據也需要存儲在yaml文件里面,從yaml文件中讀取數據、存儲以及清理的工作
'''
yaml相關操作
'''
import osimport yaml#往yaml文件中寫入數據
def write_yaml(filename,data):with open(os.getcwd()+"/data/"+filename,mode="a+",encoding="utf-8") as f:#獲取當前項目的路徑yaml.safe_dump(data,stream=f) #往文件里寫
#讀取數據
def read_yaml(filename,key): #yaml文件是json格式,我們通過key就可以把值讀出來with open(os.getcwd()+"/data/"+filename,mode="r",encoding="utf-8") as f:data = yaml.safe_load(f) #返回所有的json數據return data[key]  #只返回我要查找的數據
#清空
def clear_yaml(filename):with open(os.getcwd()+"/data/"+filename,mode="r",encoding="utf-8") as f:f.truncate()

指定測試用例運行的順序:

pip install pytest-order==1.3.0

現在有很多博客,現在我們進行刪除博客操作之后,再進行執行測試用例就會出現報錯,因為我們獲得的blogId已經失效了,因此我們要指定文件的執行順序

必須保證這兩個的順序:

登錄接口測試用例---獲取用戶登錄憑證再yaml文件中

列表頁接口測試用例 --- 獲取有效blogId保存在yaml文件中

這個可以作用在測試類上,也可以作用在測試方法上

執行測試:

測試報告:

測試時間

測試時間與測試?例數量成正?。?例數量越多,測試時間越?。如果時間太長需要通過優化測試腳本、并?執?和分布式測試環境,可以顯著縮短測試時間。
測試用例總數:用例數越多,覆蓋范圍越廣
通過率必須達到95%以上

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/93128.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/93128.shtml
英文地址,請注明出處:http://en.pswp.cn/web/93128.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Mysql數據庫遷移到GaussDB注意事項

mysql數據庫遷移高斯數據庫 建議開啟高斯數據庫M模式,mysql兼容模式,可以直接使用mysql的建表語句,自增主鍵可以使用AUTO_INCREMENT,如果不開啟M模式,只能使用高斯數據庫的序列添加自增主鍵1:如果使用數據庫…

蘋果正計劃大舉進軍人工智能硬件領域

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎?訂閱我們的簡報,深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同,從行業內部的深度分析和實用指南中受益。不要錯過這個機會,成為AI領…

Serverless 架構核心解析與應用實踐

Serverless 的核心定義與優勢??核心定義Serverless(無服務器架構)是一種云計算模型,開發者無需關注底層服務器管理,由云服務商自動分配資源、彈性擴縮容,并按實際使用量計費?。其核心特點包括:?按需計算…

Redis持久化機制詳解:RDB與AOF的全面對比與實踐指南

目錄 一、RDB持久化機制 1.1 RDB概述 1.2 RDB觸發機制 1) 手動執行save命令 2) 手動執行bgsave命令 3) Redis正常關閉時 4) 自動觸發條件滿足時 1.3 RDB詳細配置 1.4 RDB實現原理 1.5 RDB的優缺點分析 二、AOF持久化機制 2.1 AOF概述 2.2 AOF工作流程 2.3 AOF同步…

介紹一下jQuery的AJAX異步請求

目錄 一、核心方法:$.ajax() 二、簡化方法(常用場景) 1. $.get():快速發送 GET 請求(獲取數據) 2. $.post():快速發送 POST 請求(提交數據) 3. $.getJSON()&#xf…

Win10系統Ruby+Devkit3.4.5-1安裝

Win10系統RubyDevkit3.4.5-1安裝安裝步驟軟件工具安裝Ruby安裝gem mysql2處理libmysql.dll驗證mysql2安裝步驟 軟件工具 mysql-connector-c-6.1.11-winx64.zip rubyinstaller-devkit-3.4.5-1-x64.exe 安裝Ruby 執行rubyinstaller-devkit-3.4.5-1-x64.exe,期間可…

社交工程:洞穿人心防線的無形之矛

在網絡安全領域,一道無形的裂痕正在迅速蔓延。它不是復雜的零日漏洞,也不是精妙的惡意代碼,而是利用人性弱點進行攻擊的古老技藝——社交工程。當全球網絡安全支出突破千億美元大關,防火墻筑得越來越高,加密算法越來越…

Go 并發控制利器 ants 使用文檔

https://github.com/panjf2000/ants1.1 什么是 ants ants 是一個高性能的 Go 語言 goroutine 池,它能復用已完成任務的 goroutine,避免頻繁創建和銷毀 goroutine,節省 CPU 與內存開銷,并且能限制并發數量防止資源被耗盡。 1.2 安裝…

Day57--圖論--53. 尋寶(卡碼網)

Day57–圖論–53. 尋寶(卡碼網) 今天學習:最小生成樹。有兩種算法(Prim和Kruskal)和一道例題。 prim 算法是維護節點的集合,而 Kruskal 是維護邊的集合。 最小生成樹:所有節點的最小連通子圖&am…

解決海洋探測數據同步網絡問題的新思路——基于智能組網技術的探索

隨著海洋探測技術的不斷發展,數據同步網絡的穩定性和低延遲需求變得愈發重要。海洋探測數據來自多個分布式采集點,這些點需要高效的組網方式來實現實時數據傳輸。然而,由于海洋環境的特殊性(如復雜的網絡拓撲、高濕度和極端溫度&a…

設計模式筆記_行為型_責任鏈模式

1. 責任鏈模式介紹責任鏈模式(Chain of Responsibility)是一種行為設計模式,它允許將多個處理器(處理對象)連接成一條鏈,并沿著這條鏈傳遞請求,直到有一個處理器處理它為止。職責鏈模式的主要目…

pygame的幀處理中,涉及鍵盤的有`pg.event.get()`與`pg.key.get_pressed()` ,二者有什么區別與聯系?

一、pg.event.get() 返回的是一組事件 pg.event.get() 返回的是一組事件(一個包含多個事件對象的列表)。這是因為在游戲的“一幀”時間內(通常1/60秒左右),用戶可能會觸發多個事件(比如同時按下多個鍵、快速…

TF - IDF算法面試與工作常見問題全解析

在自然語言處理領域,TF - IDF算法是一個基礎且重要的概念。無論是在求職面試還是在實際工作中,都經常會遇到與TF - IDF相關的問題。以下是一些常見的問題及其詳細解答: 一、基本概念類問題 1. 什么是TF - IDF算法? TF - IDF&#…

Transformer網絡結構解析

博主會經常分享自己在人工智能階段的學習筆記,歡迎大家訪問我滴個人博客!(都不白來!) 小牛壯士 - 個人博客https://kukudelin.top/ 前言 Transformer 廣泛應用于自然語言處理(如機器翻譯、文本生成&…

gateway進行接口日志打印

打印需求:對所有的接口打印:請求方式,請求路徑,請求參數,用戶id,訪問IP,訪問時間對增刪改操作的接口打印:接口響應打印方案:給GET設置一個白名單(因為get請求…

MATLAB實現圖像增強(直方圖均衡化)

直方圖均衡化是一種常用的圖像增強技術,它通過重新分布圖像的像素強度值來增強圖像的對比度。以下是MATLAB中實現直方圖均衡化的詳細方法。%% 直方圖均衡變換 clc;close all;clear all;warning off;%清除變量 rand(seed, 100); randn(seed, 100); format long g;%% …

java15學習筆記-密封類

360:Sealed Classes (Preview) 封閉類(預覽) 總結 使用密封類和接口增強Java編程語言。密封類和接口限制了哪些其他類或接口可以擴展或實現它們。這是JDK 15中的預覽語言功能。 目標 允許類或接口的作者控制負責實現它的代碼。 提供一種比訪問…

西門子PLC通過穩聯技術EtherCAT轉Profinet網關連接baumuller伺服器的配置案例

西門子PLC用穩聯技術的EtherCAT轉Profinet網關,連上baumuller伺服器的配置例子本案例實現西門子S71200 PLC通過EtherCAT轉Profinet網關對baumuller(Baumller)伺服器的實時控制,適用于高精度運動控制場景(如精密機床、自…

Ansible 詳細筆記

Ansible 詳細筆記 一、Ansible 基礎概述 1.1 定義與定位 Ansible 是由 Red Hat 主導開發的開源自動化運維工具,基于 Python 語言實現,專注于簡化 IT 基礎設施的配置管理、應用部署、任務編排等操作。它采用無代理架構,通過 SSH 協議與被控節點…

【Java 后端】Spring Boot 集成 JPA 全攻略

Spring Boot 集成 JPA 全攻略 一、前言 在 Java Web 開發中,數據庫訪問是繞不開的話題。 傳統方式使用 JDBC 編寫 SQL,維護困難、可讀性差。后來有了 MyBatis 這種半自動 ORM 框架,再到 JPA(Java Persistence API)這…