從零搭建微服務項目Pro(第6-2章——微服務鑒權模塊SpringSecurity+JWT)

前言:

? ? ? ? 在上一章已經實現了SpringBoot單服務的鑒權,在導入SpringSecurity的相關依賴,以及使用JWT生成的accessToken和refreshToken能夠實現不同Controller乃至同一Controller中不同接口的權限單獨校驗。上一章鏈接如下:

從零搭建微服務項目Pro(第6-1章——Spring Security+JWT實現用戶鑒權訪問與token刷新)_微服務springboot+security+jwt實現刷新token-CSDN博客https://blog.csdn.net/wlf2030/article/details/146316131?spm=1001.2014.3001.5501但在微服務架構中,如何實現各服務統一使用相同鑒權模塊、如何權衡網關和鑒權模塊的關系,如何確保Feign調用不被SpringSecurity攔截如何使用對無狀態的JWT進行控制,這些問題仍需要解決。

本章針對這些問題給出了解答分析以及代碼示例。完整代碼鏈接如下:

(該鏈接為一個筆者正在開發的微服務商城項目,會逐漸整合本專欄所有功能,歡迎Star)

wlf728050719/BitGoPlushttps://github.com/wlf728050719/BitGoPlus

以及本專欄會持續更新微服務項目,每一章的項目都會基于前一章項目進行功能的完善,歡迎小伙伴們關注!同時如果只是對單章感興趣也不用從頭看,只需下載前一章項目即可,每一章都會有前置項目準備部分,跟著操作就能實現上一章的最終效果,當然如果是一直跟著做可以直接跳過這一部分。專欄目錄鏈接如下,其中Base篇為基礎微服務搭建,Pro篇為復雜模塊實現。

從零搭建微服務項目(全)-CSDN博客https://blog.csdn.net/wlf2030/article/details/145799620?spm=1001.2014.3001.5501


核心依賴:

        <!-- security --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- jwt --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency><dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId></dependency><dependency><groupId>org.glassfish.jaxb</groupId><artifactId>jaxb-runtime</artifactId></dependency>

權限實體:

package cn.bit.pojo.dto;import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;import java.util.Collection;@Getter
public class BitGoUser extends User {private final UserBaseInfo userBaseInfo;public BitGoUser(UserBaseInfo userBaseInfo, Collection<? extends GrantedAuthority> authorities) {super(userBaseInfo.getUsername(), userBaseInfo.getPassword(), authorities);this.userBaseInfo = userBaseInfo;}
}

先定義整個項目的鑒權實體類,當有請求訪問時,通過鑒權后會將這個實體類存儲在整個服務的鑒權上下文中,在加上注解進行aop操作即可實現單接口權限控制。這里是直接繼承spring.secutity定義好的user,user定義如下:

當然也可以不選擇繼承定義好的User,但必須實現UserDetails接口中的所有方法,UserDetails可以看作整個spring.security的核心。

即主要實現用戶用戶名,密碼,權限,以及是否過期,是否上鎖,是否啟用,是否權限超時。

繼續深入查看權限接口是如何定義的,其實會發現spring.security鑒權的底層實際是鑒是否有字符串。


實體初始化:

在定義好權限實體類后,我們需要給一個service用來初始化權限。具體代碼如下:

