gateway進行接口日志打印

打印需求:

? ? ? ? 對所有的接口打印:請求方式,請求路徑,請求參數,用戶id,訪問IP,訪問時間

? ? ? ? 對增刪改操作的接口打印:接口響應

打印方案:

? ? ? ? 給GET設置一個白名單(因為get請求大多數是查詢,僅有部分增刪改操作),在這個白名單里的接口,需要打印響應,不在的話就只打印一下基礎信息

? ? ? ? 給POST請求設置一個黑名單(因為post請求大多數會對數據進行操作,僅有小部分的查詢),在這個黑名單里的查詢接口,則不需要打印接口響應

?實現方式:

? ? ? ? 在過濾器中進行操作

package com.xxxxxx.gateway.filter;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.koushare.common.constant.AuthConstants;
import com.koushare.common.constant.TokenConstants;
import com.koushare.common.utils.IPUtils;
import com.koushare.common.utils.StringUtils;
import com.koushare.gateway.model.CheckRequest;
import com.koushare.gateway.service.SysPrintLogService;
import com.koushare.jwt.config.PassJavaJwtProperties;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.annotation.Resource;import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;import static com.koushare.common.convertor.ConvertHelper.bean2map;
import static com.koushare.common.utils.IPUtils.getIpAddrByServerHttpRequest;/*** 自定義全局過濾器*/
@Component
@Slf4j
public class GlobalRequestFilter implements GlobalFilter, Ordered {@Resourceprivate PassJavaJwtProperties jwtProperties;@Autowiredprivate SysPrintLogService sysPrintLogService;private static final String OPENAPI_SERVICE = "/openapi/";private static final String OPENAPI_CODE_PATH = "/code/";@Overridepublic int getOrder() {return -20;}@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest serverHttpRequest = exchange.getRequest();// 獲取原始響應對象和數據緩沖工廠ServerHttpResponse originalResponse = exchange.getResponse();DataBufferFactory bufferFactory = originalResponse.bufferFactory();// 原始響應對象,用于攔截和修改響應內容ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {/*** 重寫writeWith方法攔截響應體*/@Overridepublic Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {if (body instanceof Flux) {Flux<? extends DataBuffer> fluxBody = Flux.from(body);return super.writeWith(fluxBody.buffer().map(dataBuffers -> {// 獲取一些需要打印的參數long timestamp = System.currentTimeMillis();HttpMethod method = serverHttpRequest.getMethod();String requestUrl = serverHttpRequest.getPath().toString();String userId = Optional.ofNullable(serverHttpRequest.getHeaders().getFirst(AuthConstants.USER_ID)).filter(StringUtils::isNotBlank).orElse("未登錄");String ip = IPUtils.getIpAddrByServerHttpRequest(serverHttpRequest);String params = getRequestparams(serverHttpRequest, exchange);log.info("{} ========================接口詳細日志========================", timestamp);log.info("{} 請求方式:{}  請求路徑: {}", timestamp, method, requestUrl);log.info("{} 請求參數: {}", timestamp, params);log.info("{} 用戶ID: {}  訪問IP: {}  訪問時間:{}", timestamp, userId, ip, new Date());// 判斷是否需要打印響應if (isUpdateDate(method, requestUrl)) {// 創建數據緩沖工廠和緩沖區,用于讀取響應內容DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer buff = dataBufferFactory.join(dataBuffers);byte[] content = new byte[buff.readableByteCount()];buff.read(content);// 釋放緩沖區資源DataBufferUtils.release(buff);// 獲取響應內容類型MediaType contentType = originalResponse.getHeaders().getContentType();if (!MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {// 如果不是JSON類型,直接返回原始內容,不進行處理log.info("{} ===============================================================", timestamp);return bufferFactory.wrap(content);}// 將字節數組轉換為字符串 對響應體進行統一格式化處理String result = modifyBody(new String(content));log.info("{} 響應結果: {}", timestamp, result);log.info("{} ===============================================================", timestamp);getDelegate().getHeaders().setContentLength(result.getBytes().length);return bufferFactory.wrap(result.getBytes());} else {// 不需要打印響應結果時,直接合并并返回原始數據log.info("{} ===============================================================", timestamp);DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer joinedBuffer = dataBufferFactory.join(dataBuffers);byte[] content = new byte[joinedBuffer.readableByteCount()];joinedBuffer.read(content);DataBufferUtils.release(joinedBuffer);return bufferFactory.wrap(content);}}));} else {return super.writeWith(body);}}};return chain.filter(exchange.mutate().response(decoratedResponse).build());}private static String getRouteName(String requestUrl) {String serviceUrl = requestUrl.substring(requestUrl.indexOf("/") + 1);log.info("getRouteName: " + serviceUrl.substring(0, serviceUrl.indexOf("/")));return serviceUrl.substring(0, serviceUrl.indexOf("/"));}/*** 獲取請求token*/private String getToken(ServerHttpRequest request) {String token = request.getHeaders().getFirst(jwtProperties.getHeader());// 如果前端設置了令牌前綴,則裁剪掉前綴if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {token = token.replaceFirst(TokenConstants.PREFIX, StringUtils.EMPTY);}return token;}/*** 獲取去除路由后的path** @param requestUrl* @return*/private static String getPath(String requestUrl) {String path = requestUrl.substring(1);log.info("getPath: " + path.substring(path.indexOf("/")));return path.substring(path.indexOf("/"));}/*** 判斷是否為增刪改接口  只有增刪改接口才會打印響應結果*/private boolean isUpdateDate(HttpMethod method, String requestUrl){switch (method) {case PUT:case DELETE:return true;case GET:return sysPrintLogService.checkNeedPrint_GET(requestUrl);case POST:return sysPrintLogService.checkNeedPrint_POST(requestUrl);default:return false;}}/*** 獲取請求參數*/private String getRequestparams(ServerHttpRequest serverHttpRequest, ServerWebExchange exchange) {HttpMethod method = serverHttpRequest.getMethod();// 檢查是否為文件上傳請求,如果是則不打印參數MediaType contentType = serverHttpRequest.getHeaders().getContentType();if (contentType != null && (contentType.includes(MediaType.MULTIPART_FORM_DATA)|| contentType.includes(MediaType.APPLICATION_OCTET_STREAM))) {return "";}if (HttpMethod.GET.equals(method) || HttpMethod.DELETE.equals(method)) {StringBuilder params = new StringBuilder();serverHttpRequest.getQueryParams().forEach((key, value) -> {value.forEach(v -> params.append(key).append("=").append(v).append("&"));});// 移除末尾的 "&"if (params.length() > 0) {params.deleteCharAt(params.length() - 1);}return params.toString();} else if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method)) {return getBodyContent(exchange);}return "";}// 從其他filter中copy過來的 目的是獲取post請求的bodyprivate String getBodyContent(ServerWebExchange exchange){Flux<DataBuffer> body = exchange.getRequest().getBody();AtomicReference<String> bodyRef = new AtomicReference<>();// 緩存讀取的request body信息body.subscribe(dataBuffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());DataBufferUtils.release(dataBuffer);bodyRef.set(charBuffer.toString());});//獲取request bodyreturn bodyRef.get();}/*** 修改響應體內容,統一JSON數據格式*/private String modifyBody(String str){JSONObject json = JSON.parseObject(str, Feature.AllowISO8601DateFormat);JSONObject.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";return JSONObject.toJSONString(json, (ValueFilter) (object, name, value) ->value == null ? "" : value, SerializerFeature.WriteDateUseDateFormat);}}

其中的service實現類:

package com.xxxxxx.gateway.service.impl;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.koushare.gateway.entity.SysPrintLog;
import com.koushare.gateway.mapper.SysPrintLogMapper;
import com.koushare.gateway.service.SysPrintLogService;
import com.koushare.redis.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Slf4j
@Service
public class SysPrintLogServiceImpl implements SysPrintLogService {private final static String PrintLogUrlWhitelist_GET = "PrintLog:UrlWhiteList";private final static String PrintLogUrlBlacklist_POST = "PrintLog:UrlBlackList";@Autowiredprivate SysPrintLogMapper sysPrintLogMapper;@Autowiredprivate RedisUtils redisUtils;/*** 檢查Get請求是否需要打印* @return true:需要打印; false:不需要打印*/@Overridepublic boolean checkNeedPrint_GET(String requestUrl) {return checkWhiteList(requestUrl);}/*** 檢查Post請求是否需要打印* @return true:需要打印; false:不需要打印*/@Overridepublic boolean checkNeedPrint_POST(String requestUrl) {return checkBlackList(requestUrl);}/*** 重新加載redis的Get請求日志打印接口白名單*/@Overridepublic List<SysPrintLog> loadPringLogWhiteList() {List<SysPrintLog> list = sysPrintLogMapper.queryPrintLogWhiteList();JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(list));redisUtils.set(PrintLogUrlWhitelist_GET, jsonArray);return list;}/*** 重新加載redis的Post請求日志打印接口黑名單*/@Overridepublic List<SysPrintLog> loadPrintLogBlackList() {List<SysPrintLog> list = sysPrintLogMapper.queryPrintLogBlackList();JSONArray jsonArray = JSONArray.parseArray(JSON.toJSONString(list));redisUtils.set(PrintLogUrlBlacklist_POST, jsonArray);return list;}/*** 讀redis中的白名單,不存在則讀取數據庫*/private List<SysPrintLog> WhiteList(){JSONArray jsonArray = redisUtils.getJsonArray(PrintLogUrlWhitelist_GET);if (jsonArray == null || jsonArray.size() == 0) {return loadPringLogWhiteList();}return jsonArray.toJavaList(SysPrintLog.class);}/*** 讀redis中的黑名單,不存在則讀取數據庫*/private List<SysPrintLog> BlackList(){JSONArray jsonArray = redisUtils.getJsonArray(PrintLogUrlBlacklist_POST);if (jsonArray == null || jsonArray.size() == 0) {return loadPrintLogBlackList();}return jsonArray.toJavaList(SysPrintLog.class);}/*** 白名單列表中查找是否存在匹配的URL*/private boolean checkWhiteList(String requestUrl){return WhiteList().stream().anyMatch(sysPrintLog -> sysPrintLog.getUrl().equals(requestUrl));}/*** 黑名單列表中查找是否存在匹配的URL*/private boolean checkBlackList(String requestUrl){return BlackList().stream().noneMatch(sysPrintLog -> sysPrintLog.getUrl().equals(requestUrl));}}

