自動化運維實驗(二)---自動識別設備,并導出配置

?目錄

一、實驗拓撲

二、實驗目的

三、實驗步驟

實驗思路:

代碼部分:

四、實驗結果:


一、實驗拓撲

二、實驗目的

ssh遠程登錄后,識別設備類型(華三、華為、銳捷、山石、飛塔、深信服等),再輸入對應設備的命令進行配置導出

三、實驗步驟

實驗開始之前先搭好環境,測試無誤再開始

實驗思路:

利用ip.txt,存放需要登錄的設備IP

再一臺一臺登錄,更具設備獨有的命令進行識別,再對配置進行導出保存。

代碼部分:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量導出設備配置(改進版)
- 在探測/關閉分頁階段不保存任何輸出
- 僅從首次成功的“導出配置”命令開始保存 raw 輸出,并對其進行清理后保存 clean 輸出
- 支持自動翻頁、錯誤判斷、廠商識別等
- 輸出文件:output/<ip>_<vendor>_<ts>_raw.txt   (可選)output/<ip>_<vendor>_<ts>_clean.txt
配置項可在腳本頂部調整。
"""
import paramiko
import time
import re
import os
import sys
from getpass import getpass
from datetime import datetime# ---------- 配置區域(可按需修改) ----------
IP_FILE = "ip.txt"
OUTPUT_DIR = "output"
SSH_PORT = 22
CONNECT_TIMEOUT = 10
CMD_TIMEOUT = 6.0
READ_CHUNK = 65536# 是否只保存 clean(True)或同時保存 raw(False)
SAVE_CLEAN_ONLY = TrueVENDOR_KEYWORDS = {"huawei": ["huawei", "huawei vrp", "huawei Technologies", "Huawei"],"h3c": ["h3c", "h3c technologies", "Comware", "H3C"],"cisco": ["cisco", "ios", "cisco ios", "Cisco IOS Software"],"ruijie": ["ruijie", "ruijie networks", "rzos", "Ruijie"],"fortigate": ["fortigate", "fortios", "fortinet", "FortiGate"],"hillstone": ["hillstone", "hillstone networks", "shanshi", "HS"],"sangfor": ["sangfor", "深信服"],
}# 每個廠商嘗試的“導出配置”命令列表
VENDOR_CONFIG_CMDS = {"cisco": ["terminal length 0", "show running-config"],"huawei": ["display current-configuration", "screen-length 0 temporary", "display this-configuration"],"h3c": ["display current-configuration", "display this-configuration", "screen-length 0 temporary"],"ruijie": ["display current-configuration", "show running-config", "screen-length 0 temporary"],"fortigate": ["show full-configuration", "get system status", "show running-config", "show"],"hillstone": ["display current-configuration", "show running-config", "terminal length 0"],"sangfor": ["display current-configuration", "show running-config"],# fallback"unknown": ["display current-configuration", "show running-config", "show full-configuration", "show"]
}# 一些通用的關閉分頁命令(嘗試但不將其直接視為導出成功)
PAGING_CMDS = ["terminal length 0","screen-length 0 temporary","screen-length disable","page 0","set cli pagination off","no page","undo page",
]# ---------- 正則/判定模式 ----------
_ERR_PAT = re.compile(r"unrecognized command|unknown command|invalid input|command not found|% Unrecognized|% Invalid|% Unknown|^%|[\^]\s*$",re.IGNORECASE,
)
_PAGING_PAT = re.compile(r"--More--|-- MORE --|--More--|\[More\]|Press any key|<--- More --->|--More--|More:|\(q\)|\-\-more\-\-",re.IGNORECASE,
)
_CONFIG_HINTS = re.compile(r"\b(hostname|sysname|interface|system-view|vlan|ip address|current-configuration|running-config|service password-encryption|ntp server|snmp-server|boot-image|bootrom|startup-config|device name)\b",re.IGNORECASE,
)# ---------- 輔助函數 ----------
def read_ip_file(path):ips = []if not os.path.exists(path):print("ip 文件不存在:", path)return ipswith open(path, "r", encoding="utf-8") as f:for ln in f:ln = ln.strip()if not ln or ln.startswith("#"):continueparts = [p.strip() for p in ln.split(",")]if len(parts) == 1:ips.append((parts[0], None, None))elif len(parts) >= 3:ips.append((parts[0], parts[1], parts[2]))else:ips.append((parts[0], parts[1] if len(parts) > 1 else None, None))return ipsdef ensure_output_dir(path):if not os.path.exists(path):os.makedirs(path, exist_ok=True)def timestamp():return datetime.now().strftime("%Y%m%d_%H%M%S")def open_ssh_client(ip, username, password, port=SSH_PORT, timeout=CONNECT_TIMEOUT):client = paramiko.SSHClient()client.set_missing_host_key_policy(paramiko.AutoAddPolicy())client.connect(ip, port=port, username=username, password=password, timeout=timeout, look_for_keys=False, allow_agent=False)return clientdef recv_all_from_shell(shell, timeout=CMD_TIMEOUT, pause=0.2):"""非阻塞讀取:讀取直到超時段內沒有新數據。"""output = b""end_time = time.time() + timeoutwhile True:while shell.recv_ready():try:chunk = shell.recv(READ_CHUNK)if not chunk:breakoutput += chunkend_time = time.time() + timeoutexcept Exception:breakif time.time() > end_time:breaktime.sleep(pause)try:return output.decode('utf-8', errors='ignore')except Exception:return output.decode('latin1', errors='ignore')def send_cmd_and_recv(shell, cmd, cmd_timeout=CMD_TIMEOUT, short_sleep=0.2):"""發送命令并收集輸出(支持自動翻頁)。返回 (out_str, error_str)"""if not cmd.endswith("\n"):to_send = cmd + "\n"else:to_send = cmdtry:shell.send(to_send)except Exception as e:return "", "send_error: " + str(e)time.sleep(short_sleep)out = recv_all_from_shell(shell, timeout=cmd_timeout)# 如果輸出包含分頁提示則自動翻頁(發送空格)if out and _PAGING_PAT.search(out):accum = outfor i in range(300):  # 上限,避免無限循環try:shell.send(" ")except Exception:breaktime.sleep(0.12)more = recv_all_from_shell(shell, timeout=1.0)if not more:breakaccum += more# 若本次返回不再包含分頁提示,結束if not _PAGING_PAT.search(more):breakout = accumreturn out, Nonedef try_disable_paging(shell):"""嘗試發送分頁關閉命令(不保存這些輸出)。返回拼接的輸出字符串(僅用于本地判斷/日志),但調用方不會把它保存為主配置文件。"""accum = []for c in PAGING_CMDS:try:out, err = send_cmd_and_recv(shell, c, cmd_timeout=1.0)accum.append(f"CMD: {c}\n{out or ''}\nerr:{err}")except Exception as e:accum.append(f"CMD: {c}\nEXCEPTION: {e}")return "\n".join(accum)def detect_vendor_from_text(text):if not text:return Nonetl = text.lower()for vendor, keys in VENDOR_KEYWORDS.items():for k in keys:if k.lower() in tl:return vendorreturn Nonedef try_commands_and_collect(shell, cmds, cmd_timeout=CMD_TIMEOUT):"""依次嘗試 cmds:- 對于明顯用于關閉分頁/切換上下文的命令(非配置導出命令),發送后丟棄輸出- 對于疑似配置導出命令(包含 display/show running-config/current-configuration 等關鍵詞),發起捕獲并返回 raw 輸出返回 (successful_cmd_or_None, raw_output_or_None, tried_cmd_list)tried_cmd_list 為命令字符串列表(僅便于 clean 函數去除回顯)"""tried = []for c in cmds:tried.append(c)lower = c.strip().lower()# 判斷是否可能為配置導出命令(較寬的匹配)if any(k in lower for k in ("display current-configuration", "display this-configuration", "show running-config", "show full-configuration", "show configuration", "show config", "show running", "show full")):# 將該命令視為配置導出命令,開始 captureout, err = send_cmd_and_recv(shell, c, cmd_timeout=max(cmd_timeout, 10.0))if out and out.strip():return c, out, tried# 若沒有拿到輸出,繼續嘗試下一個命令continue# 否則把它當成分頁關閉或上下文切換之類的命令,發送但不當作最終配置輸出try:send_cmd_and_recv(shell, c, cmd_timeout=1.5)except Exception:# 忽略錯誤,繼續嘗試下一條命令passtime.sleep(0.12)# 如果循環結束仍未明確成功,則返回 None,caller 會再嘗試 fallbackreturn None, None, trieddef clean_config_output_v2(text, successful_cmd=None, tried_cmds=None):"""更強力的配置清理函數(原樣復用并允許傳入 tried_cmds 以去掉回顯)"""if not text:return ""# 1. 去掉 ANSI 控制碼t = re.sub(r'\x1B\[[0-?]*[ -/]*[@-~]', '', text)# 2. 統一換行并按行處理t = t.replace('\r\n', '\n').replace('\r', '\n')lines = t.split('\n')# 3. 構建一些便捷匹配集合tried_set = set()if tried_cmds:for c in tried_cmds:if c:tried_set.add(c.strip().lower())if successful_cmd:tried_set.add(successful_cmd.strip().lower())# helper 判斷是否是 prompt 行(像 <SW6> 或 hostname> 或 hostname#)def is_prompt_line(l):s = l.strip()if not s:return False# <SW6>if re.match(r'^<[^<>]+>$', s):return True# name> or name# (單獨的提示符)if re.match(r'^[\w\-\._]+[>#]$', s):return Truereturn False# helper 判斷是否是命令回顯(完全等于某個嘗試過的命令)def is_cmd_echo(l):s = l.strip().lower()if s in tried_set:return Truefor cmd in tried_set:if cmd and s.startswith(cmd) and len(s) <= len(cmd) + 6:return Truereturn False# helper 判斷是否是錯誤/噪音行def is_error_line(l):s = l.strip()if not s:return Falseif s.startswith('%') or s == '^' or s.startswith('^'):return Trueif re.search(r'unrecognized command|invalid input|command not found|Unrecognized', s, re.IGNORECASE):return Truereturn False# helper 判斷是否為“裝飾性行”(大量符號)def is_decorative(l):s = l.strip()if not s:return Falseif len(s) >= 10 and (re.sub(r'[\W_]', '', s) == '' or (sum(ch.isalnum() for ch in s) / len(s)) < 0.2):return Trueif re.search(r'copyright', s, re.IGNORECASE) and len(s) < 200:return Truereturn False# 4. 先去掉明顯噪聲行(但保持行索引,以便后續定位 config 起點)cleaned_lines = []for ln in lines:if is_prompt_line(ln):continueif is_cmd_echo(ln):continueif is_error_line(ln):continueif is_decorative(ln):continuecleaned_lines.append(ln)# 5. 在 cleaned_lines 里尋找配置起點cfg_start_keywords = [re.compile(r'^\s*#\s*$', re.IGNORECASE),re.compile(r'^\s*version\b', re.IGNORECASE),re.compile(r'^\s*sysname\b', re.IGNORECASE),re.compile(r'^\s*hostname\b', re.IGNORECASE),re.compile(r'^\s*interface\b', re.IGNORECASE),re.compile(r'current-configuration', re.IGNORECASE),re.compile(r'running-config', re.IGNORECASE),re.compile(r'^\s*!', re.IGNORECASE),]start_idx = Nonefor i, ln in enumerate(cleaned_lines):for p in cfg_start_keywords:if p.search(ln):if p.pattern == r'^\s*#\s*$':if i + 1 < len(cleaned_lines):nxt = cleaned_lines[i + 1]if re.search(r'^\s*version\b|^\s*sysname\b|^\s*hostname\b|^\s*interface\b|current-configuration|running-config', nxt, re.IGNORECASE):start_idx = ibreakelse:start_idx = ibreakelse:start_idx = ibreakelse:start_idx = ibreakif start_idx is not None:break# 6. 如果沒找到起點,嘗試根據 CONFIG_HINTS 的 regex 找第一個出現位置if start_idx is None:joined = '\n'.join(cleaned_lines)m = _CONFIG_HINTS.search(joined)if m:pos = m.start()up_to = joined[:pos]start_idx = up_to.count('\n')else:out = '\n'.join(l for l in cleaned_lines).strip()out = re.sub(r'\n\s*\n+', '\n\n', out)return out# 7. 從 start_idx 開始取后續行,并再做最后清理(去掉行首/尾多余空格,去掉連續多空行)final_lines = cleaned_lines[start_idx:]while final_lines and not final_lines[0].strip():final_lines.pop(0)final_lines2 = []for ln in final_lines:if is_prompt_line(ln):continueif is_error_line(ln):continuefinal_lines2.append(ln.rstrip())out = '\n'.join(final_lines2).strip()out = re.sub(r'\n\s*\n+', '\n\n', out)return outdef save_output(ip, vendor, raw_text, cleaned_text, prefix=OUTPUT_DIR):"""保存文件:如果 SAVE_CLEAN_ONLY 為 True 則僅保存 cleaned_text,否則保存 raw + clean返回已保存的主路徑(clean 的路徑)"""ensure_output_dir(prefix)ts = timestamp()safe_vendor = vendor if vendor else "unknown"base = f"{ip}_{safe_vendor}_{ts}"clean_path = os.path.join(prefix, base + "_clean.txt")try:with open(clean_path, "w", encoding="utf-8") as f:f.write(cleaned_text or "")except Exception as e:print(f"[{ip}] 寫 clean 文件失敗: {e}")if not SAVE_CLEAN_ONLY and raw_text is not None:raw_path = os.path.join(prefix, base + "_raw.txt")try:with open(raw_path, "w", encoding="utf-8") as f:f.write(raw_text or "")except Exception as e:print(f"[{ip}] 寫 raw 文件失敗: {e}")return clean_path# ---------- 主流程 ----------
def process_device(ip, user, pwd, port=SSH_PORT):ip_str = ipprint("-> 處理:", ip_str)try:client = open_ssh_client(ip, username=user, password=pwd, port=port)except Exception as e:msg = f"SSH 連接失敗: {e}"print(msg)return {"ip": ip, "ok": False, "error": msg}try:shell = client.invoke_shell()time.sleep(0.2)except Exception as e:client.close()msg = f"invoke_shell 失敗: {e}"print(msg)return {"ip": ip, "ok": False, "error": msg}# 讀取初始 banner(不保存)time.sleep(0.2)try:intro = recv_all_from_shell(shell, timeout=1.0)except Exception:intro = ""# 嘗試關閉分頁(輸出不保存)try:_ = try_disable_paging(shell)except Exception:pass# 探測廠商(發送若干探測命令,輸出不保存)detect_cmds = ["display version", "show version", "get system status", "show system info", "uname -a"]detect_text = intro or ""for dc in detect_cmds:try:out, err = send_cmd_and_recv(shell, dc, cmd_timeout=1.5)except Exception:out = ""if out:detect_text += "\n" + outif re.search(r"huawei|h3c|cisco|forti|fortigate|ruijie|hillstone|sangfor|ios|vrp|comware", out, re.IGNORECASE):breakvendor = detect_vendor_from_text(detect_text) or "unknown"print(f"   識別廠商為: {vendor}")# 嘗試按廠商命令導出配置 —— 僅在配置命令上開始 capture 并保存cmds = VENDOR_CONFIG_CMDS.get(vendor, VENDOR_CONFIG_CMDS["unknown"])successful_cmd, raw_output, tried_cmds = try_commands_and_collect(shell, cmds, cmd_timeout=CMD_TIMEOUT)# 若沒有拿到,作為最后手段再嘗試常見命令并長時間讀取(這些輸出將被當作配置輸出嘗試保存)if not raw_output:fallback_cmds = ["display current-configuration", "show running-config", "show full-configuration"]for fc in fallback_cmds:try:out, err = send_cmd_and_recv(shell, fc, cmd_timeout=10.0)except Exception:out = ""tried_cmds.append(fc)if out and out.strip():raw_output = outsuccessful_cmd = fcbreak# 如果仍然沒有明顯配置輸出,嘗試讀取 shell 剩余的輸出保存以便排查(但這并非配置)if not raw_output:time.sleep(0.5)more = recv_all_from_shell(shell, timeout=2.0)if more and more.strip():raw_output = more# 只保存配置相關內容:clean 后寫入文件;raw 可選cleaned = clean_config_output_v2(raw_output or "", successful_cmd=successful_cmd, tried_cmds=tried_cmds)saved_path = save_output(ip, vendor, raw_output, cleaned)print(f"   輸出已保存: {saved_path}")try:shell.close()except Exception:passtry:client.close()except Exception:passok = bool(cleaned and _CONFIG_HINTS.search(cleaned))return {"ip": ip, "ok": ok, "vendor": vendor, "path": saved_path, "raw_out": raw_output, "clean_out": cleaned}def main():print("批量導出設備配置腳本(僅保存配置階段輸出)")ips = read_ip_file(IP_FILE)if not ips:print("ip 列表為空,請在 ip.txt 中每行寫入一個 IP(或 ip,username,password)")returnneed_cred = any(u is None or p is None for (_, u, p) in ips)default_user = Nonedefault_pass = Noneif need_cred:default_user = input("請輸入 SSH 用戶名: ").strip()default_pass = getpass("請輸入 SSH 密碼: ")ensure_output_dir(OUTPUT_DIR)results = []for ip, u, p in ips:user = u if u else default_userpwd = p if p else default_passif not user or not pwd:print(f"缺少該設備 {ip} 的用戶名或密碼,跳過")results.append({"ip": ip, "ok": False, "error": "no credentials"})continuetry:res = process_device(ip, user, pwd, port=SSH_PORT)results.append(res)except Exception as e:print(f"處理 {ip} 時異常: {e}")results.append({"ip": ip, "ok": False, "error": str(e)})print("\n處理完成,總結:")for r in results:if r.get("ok"):print(f" {r['ip']} -> OK, vendor={r.get('vendor')}, file={r.get('path')}")else:print(f" {r['ip']} -> MAYBE FAILED (仍保存了可供排查的文件), reason={r.get('error')}, file={r.get('path')}")if __name__ == "__main__":try:main()except KeyboardInterrupt:print("\n用戶中斷,退出")sys.exit(1)

四、實驗結果:

建議在終端運行,pycharm運行,getpass會卡住

可以看到每臺設備都成功登錄,并將配置保存在txt文件(2.15應該是設備問題)

最終生成的文件:

實驗完成!

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

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

相關文章

Matlab(4)初階繪圖

一、Basic plotting1.plot&#xff08;&#xff09;plot(x,y) &#xff1a;x圖片中點的橫坐標&#xff0c;y圖片中點的縱坐標plot(y) &#xff1a;y圖片中點的縱坐標&#xff0c;x圖片中點的橫坐標默認為1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5........plot(co…

服務器硬件電路設計之 I2C 問答(五):I2C 總線數據傳輸方向如何確定、信號線上的串聯電阻有什么作用?

在服務器硬件電路設計中&#xff0c;I2C 總線作為常用的串行通信總線&#xff0c;其數據傳輸方向的確定和信號線上串聯電阻的作用是關鍵知識點。?I2C 總線數據傳輸方向由主設備和從設備的角色以及讀寫位共同確定。主設備是發起通信的一方&#xff0c;從設備則是被尋址的對象。…

OpenBMC中C++策略模式架構、原理與應用

1. 策略模式概述 策略模式&#xff08;Strategy Pattern&#xff09;是一種行為型設計模式&#xff0c;它允許在運行時動態選擇算法或行為&#xff0c;而無需修改客戶端代碼。 核心思想&#xff1a;封裝可互換的算法族&#xff0c;使它們可以獨立于使用它們的客戶端變化。 1.…

【python實用小腳本-187】Python一鍵批量改PDF文字:拖進來秒出新文件——再也不用Acrobat來回導

Python一鍵批量改PDF文字&#xff1a;拖進來秒出新文件——再也不用Acrobat來回導 PDF文字替換, 批量導出, 零依賴轉檔, 一鍵完成, 瑞士軍刀 故事開場&#xff1a;一把瑞士軍刀救了周五下班的你 周五 18:00&#xff0c;老板甩來 50 份合同 PDF&#xff1a; “把里面的‘2023’全…

汽車后霧燈色度難達標?OAS 軟件精準解決破瓶頸

汽車后霧燈案例分析簡介汽車后霧燈是車輛在能見度較低的霧、雨、雪等惡劣天氣條件下行駛時&#xff0c;向后方車輛傳遞警示信號的重要裝置&#xff0c;其性能直接關系到車輛的后方安全。根據規定&#xff0c;紅色信號燈需符合 CIE1931 標準&#xff0c;其色度坐標 X 值應在 0.6…

[系統架構設計師]架構設計專業知識(二)

[系統架構設計師]架構設計專業知識&#xff08;二&#xff09; 一.信息系統基礎知識 1.信息系統概述 信息系統功能&#xff1a;輸入&#xff0c;存儲&#xff0c;處理&#xff0c;輸出&#xff0c;控制 理查德.諾蘭&#xff1a; 初始&#xff0c;傳播&#xff0c;控制&#xff…

如果用ApiFox調用Kubernetes API,需要怎么設置證書?

針對Docker Desktop中Kubernetes訪問報SSL/TLS信任關系錯誤的問題&#xff0c;以下是綜合解決方案&#xff1a;要在Postman中調用Kubernetes API并設置證書&#xff0c;需按以下步驟操作&#xff1a;&#x1f510; 證書設置步驟?提取證書文件?從kubeconfig文件&#xff08;~/…

nodejs 路由/請求

//導入模塊 const express require(express); //創建應用 const app express();//設置路由 app.get(/,(req,resp)>{//輸出響應console.log(request coming.............);resp.json(req.headers); });app.get(/user/:id, (req, res) > {const userId req.params.id; …

Python 數據可視化:柱狀圖/熱力圖繪制實例解析

Python 數據可視化&#xff1a;柱狀圖繪制實例解析 一、引言 數據可視化是數據分析中至關重要的環節&#xff0c;它能將復雜的數據以直觀的圖形方式呈現&#xff0c;幫助我們更好地理解數據特征和規律。Python 擁有豐富的可視化庫&#xff0c;其中 Matplotlib 是最常用的基礎庫…

API生命周期10階段

一、策略規劃&#xff08;Strategy Planning&#xff09; 核心任務&#xff1a;業務價值對齊、技術路線設計關鍵產出&#xff1a; API產品藍圖&#xff1a;定義業務領域邊界&#xff08;如支付API域、用戶API域&#xff09;治理規范&#xff1a;《API安全標準》《版本管理策略》…

UGUI源碼剖析(9):布局的實現——LayoutGroup的算法與實踐

UGUI源碼剖析&#xff08;第九章&#xff09;&#xff1a;布局的實現——LayoutGroup的算法與實踐 在前一章中&#xff0c;我們剖析了LayoutRebuilder是如何調度布局重建的。現在&#xff0c;我們將深入到布局核心&#xff0c;去看看那些具體的組件——LayoutGroup系列組件是如…

GitHub PR 提交流程

step1 在 GitHub 上 fork 目標倉庫&#xff08;手動操作&#xff09; step2 將 fork 的目標倉庫克隆到本地 git clone https://github.com/<your-username>/<repo-name>.git cd <repo-name>step3 與上游目標倉庫建立鏈接 git remote add upstream https://gi…

礦物分類案列 (一)六種方法對數據的填充

目錄 礦物數據項目介紹&#xff1a; 數據問題與處理方案&#xff1a; 數據填充策略討論&#xff1a; 模型選擇與任務類型&#xff1a; 模型訓練計劃&#xff1a; 一.數據集填充 1.讀取數據 2.把標簽轉化為數值 3.把異常數據轉化為nan 4.數據Z標準化 5.劃分訓練集測試…

vue:vue3的方法torefs和方法toref

在 Vue 3 的 Composition API 中,toRef 和 toRefs 是兩個用于處理響應式數據的重要工具,它們專門用于從 reactive() 對象中提取屬性并保持響應性。 toRef() 作用:將 reactive 對象的單個屬性轉換為一個 ref 對象,保持與源屬性的響應式連接。 使用場景: 需要單獨提取 rea…

Android 移動端 UI 設計:前端常用設計原則總結

在 Android 移動端開發中&#xff0c;優秀的 UI 設計不僅需要視覺上的美觀&#xff0c;更需要符合用戶習慣、提升操作效率的設計邏輯。前端 UI 設計原則是指導開發者將功能需求轉化為優質用戶體驗的核心準則&#xff0c;這些原則貫穿于布局結構、交互反饋、視覺呈現等各個環節。…

計算機網絡 TCP三次握手、四次揮手超詳細流程【報文交換、狀態變化】

TCP&#xff08;傳輸控制協議&#xff09;是互聯網最重要的協議之一&#xff0c;它保證了數據的可靠、有序傳輸。連接建立時的“三次握手”和連接關閉時的“四次揮手”是其核心機制&#xff0c;涉及特定的報文交換和狀態變化。 一、TCP 三次握手&#xff08;Three-Way Handshak…

使用Applications Manager進行 Apache Solr 監控

Apache Solr 為一些對性能極為敏感的環境提供搜索支持&#xff1a;電子商務、企業應用、內容門戶和內部知識系統。因此&#xff0c;當出現延遲增加或結果不一致的情況時&#xff0c;用戶會立刻察覺。而當這些問題未被發現時&#xff0c;情況會迅速惡化。 Apache Solr 基于 Apa…

Shell腳本-for循環語法結構

一、前言在 Linux Shell 腳本編程中&#xff0c;for 循環 是最常用的控制結構之一&#xff0c;用于重復執行一段命令&#xff0c;特別適用于處理列表、文件、數字序列等場景。本文將詳細介紹 Shell 腳本中 for 循環的各種語法結構&#xff0c;包括&#xff1a;? 經典 for in 結…

記SpringBoot3.x + Thymeleaf 項目實現(MVC架構模式)

目錄 前言 一、創建SpringBoot項目 1. 創建項目 2. 運行項目 二、連接數據庫實現登錄 1. pom.xml文件引入依賴包 2. application.yml文件配置 3. 數據持久層&#xff0c;mybatis操作映射 4. Service接口及實現 5. Controller代碼 6. Thymeleaf頁面登錄 7. 運行項目…

Java 導出word 實現表格內插入圖表(柱狀圖、折線圖、餅狀圖)--可編輯數據

表格內插入圖表導出效果表格內圖表生成流程分析 核心問題與解決方案 問題 Word 圖表作為獨立對象&#xff0c;容易與文本分離位置難以精確控制&#xff0c;編輯時容易偏移缺乏與表格數據的關聯性 解決方案 直接嵌入&#xff1a;將圖表嵌入表格單元格&#xff0c;確保數據關聯精…