package cn.bit.service.impl;import cn.bit.constant.SecurityConstant;
import cn.bit.pojo.dto.BitGoAuthorization;
import cn.bit.pojo.dto.UserBaseInfo;
import cn.bit.service.BitGoUserService;
import cn.bit.exception.BizException;
import cn.bit.exception.SysException;
import cn.bit.client.UserClient;
import cn.bit.pojo.dto.BitGoUser;
import cn.bit.pojo.vo.R;
import lombok.AllArgsConstructor;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;import java.util.Collection;
import java.util.Set;@Service("BitGoUserService")
@AllArgsConstructor
public class BitGoUserServiceFeignImpl implements BitGoUserService {private final UserClient userClient;@Overridepublic UserDetails loadUserByUsername(String username) {return getBitGoUserFromRPC(username);}public BitGoUser getBitGoUserFromRPC(String username) {// 獲取用戶基本信息R<UserBaseInfo> userResponse = userClient.getInfoByUsername(username);if (userResponse == null) {throw new SysException("get response from user-service failed");}if (userResponse.getData() == null) {throw new BizException("用戶名不存在");}UserBaseInfo user = userResponse.getData();// 獲取用戶角色信息R<Set<BitGoAuthorization>> roleResponse = userClient.getBitGoAuthorizationByUserId(user.getUserId());if (roleResponse == null) {throw new SysException("get response from user-service failed");}// 構建BitGoUser對象return new BitGoUser(user, roleResponse.getData());}@Overridepublic boolean checkUser(BitGoUser user, Long userId) {if (user == null) {return false;}return user.getUserBaseInfo().getUserId().equals(userId);}@Overridepublic boolean checkAdmin(BitGoUser user) {return checkRoleAndTenantId(user, null, SecurityConstant.ROLE_ADMIN);}@Overridepublic boolean checkShopKeeper(BitGoUser user, Long tenantId) {return checkRoleAndTenantId(user, tenantId, SecurityConstant.ROLE_SHOPKEEPER);}@Overridepublic boolean checkClerk(BitGoUser user, Long tenantId) {return checkRoleAndTenantId(user, tenantId, SecurityConstant.ROLE_CLERK);}private boolean checkRoleAndTenantId(BitGoUser user, Long tenantId, String roleCode) {if (user == null) {return false;}// 獲取用戶的所有授權信息Collection<? extends GrantedAuthority> authorities = user.getAuthorities();// 檢查是否有匹配的角色return authorities.stream().filter(auth -> auth instanceof BitGoAuthorization).map(auth -> (BitGoAuthorization) auth).anyMatch(auth -> {// 1. 先檢查角色是否匹配boolean roleMatches = auth.getRoleCode().equals(roleCode);// 2. 如果角色不匹配,直接返回 falseif (!roleMatches) {return false;}// 3. 如果角色匹配,且不需要檢查租戶(tenantId == null),則直接返回 trueif (tenantId == null) {return true;}// 4. 如果需要檢查租戶,則檢查該角色的租戶是否匹配return tenantId.equals(auth.getTenantId());});}}

這里先是定義了一個服務接口方便后續拓展,接口如下:

主要功能是加載用戶,以及判斷用戶的身份是否符合要求。加載用戶時遠程調用user-service從數據庫提取用戶權限并裝載實體類。主要核心是實現UserDetailService的方法。


實體類注入:

我們需要將實體類注入上下文中,方便每個接口進行鑒權。這里是在每次請求的過濾器進行注入:

package cn.bit.filter;import cn.bit.constant.RedisKey;
import cn.bit.pojo.dto.BitGoUser;
import cn.bit.pojo.dto.InternalServiceAuthentication;
import cn.bit.constant.SecurityConstant;
import cn.bit.util.JwtUtil;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@AllArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {private JwtUtil jwtUtil;private UserDetailsService userDetailsService;private RedisTemplate<String, Object> redisTemplate;@SuppressWarnings("checkstyle:ReturnCount")@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException {final String authorizationHeader = request.getHeader(SecurityConstant.HEADER_AUTHORIZATION);final String sourceHeader = request.getHeader(SecurityConstant.HEADER_SOURCE);// 處理內部服務Tokenif (authorizationHeader != null && authorizationHeader.startsWith(SecurityConstant.TAG_INTERNAL)&& sourceHeader != null && sourceHeader.startsWith(SecurityConstant.TAG_SERVICE)) {String source = sourceHeader.substring(SecurityConstant.TAG_SERVICE.length());String token = authorizationHeader.substring(SecurityConstant.TAG_INTERNAL.length());if (jwtUtil.validateInternalToken(token, source)) {Authentication auth = new InternalServiceAuthentication(source);SecurityContextHolder.getContext().setAuthentication(auth);chain.doFilter(request, response);return;}}// 處理外部請求TokenString username = null;String jwt = null;if (authorizationHeader != null && authorizationHeader.startsWith(SecurityConstant.TAG_BEARER)) {jwt = authorizationHeader.substring(7);username = jwtUtil.extractData(jwt);}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);BitGoUser bitGoUser = (BitGoUser) userDetails;String key = String.format(RedisKey.TOKEN_KEY_FORMAT, bitGoUser.getUsername());String value = (String) redisTemplate.opsForValue().get(key);// 與緩存中jwt不一致禁止訪問if (value == null || !value.equals(jwt)) {chain.doFilter(request, response);return;}// 用戶被刪除或凍結時禁止訪問if (bitGoUser.getUserBaseInfo().getLockFlag() != 0 || bitGoUser.getUserBaseInfo().getDelFlag() != 0) {chain.doFilter(request, response);return;}if (jwtUtil.validateToken(jwt, userDetails)) {UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);}}chain.doFilter(request, response);}
}

即每次請求,從請求頭中取出jwt并進行解密,然后將解密出的用戶名通過之前定義的service完成權限實體的初始化并注入上下文中。


全局訪問規則配置:

在對接口進行精細化權限控制前,可對每個服務做全局規則配置。

package cn.bit.config;import cn.bit.constant.SecurityConstant;
import cn.bit.filter.JwtAuthenticationFilter;
import cn.bit.util.JwtUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MicroserviceSecurityConfig extends WebSecurityConfigurerAdapter {private final JwtUtil jwtUtil;private final UserDetailsService userDetailsService;private final RedisTemplate<String, Object> redisTemplate;// 推薦使用構造函數注入public MicroserviceSecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService,RedisTemplate<String, Object> redisTemplate) {this.jwtUtil = jwtUtil;this.userDetailsService = userDetailsService;this.redisTemplate = redisTemplate;}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 無狀態會話.and()// 將JWT過濾器添加到UsernamePasswordAuthenticationFilter之前.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class).authorizeRequests().antMatchers("/auth/**").permitAll().antMatchers("/user/open/**").permitAll().antMatchers("/api/**").hasRole(SecurityConstant.ROLE_INTERNAL_SERVICE)// 允許認證端點公開訪問.anyRequest().authenticated(); // 其他所有請求需要認證}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter(jwtUtil, userDetailsService, redisTemplate);}
}

這里的config中即配置允許所有用戶訪問/auth路徑下接口和/user/open接口,同時只允許有內部服務權限的用戶訪問/api接口。


單接口注解配置:

package cn.bit.annotation;import org.springframework.security.access.prepost.PreAuthorize;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("@BitGoUserService.checkAdmin(authentication.principal)")
public @interface Admin {
}

定義注解Admin,當調用接口前會執行名稱為BitGoService的checkAdmin方法,方法實參為上下文中的實體,這樣當上下文中權限實體被判定為true時才允許通過。

同時每個接口還能隨時從上下文中取出權限實體類進行操作。下面接口為只有管理員用戶能夠訪問,且輸出訪問實體類的用戶id的示例。

使用工具類如下:

package cn.bit.util;import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;/*** 安全工具類** @author L.cm*/
@UtilityClass
public class SecurityUtils {/*** 獲取Authentication*/public Authentication getAuthentication() {return SecurityContextHolder.getContext().getAuthentication();}/*** 獲取用戶*/public User getUser(Authentication authentication) {if (authentication == null || authentication.getPrincipal() == null) {return null;}Object principal = authentication.getPrincipal();if (principal instanceof User) {return (User) principal;}return null;}/*** 獲取用戶*/public User getUser() {Authentication authentication = getAuthentication();return getUser(authentication);}
}

內部服務鑒權:

在添加鑒權后,會發現原有的feign調用也一并被攔截。這里可以同樣為feign做配置。實現思路有三種,一種是使用固定的key,在jwt過濾器時提取到key后單獨設置,但key需要單獨配置,且泄露后會造成較大危害。一種是服務內部的api允許任何人訪問,內部服務通過自定義注解,當請求頭中不含有某個自定義header時視為非法訪問,網關對所有原始請求清洗對應header,同時feign調用時添加上對應請求頭。但當請求頭header內容泄露以及服務端口泄露,可直接不經過網關訪問從而造成攻擊。一種是同樣為feign調用添加jwt識別header,但設置jwt過期時間很短,這樣即使jwt意外泄漏也不會造成過大危害,只是稍微影響服務間調用的性能。很明細第三種最為安全,只有當jwt密鑰泄露,或持續抓取服務間調用請求內容并攻擊(這兩種情況無論哪種防護方法都無解)才會出現問題。具體配置如下:

package cn.bit.config;import cn.bit.constant.SecurityConstant;
import cn.bit.util.JwtUtil;
import feign.RequestInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class FeignSecurityConfiguration {@Bean@ConditionalOnMissingBeanpublic RequestInterceptor requestInterceptor(JwtUtil jwtUtil) {return template -> {// 從配置獲取服務名,而不是硬編碼String serviceName = template.feignTarget().name();String token = jwtUtil.generateInternalToken(serviceName);template.header(SecurityConstant.HEADER_AUTHORIZATION, SecurityConstant.TAG_INTERNAL + token);template.header(SecurityConstant.HEADER_SOURCE, SecurityConstant.TAG_SERVICE + serviceName);};}
}

對應前面filter內容

以及全局配置


JWT密鑰狀態管理:

盡管jwt無狀態管理能夠極大減小服務器存儲壓力,但試想下面案例,當用戶密碼泄露后,他人使用密碼獲取token,用戶修改密碼試圖減小損失,他人使用原有token仍然能夠訪問對應接口,即同一時間能有多個token對應同一用戶并同時操作,這顯然是存在問題的,因此需要引入緩存存儲每次頒發的token,當用戶的token與緩存中token不一致時,拒絕訪問。

對應過濾器內容

對應登錄內容:


各服務啟用鑒權:

上述所有內容定義在common-security模塊中

并將所有需要的bean導出給每個服務使用

各服務只需要在對應pom導入即可使用。


最后:

? ? ? ? spring.security其實的整體思路很清晰,但一定要搞清楚其和網關的關系,一開始我誤以為jwt過濾器設置在網關,對于每一個請求網關均提取token并調用auth服務獲取user后存取在上下文中,之后各服務直接從網關給的上下文拿取實體類,但實際上我犯了一個很嚴重的錯誤,spring cloud的每個服務都是獨立的上下文,存在網關中的上下文是不能傳遞給其他服務的,正確思路應該如下,網關只負載基本的header清洗以及路由轉發,jwt過濾器設置在每個服務上,有n個服務,則項目總共有n個jwt過濾器,同時有n個全局訪問規則配置在生效,但由于所有服務都裝填同一個配置類導致看起來像是在給網關配置訪問規則,各服務使用UserDetailService完成實體類初始化后注入自己的上下文,并通過注解完成權限控制。不知道上面解釋能否解答你的問題,如果仍不明白,強烈建議git上面代碼鏈接,并查看spring.security的源碼。

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

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

相關文章

win安裝軟件

win安裝軟件 jdk安裝 jdk安裝 首先去官網下載適合系統版本的JDK&#xff0c;下載地址&#xff1a; http://www.oracle.com/technetwork/java/javase/downloads/index.html進入下載頁面&#xff0c;如下圖&#xff1a; 首先選擇&#xff1a;Accept License Agreement單選按鈕&…

Prompt-Tuning 提示詞微調

1. Hard Prompt 定義&#xff1a; Hard prompt 是一種更為具體和明確的提示&#xff0c;要求模型按照給定的信息生成精確的結果&#xff0c;通常用于需要模型提供準確答案的任務. 原理&#xff1a; Prompt Tuning原理如下圖所示&#xff1a;凍結主模型全部參數&#xff0c;在…

【Vue生命周期的演變:從Vue 2到Vue 3的深度剖析】

Vue生命周期的演變&#xff1a;從Vue 2到Vue 3的深度剖析 1. 生命周期鉤子的概念與意義 Vue框架通過生命周期鉤子函數使開發者可以在組件不同階段執行自定義邏輯。這些鉤子函數是Vue組件生命周期中的關鍵切入點&#xff0c;對于控制組件行為至關重要。 2. Vue 2中的生命周期…

java ai 圖像處理

Java AI 圖像處理 圖像處理是人工智能&#xff08;AI&#xff09;領域中非常重要的一個應用方向。通過使用Java編程語言和相應的庫&#xff0c;我們可以實現各種圖像處理任務&#xff0c;如圖像識別、圖像分類、圖像分割等。本文將介紹一些常見的圖像處理算法&#xff0c;并通過…

從 0~1 保姆級 詳細版 PostgreSQL 數據庫安裝教程

PostgreSQL數據庫安裝 PostgreSQL官網 【PostgreSQL官網】 | 【PostgreSQL安裝官網_Windows】 安裝步驟 step1&#xff1a; 選擇與電腦相對應的PostgreSQL版本進行下載。 step2&#xff1a; 雙擊打開剛才下載好的文件。 step3&#xff1a; 在彈出的setup窗口中點擊 …

Keil MDK中禁用半主機(No Semihosting)

在 ARM 編譯器&#xff08;如 Keil MDK&#xff09; 中禁用半主機&#xff08;Semihosting&#xff09;并實現標準庫的基本功能&#xff0c;需要以下步驟&#xff1a; 1. 禁用半主機 #pragma import(__use_no_semihosting) // 禁用半主機模式作用&#xff1a;防止標準庫函數&…

github | 倉庫權限管理 | 開權限

省流版總結&#xff1a; github 給別人開權限&#xff1a;倉庫 -> Setting -> Cllaborate -> Add people GitHub中 將公開倉庫改為私有&#xff1a;倉庫 -> Setting -> Danger Zone&#xff08;危險區&#xff09; ->Change repository visibility( 更改倉…

快速部署大模型 Openwebui + Ollama + deepSeek-R1模型

背景 本文主要快速部署一個帶有web可交互界面的大模型的應用&#xff0c;主要用于開發測試節點&#xff0c;其中涉及到的三個組件為 open-webui Ollama deepSeek開放平臺 首先 Ollama 是一個開源的本地化大模型部署工具,提供與OpenAI兼容的Api接口&#xff0c;可以快速的運…

極狐GitLab 項目導入導出設置介紹?

極狐GitLab 是 GitLab 在中國的發行版&#xff0c;關于中文參考文檔和資料有&#xff1a; 極狐GitLab 中文文檔極狐GitLab 中文論壇極狐GitLab 官網 導入導出設置 (BASIC SELF) 導入和導出相關功能的設置。 配置允許的導入源 在從其他系統導入項目之前&#xff0c;必須為該…

信奧還能考嗎?未來三年科技特長生政策變化

近年來&#xff0c;科技特長生已成為名校錄取的“黃金敲門磚”。 從CSP-J/S到NOI&#xff0c;編程競賽成績直接關聯升學優勢。 未來三年&#xff0c;政策將如何調整&#xff1f;家長該如何提前布局&#xff1f; 一、科技特長生政策趨勢&#xff1a;2025-2027關鍵變化 1. 競…

AI測試用例生成平臺

AI測試用例生成平臺 項目背景技術棧業務描述項目展示項目重難點 項目背景 針對傳統接口測試用例設計高度依賴人工經驗、重復工作量大、覆蓋場景有限等行業痛點&#xff0c;基于大語言模型技術實現接口測試用例智能生成系統。 技術棧 LangChain框架GLM-4模型Prompt Engineeri…

操作系統-PV

&#x1f9e0; 背景&#xff1a;為什么會有 PV&#xff1f; 類比&#xff1a;內存&#xff08;生產者&#xff09; 和 CPU&#xff08;消費者&#xff09; 內存 / IO / 磁盤 / 網絡下載 → 不斷“生產數據” 例如&#xff1a;讀取文件、下載視頻、從數據庫加載信息 CPU → 負…

工廠方法模式詳解及在自動駕駛場景代碼示例(c++代碼實現)

模式定義 工廠方法模式&#xff08;Factory Method Pattern&#xff09;是一種創建型設計模式&#xff0c;通過定義抽象工廠接口將對象創建過程延遲到子類實現&#xff0c;實現對象創建與使用的解耦。該模式特別適合需要動態擴展產品類型的場景。 自動駕駛感知場景分析 自動駕…

基于 S2SH 架構的企業車輛管理系統:設計、實現與應用

在企業運營中&#xff0c;車輛管理是一項重要工作。隨著企業規模的擴大&#xff0c;車輛數量增多&#xff0c;傳統管理方式效率低下&#xff0c;難以滿足企業需求。本文介紹的基于 S2SH 的企業車輛管理系統&#xff0c;借助現代化計算機技術&#xff0c;實現車輛、駕駛員和出車…

IntelliJ IDEA download JDK

IntelliJ IDEA download JDK 自動下載各個版本JDK&#xff0c;步驟 File - Project Structure &#xff08;快捷鍵 Ctrl Shift Alt S&#xff09; 如果下載失敗&#xff0c;換個下載站點吧。一般選擇Oracle版本&#xff0c;因為java被Oracle收購了 好了。 花里胡哨&#…

MCP協議在納米材料領域的深度應用:從跨尺度協同到智能研發范式重構

MCP協議在納米材料領域的深度應用&#xff1a;從跨尺度協同到智能研發范式重構 文章目錄 MCP協議在納米材料領域的深度應用&#xff1a;從跨尺度協同到智能研發范式重構一、MCP協議的技術演進與納米材料研究的適配性分析1.1 MCP協議的核心架構升級1.2 納米材料研發的核心挑戰與…

OpenAI發布GPT-4.1:開發者專屬模型的深度解析 [特殊字符]

最近OpenAI發布了GPT-4.1模型&#xff0c;卻讓不少人感到困惑。今天我們就來深入剖析這個新模型的關鍵信息&#xff01; 重要前提&#xff1a;API專屬模型 &#x1f4bb; 首先需要明確的是&#xff0c;GPT-4.1僅通過API提供&#xff0c;不會出現在聊天界面中。這是因為該模型主…

DemoGen:用于數據高效視覺運動策略學習的合成演示生成

25年2月來自清華、上海姚期智研究院和上海AI實驗室的論文“DemoGen: Synthetic Demonstration Generation for Data-Efficient Visuomotor Policy Learning”。 視覺運動策略在機器人操控中展現出巨大潛力&#xff0c;但通常需要大量人工采集的數據才能有效執行。驅動高數據需…

界面控件DevExpress WPF v25.1新功能預覽 - 文檔處理類功能升級

DevExpress WPF擁有120個控件和庫&#xff0c;將幫助您交付滿足甚至超出企業需求的高性能業務應用程序。通過DevExpress WPF能創建有著強大互動功能的XAML基礎應用程序&#xff0c;這些應用程序專注于當代客戶的需求和構建未來新一代支持觸摸的解決方案。 無論是Office辦公軟件…

Muduo網絡庫實現 [十六] - HttpServer模塊

目錄 設計思路 類的設計 模塊的實現 公有接口 私有接口 疑問點 設計思路 本模塊就是設計一個HttpServer模塊&#xff0c;提供便攜的搭建http協議的服務器的方法。那么這個模塊需要如何設計呢&#xff1f; 這還需要從Http請求說起。 首先從http請求的請求行開始分析&…