用到的兩個查詢:

@Mapper
public interface SysPrintLogMapper {@Select("select id,url,description from sys_print_log_whitelist")List<SysPrintLog> queryPrintLogWhiteList();@Select("select id,url,description from sys_print_log_blacklist")List<SysPrintLog> queryPrintLogBlackList();}

以及數據庫實體

@Data
@Builder(setterPrefix = "set")
@NoArgsConstructor
@AllArgsConstructor
public class SysPrintLog {private Integer id;private String url;private String description;}

獲取ip地址的工具類:

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;/*** IP地址** @author zhanghai on 2018/9/17*/
@Slf4j
public class IPUtils {// 多次反向代理后會有多個ip值 的分割符private final static String IP_UTILS_FLAG = ",";// 未知IPprivate final static String UNKNOWN = "unknown";// 本地 IPprivate final static String LOCALHOST_IP = "0:0:0:0:0:0:0:1";private final static String LOCALHOST_IP1 = "127.0.0.1";/*** 獲取IP地址* 使用Nginx等反向代理軟件, 則不能通過request.getRemoteAddr()獲取IP地址* 如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個,而是一串IP地址,X-Forwarded-For中第一個非unknown的有效IP字符串,則為真實IP地址*/public static String getIpAddr(HttpServletRequest request) {String ip = null;try {ip = request.getHeader("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}} catch (Exception e) {log.error("IPUtils ERROR ", e);}// 使用代理,則獲取第一個IP地址if (StringUtils.isEmpty(ip) && ip.length() > 15) {if (ip.indexOf(",") > 0) {ip = ip.substring(0, ip.indexOf(","));}}return ip;}public static String getIpAddrByServerHttpRequest(ServerHttpRequest request) {// 根據 HttpHeaders 獲取 請求 IP地址String ip = request.getHeaders().getFirst("X-Forwarded-For");if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("x-forwarded-for");if (ip != null && ip.length() != 0 && !UNKNOWN.equalsIgnoreCase(ip)) {// 多次反向代理后會有多個ip值,第一個ip才是真實ipif (ip.contains(IP_UTILS_FLAG)) {ip = ip.split(IP_UTILS_FLAG)[0];}}}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("X-Real-IP");}//兼容k8s集群獲取ipif (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddress().getAddress().getHostAddress();if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {//根據網卡取本機配置的IPInetAddress iNet = null;try {iNet = InetAddress.getLocalHost();} catch (UnknownHostException e) {log.error("getClientIp error: ", e);}ip = iNet.getHostAddress();}}return ip;}/*** 獲取IP地址* 使用Nginx等反向代理軟件, 則不能通過request.getRemoteAddr()獲取IP地址* 如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個,而是一串IP地址,X-Forwarded-For中第一個非unknown的有效IP字符串,則為真實IP地址*/public static String getIpAddrByHttp(ServerHttpRequest request) {String ip = null;try {ip = request.getHeaders().getFirst("x-forwarded-for");if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("WL-Proxy-Client-IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_CLIENT_IP");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("HTTP_X_FORWARDED_FOR");}if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeaders().getFirst("X-Real-IP");}} catch (Exception e) {log.error("IPUtils ERROR ", e);}// 使用代理,則獲取第一個IP地址if (StringUtils.isEmpty(ip) && ip.length() > 15) {if (ip.indexOf(",") > 0) {ip = ip.substring(0, ip.indexOf(","));}}return ip;}}

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

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

相關文章

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

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

java15學習筆記-密封類

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

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

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

Ansible 詳細筆記

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

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

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

pytorch學習筆記-加載現有的網絡模型(VGG16)、增加/修改其中的網絡層(修改為10分類)

寫在前面&#xff1a;有些地方和視頻里不一樣的是因為官方文檔更新了&#xff0c;一些參數用法不一樣也很正常&#xff0c;包括我現在的也是我這個時間節點最新的&#xff0c;誰知道過段時間會不會更新呢 建議大家不要一味看視頻/博客&#xff0c;多看看官方文檔才是正道&#…

RocketMQ 4.9.3源碼解讀-NameServer組件啟動流程分析

作者源碼閱讀筆記主要采用金山云文檔記錄的,所有的交互圖和代碼閱讀筆記都是記錄在云文檔里面,本平臺的文檔編輯實在不方便,會導致我梳理的交互圖和文檔失去原來的格式,所以整理在文檔里面,供大家閱讀交流 【金山文檔 | WPS云文檔】 namesrv 啟動流程 相關重要類介紹說明…

《嵌入式 C 語言編碼規范與工程實踐個人筆記》參考華為C語言規范標準

《嵌入式 C 語言編碼規范與工程實踐個人筆記》參考華為C語言規范標準 前言 在電子系統開發領域&#xff0c;C 語言作為底層開發的核心語言&#xff0c;其代碼質量直接關系到系統的穩定性、可維護性和擴展性。良好的編碼規范不僅是團隊協作的基礎&#xff0c;更是降低生命周期成…

純半精度模型和全精度模型的耗時分別為248微秒和1400微秒。混合精度模型371微秒比原始模型快大約四倍!

不過有一點需要注意:在上下文管理器內部生成的任何輸出,必然會采用該上下文管理器的數據類型。因此,之后我們必須將這些輸出轉換回FP32(例如,使用float()函數)。 with torch.autocast(device_type="cuda", dtype=torch.float16): res16 = mixed32(torch.randn…

一款開源的遠程桌面軟件,旨在為用戶提供流暢的游戲體驗,支持 2K 分辨率、60 FPS,延遲僅為 40ms。

軟件介紹 CloudPlayPlus&#xff08;云玩加&#xff09;是一款令人驚艷的開源遠程桌面、串流軟件&#xff0c;云玩加由個人開發者開發者&#xff0c;具有四大特征&#xff1a;開源、免費、低延遲、安全。 軟件使用 客戶端支持多個平臺&#xff0c;包括 Windows、Mac OS、安卓…

MySql——binlog和redolog的區別

目錄一、binlog和redolog的區別一、binlog和redolog的區別 binlog和redolog都是存儲修改的新數據&#xff0c;是否保留binlog和redolog中的一個即可。 binlog屬于整個mysql&#xff0c;是所有引擎共用的&#xff0c;不是只屬于innoDB引擎。而redolog屬于InnoDB存儲引擎。binlo…

軟件著作權產生與登記關鍵點

知識講解一、 軟件著作權的核心特征與權利內容自動產生原則&#xff1a; 這是軟件著作權最核心、最重要的特征。產生時間&#xff1a; 軟件著作權自軟件開發完成之日起自動產生。法律依據&#xff1a; 《中華人民共和國著作權法》第二條及《計算機軟件保護條例》第五條明確規定…

什么是主成分分析(PCA)和數據降維

主成分分析&#xff08;PCA&#xff09;和數據降維是機器學習和統計學中處理高維數據的核心工具。下面用清晰的結構解釋其概念、原理和應用&#xff1a; 一、數據降維&#xff08;Dimensionality Reduction&#xff09; 1. 是什么&#xff1f; 目標&#xff1a;將高維數據&…

圖論(4)單源賦權最短路徑算法實現(BFS實現)

目錄 1. 什么是賦權最短路徑 2. 賦權最短路徑中的關鍵概念 3. Dijkstra 算法的基本思想 4. Dijkstra 算法實現&#xff08;Java&#xff09; 1. 什么是賦權最短路徑 在圖論中&#xff0c;最短路徑問題是指在圖中尋找兩點之間路徑總權重最小的路徑問題。如果圖的每條邊都帶…

【Lua】題目小練9

題目&#xff1a;實現一個簡單的“銀行賬戶”類要求&#xff1a;使用 元表 模擬面向對象。支持以下功能&#xff1a;Account:new(owner, balance) 創建賬戶&#xff08;初始余額可選&#xff0c;默認為 0&#xff09;。deposit(amount) 存款&#xff08;不能為負數&#xff09;…

【二分圖】染色問題

核心思想&#xff1a;為每一個未染色的&#xff0c;對它自己和它的鄰居進行染色&#xff0c;看是否會出現沖突時間復雜度O&#xff08;nm&#xff09;#include<bits/stdc.h> using namespace std; using lllong long; const int N200010; int n,m; vector<int>edge…

報數游戲(我將每文更新tips)

今日tips&#xff1a;報數游戲題目描述報數游戲的游戲規則如下&#xff1a;對一個區間內的整數進行報數&#xff0c;若遇到的數字是質數或個位數是 1&#xff0c;則不報數&#xff0c;輸出 pass。 給定開始游戲的第一個整數 a&#xff0c;及結束游戲時的最后一個整數 b&#xf…

大模型開發 - 基于Spring AI 借助MCP Client 通過STDIO和SSE協議調用MCP Server (上)

文章目錄概述MCP協議&#xff1a;為AI應用連接外部世界的橋梁MCP Server&#xff1a;上下文與能力的提供者基于Spring AI 1.0.0的開發之路1. 使用Spring AI構建MCP客戶端2. 使用Spring AI構建MCP服務器Mcp Client 實戰整體架構概覽技術棧Codepom配置mcp servers(sse&stdio)…

分析三個文件--啟動文件、鏈接文件、map文件

目錄 啟動文件 鏈接文件 部分map文件內容 FLASH物理地址(0x08000000開始)的映射關系 0x08000000 之前地址空間 啟動文件 ;******************** (C) COPYRIGHT 2016 STMicroelectronics ******************** ;* File Name : startup_stm32f40_41xxx.s ;* Author…

從零開始學Python之數據結構(字符串以及數字)

一、字符串 1.1 怎么定義字符串 字符串是Python最常用的數據結構之一。在 Python 里是用于處理文本數據的&#xff0c;比如存儲姓名、文章內容等文本信息 。 定義方式&#xff1a; 單引號&#xff1a;用單引號 包裹文本&#xff0c;如 name Alice &#xff0c;單引號內可…