Spring Security 源码分析一:Spring Security 认证过程 - 教程

news/2026/1/25 8:07:06/文章来源:https://www.cnblogs.com/tlnshuju/p/19528255

Spring Security 源码分析一:Spring Security 认证过程 - 教程

2026-01-25 08:03  tlnshuju  阅读(0)  评论(0)    收藏  举报

1. Spring Security 整体架构概览

Spring Security 的认证过程是一个复杂而精密的流程,涉及多个核心组件的协作。在深入分析之前,我们先了解整体的认证架构:

text

HTTP Request → Security Filter Chain → AuthenticationManager → AuthenticationProvider → UserDetailsService

2. 认证入口:Security Filter Chain

2.1 FilterChainProxy 的初始化

java

// FilterChainProxy 是 Spring Security 的核心过滤器
public class FilterChainProxy extends GenericFilterBean {private List filterChains;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 1. 获取当前请求对应的 SecurityFilterChainList filters = getFilters((HttpServletRequest) request);if (filters == null || filters.size() == 0) {// 没有匹配的过滤器链,直接继续原有过滤器链chain.doFilter(request, response);return;}// 2. 创建 VirtualFilterChain 执行安全过滤器VirtualFilterChain vfc = new VirtualFilterChain((HttpServletRequest) request, chain, filters);vfc.doFilter(request, response);}private List getFilters(HttpServletRequest request) {for (SecurityFilterChain chain : filterChains) {if (chain.matches(request)) {return chain.getFilters();}}return null;}
}

2.2 默认的安全过滤器链

Spring Security 默认配置了以下过滤器(按顺序执行):

java

// DefaultSecurityFilterChain 包含的标准过滤器顺序
public static class DefaultFilters {static List> get(WebSecurityConfigurerAdapter adapter) {List> filters = new ArrayList<>();// 核心认证相关过滤器filters.add(WebAsyncManagerIntegrationFilter.class);filters.add(SecurityContextPersistenceFilter.class);  // 安全上下文持久化filters.add(HeaderWriterFilter.class);filters.add(CsrfFilter.class);filters.add(LogoutFilter.class);  // 注销处理// 用户名密码认证过滤器filters.add(UsernamePasswordAuthenticationFilter.class);filters.add(DefaultLoginPageGeneratingFilter.class);filters.add(DefaultLogoutPageGeneratingFilter.class);filters.add(BasicAuthenticationFilter.class);  // HTTP Basic 认证filters.add(RequestCacheAwareFilter.class);filters.add(SecurityContextHolderAwareRequestFilter.class);filters.add(AnonymousAuthenticationFilter.class);  // 匿名认证filters.add(SessionManagementFilter.class);filters.add(ExceptionTranslationFilter.class);  // 异常转换filters.add(FilterSecurityInterceptor.class);   // 授权拦截return filters;}
}

3. 认证核心流程详解

3.1 SecurityContextPersistenceFilter - 安全上下文持久化

java

public class SecurityContextPersistenceFilter extends GenericFilterBean {private SecurityContextRepository repo;@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;// 1. 从 SecurityContextRepository 加载 SecurityContextSecurityContext contextBeforeChainExecution = repo.loadContext(new HttpRequestResponseHolder(request, response));try {// 2. 设置到 SecurityContextHolderSecurityContextHolder.setContext(contextBeforeChainExecution);// 3. 继续执行过滤器链chain.doFilter(request, response);} finally {// 4. 清理 SecurityContextHolderSecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();SecurityContextHolder.clearContext();// 5. 保存 SecurityContext 到 Repositoryrepo.saveContext(contextAfterChainExecution, request, response);}}
}

3.2 UsernamePasswordAuthenticationFilter - 表单认证处理

java

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;// 认证处理的核心方法@Overridepublic Authentication attemptAuthentication(HttpServletRequest request,HttpServletResponse response) throws AuthenticationException {// 1. 验证请求方法必须是 POSTif (postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}// 2. 从请求参数中提取用户名和密码String username = obtainUsername(request);String password = obtainPassword(request);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();// 3. 创建认证令牌UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// 4. 设置详细信息setDetails(request, authRequest);// 5. 委托给 AuthenticationManager 进行认证return this.getAuthenticationManager().authenticate(authRequest);}protected String obtainUsername(HttpServletRequest request) {return request.getParameter(usernameParameter);}protected String obtainPassword(HttpServletRequest request) {return request.getParameter(passwordParameter);}
}

3.3 AuthenticationManager 认证管理器

ProviderManager 实现

java

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {private List providers;private AuthenticationManager parent;private AuthenticationEventPublisher eventPublisher;@Overridepublic Authentication authenticate(Authentication authentication)throws AuthenticationException {Class toTest = authentication.getClass();AuthenticationException lastException = null;Authentication result = null;// 1. 遍历所有 AuthenticationProvider 尝试认证for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}try {// 2. 调用具体 Provider 进行认证result = provider.authenticate(authentication);if (result != null) {// 3. 认证成功,复制详细信息copyDetails(authentication, result);break;}} catch (AccountStatusException | InternalAuthenticationServiceException e) {// 4. 处理特定异常prepareException(e, authentication);throw e;} catch (AuthenticationException e) {lastException = e;}}// 5. 如果没有任何 Provider 认证成功if (result == null && parent != null) {try {// 6. 尝试父级 AuthenticationManagerresult = parent.authenticate(authentication);} catch (ProviderNotFoundException e) {// 忽略,继续处理} catch (AuthenticationException e) {lastException = e;}}if (result != null) {// 7. 认证成功,发布事件if (eventPublisher != null) {eventPublisher.publishAuthenticationSuccess(result);}return result;}// 8. 认证失败处理if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}prepareException(lastException, authentication);throw lastException;}
}

3.4 DaoAuthenticationProvider - 基于数据库的认证提供者

java

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {private PasswordEncoder passwordEncoder;private UserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;// 主要的认证逻辑@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {// 1. 密码验证if (authentication.getCredentials() == null) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}// 2. 获取提交的密码String presentedPassword = authentication.getCredentials().toString();// 3. 使用 PasswordEncoder 验证密码if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials","Bad credentials"));}}// 检索用户信息@Overrideprotected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {try {// 1. 通过 UserDetailsService 加载用户UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;} catch (UsernameNotFoundException ex) {// 2. 用户不存在异常处理mitigateAgainstTimingAttack(authentication);throw ex;} catch (InternalAuthenticationServiceException ex) {throw ex;} catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}// 防止时序攻击private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials().toString();// 执行密码编码操作,消耗与正常认证相同的时间this.passwordEncoder.matches(presentedPassword, TIMING_ATTACK_PROTECTION);}}
}

3.5 UserDetailsService 用户详情服务

java

public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}// 内存实现示例
public class InMemoryUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {private final Map users = new HashMap<>();@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {MutableUserDetails user = this.users.get(username.toLowerCase());if (user == null) {throw new UsernameNotFoundException(username);}return new User(user.getUsername(), user.getPassword(), user.isEnabled(),user.isAccountNonExpired(), user.isCredentialsNonExpired(),user.isAccountNonLocked(), user.getAuthorities());}
}// 数据库实现示例
@Service
public class JdbcUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 从数据库查询用户User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));// 2. 查询用户权限List authorities = authorityRepository.findByUserId(user.getId()).stream().map(authority -> new SimpleGrantedAuthority(authority.getAuthority())).collect(Collectors.toList());// 3. 构建 UserDetails 对象return org.springframework.security.core.userdetails.User.builder().username(user.getUsername()).password(user.getPassword()).authorities(authorities).accountExpired(user.isAccountExpired()).accountLocked(user.isAccountLocked()).credentialsExpired(user.isCredentialsExpired()).disabled(!user.isEnabled()).build();}
}

4. 密码编码与验证

4.1 PasswordEncoder 接口与实现

java

public interface PasswordEncoder {// 编码原始密码String encode(CharSequence rawPassword);// 验证密码是否匹配boolean matches(CharSequence rawPassword, String encodedPassword);// 检查编码是否需要升级default boolean upgradeEncoding(String encodedPassword) {return false;}
}// BCrypt 实现
public class BCryptPasswordEncoder implements PasswordEncoder {private final BCryptPasswordEncoder.BCryptVersion version;private final int strength;private final SecureRandom random;@Overridepublic String encode(CharSequence rawPassword) {if (rawPassword == null) {throw new IllegalArgumentException("rawPassword cannot be null");}String salt = getSalt();return BCrypt.hashpw(rawPassword.toString(), salt);}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {if (rawPassword == null) {throw new IllegalArgumentException("rawPassword cannot be null");}if (encodedPassword == null || encodedPassword.length() == 0) {logger.warn("Empty encoded password");return false;}if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {logger.warn("Encoded password does not look like BCrypt");return false;}return BCrypt.checkpw(rawPassword.toString(), encodedPassword);}private String getSalt() {if (this.random != null) {return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);}return BCrypt.gensalt(this.version.getVersion(), this.strength);}
}

4.2 DelegatingPasswordEncoder 委托密码编码器

java

public class DelegatingPasswordEncoder implements PasswordEncoder {private static final String PREFIX = "{";private static final String SUFFIX = "}";private final String idForEncode;private final PasswordEncoder passwordEncoderForEncode;private final Map idToPasswordEncoder;@Overridepublic String encode(CharSequence rawPassword) {// 添加编码器ID前缀return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);}@Overridepublic boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {if (rawPassword == null && prefixEncodedPassword == null) {return true;}// 1. 提取编码器IDString id = extractId(prefixEncodedPassword);// 2. 获取对应的密码编码器PasswordEncoder delegate = this.idToPasswordEncoder.get(id);if (delegate == null) {throw new IllegalArgumentException("There is no PasswordEncoder mapped for id \"" + id + "\"");}// 3. 提取编码后的密码(不含前缀)String encodedPassword = extractEncodedPassword(prefixEncodedPassword);// 4. 使用对应的编码器验证密码return delegate.matches(rawPassword, encodedPassword);}private String extractId(String prefixEncodedPassword) {if (prefixEncodedPassword == null) {return null;}int start = prefixEncodedPassword.indexOf(PREFIX);if (start != 0) {return null;}int end = prefixEncodedPassword.indexOf(SUFFIX, start);if (end < 0) {return null;}return prefixEncodedPassword.substring(start + 1, end);}
}

5. 认证成功与失败处理

5.1 认证成功处理

java

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBeanimplements ApplicationEventPublisherAware, MessageSourceAware {protected void successfulAuthentication(HttpServletRequest request,HttpServletResponse response, FilterChain chain, Authentication authResult)throws IOException, ServletException {// 1. 将认证信息设置到 SecurityContextSecurityContext context = SecurityContextHolder.createEmptyContext();context.setAuthentication(authResult);SecurityContextHolder.setContext(context);// 2. 发布认证成功事件if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));}// 3. 调用成功处理器this.successHandler.onAuthenticationSuccess(request, response, authResult);}
}// 默认的成功处理器
public class SavedRequestAwareAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)throws ServletException, IOException {// 1. 获取保存的请求SavedRequest savedRequest = requestCache.getRequest(request, response);if (savedRequest == null) {// 2. 没有保存的请求,使用默认目标页super.onAuthenticationSuccess(request, response, authentication);return;}String targetUrlParameter = getTargetUrlParameter();if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {// 3. 明确指定了目标页或总是使用默认页requestCache.removeRequest(request, response);super.onAuthenticationSuccess(request, response, authentication);return;}// 4. 清除保存的请求clearAuthenticationAttributes(request);// 5. 重定向到原始请求页面String targetUrl = savedRequest.getRedirectUrl();getRedirectStrategy().sendRedirect(request, response, targetUrl);}
}

5.2 认证失败处理

java

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean {protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response, AuthenticationException failed)throws IOException, ServletException {// 1. 清除 SecurityContextSecurityContextHolder.clearContext();// 2. 发布认证失败事件if (this.eventPublisher != null) {this.eventPublisher.publishEvent(new AuthenticationFailureBadCredentialsEvent(authentication, failed));}// 3. 记住我服务处理rememberMeServices.loginFail(request, response);// 4. 调用失败处理器this.failureHandler.onAuthenticationFailure(request, response, failed);}
}// 默认的失败处理器
public class SimpleUrlAuthenticationFailureHandler implements AuthenticationFailureHandler {private String defaultFailureUrl;private boolean forwardToDestination = false;private boolean allowSessionCreation = true;private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();@Overridepublic void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException {if (defaultFailureUrl == null) {logger.debug("No failure URL set, sending 401 Unauthorized error");response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());return;}// 保存异常信息到 sessionsaveException(request, exception);if (forwardToDestination) {logger.debug("Forwarding to " + defaultFailureUrl);request.getRequestDispatcher(defaultFailureUrl).forward(request, response);} else {redirectStrategy.sendRedirect(request, response, defaultFailureUrl);}}
}

6. SecurityContext 安全上下文管理

6.1 SecurityContext 接口与实现

java

public interface SecurityContext extends Serializable {Authentication getAuthentication();void setAuthentication(Authentication authentication);
}// 默认实现
public class SecurityContextImpl implements SecurityContext {private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;private Authentication authentication;@Overridepublic Authentication getAuthentication() {return authentication;}@Overridepublic void setAuthentication(Authentication authentication) {this.authentication = authentication;}
}// 安全上下文持有器
public class SecurityContextHolder {public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";public static final String MODE_GLOBAL = "MODE_GLOBAL";private static SecurityContextHolderStrategy strategy;private static int initializeCount = 0;static {initialize();}public static void clearContext() {strategy.clearContext();}public static SecurityContext getContext() {return strategy.getContext();}public static void setContext(SecurityContext context) {strategy.setContext(context);}private static void initialize() {// 初始化策略String strategyName = System.getProperty(SYSTEM_PROPERTY);if (strategyName == null) {strategyName = MODE_THREADLOCAL;}initializeStrategy(strategyName);initializeCount++;}
}

6.2 SecurityContextRepository 安全上下文存储

java

public interface SecurityContextRepository {SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);boolean containsContext(HttpServletRequest request);
}// 基于 HttpSession 的实现
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {public static final String SPRING_SECURITY_CONTEXT_KEY = "SPRING_SECURITY_CONTEXT";@Overridepublic SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {HttpServletRequest request = requestResponseHolder.getRequest();HttpServletResponse response = requestResponseHolder.getResponse();HttpSession httpSession = request.getSession(false);SecurityContext context = readSecurityContextFromSession(httpSession);if (context == null) {context = generateNewContext();}return context;}private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {if (httpSession == null) {return null;}Object contextFromSession = httpSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY);if (contextFromSession == null) {return null;}if (!(contextFromSession instanceof SecurityContext)) {return null;}return (SecurityContext) contextFromSession;}@Overridepublic void saveContext(SecurityContext context, HttpServletRequest request,HttpServletResponse response) {HttpSession httpSession = request.getSession(false);if (httpSession == null) {// 没有 Session 且认证为 null,不需要保存if (context.getAuthentication() == null) {return;}// 创建新的 SessionhttpSession = request.getSession(true);}httpSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, context);}
}

7. 匿名认证处理

7.1 AnonymousAuthenticationFilter

java

public class AnonymousAuthenticationFilter extends GenericFilterBean implements InitializingBean {private AuthenticationDetailsSource authenticationDetailsSource;private String key;private Object principal;private List authorities;@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {// 1. 检查是否已有认证信息if (SecurityContextHolder.getContext().getAuthentication() == null) {// 2. 创建匿名认证令牌Authentication authentication = createAuthentication((HttpServletRequest) req);// 3. 设置到安全上下文SecurityContextHolder.getContext().setAuthentication(authentication);}chain.doFilter(req, res);}protected Authentication createAuthentication(HttpServletRequest request) {AnonymousAuthenticationToken token = new AnonymousAuthenticationToken(key, principal, authorities);token.setDetails(authenticationDetailsSource.buildDetails(request));return token;}
}

8. 异常处理与转换

8.1 ExceptionTranslationFilter

java

public class ExceptionTranslationFilter extends GenericFilterBean {private AuthenticationEntryPoint authenticationEntryPoint;private AccessDeniedHandler accessDeniedHandler;private final AuthenticationTrustResolver authenticationTrustResolver;private final ThrowableAnalyzer throwableAnalyzer;@Overridepublic void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;HttpServletResponse response = (HttpServletResponse) res;try {chain.doFilter(request, response);} catch (Exception ex) {// 处理过滤器链中抛出的异常Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);// 1. 处理认证异常RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);if (ase != null) {handleAuthenticationException(request, response, chain, (AuthenticationException) ase);return;}// 2. 处理访问拒绝异常ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);if (ase != null) {handleAccessDeniedException(request, response, chain, (AccessDeniedException) ase);return;}}}private void handleAuthenticationException(HttpServletRequest request,HttpServletResponse response, FilterChain chain, AuthenticationException failed)throws IOException, ServletException {// 1. 清除 SecurityContextSecurityContextHolder.clearContext();// 2. 记录日志logger.debug("Authentication exception occurred; redirecting to authentication entry point", failed);// 3. 调用认证入口点authenticationEntryPoint.commence(request, response, failed);}private void handleAccessDeniedException(HttpServletRequest request,HttpServletResponse response, FilterChain chain, AccessDeniedException denied)throws IOException, ServletException {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// 1. 检查是否是匿名用户或记住我用户if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {logger.debug("Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point", denied);// 2. 匿名或记住我用户,重定向到登录页authenticationEntryPoint.commence(request, response,new InsufficientAuthenticationException("Full authentication is required to access this resource"));} else {logger.debug("Access is denied (user is not anonymous); delegating to AccessDeniedHandler", denied);// 3. 认证用户但权限不足,调用访问拒绝处理器accessDeniedHandler.handle(request, response, denied);}}
}

9. 完整的认证流程总结

通过以上源码分析,我们可以总结出 Spring Security 认证的完整流程:

  1. 请求拦截:SecurityFilterChain 拦截 HTTP 请求

  2. 上下文加载:SecurityContextPersistenceFilter 从 Session 加载 SecurityContext

  3. 认证处理:UsernamePasswordAuthenticationFilter 处理表单登录

  4. 认证管理:AuthenticationManager 委托给合适的 AuthenticationProvider

  5. 用户加载:DaoAuthenticationProvider 通过 UserDetailsService 加载用户信息

  6. 密码验证:PasswordEncoder 验证密码正确性

  7. 认证结果:认证成功创建 Authentication 对象,失败抛出异常

  8. 上下文保存:认证信息保存到 SecurityContext 和 Session

  9. 后续处理:根据认证结果进行成功或失败的重定向/响应

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1213389.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 列表 - 详解

Python 列表 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "C…

2026年知名的阳台栏杆/楼梯栏杆用户口碑最好的厂家榜

在阳台栏杆和楼梯栏杆行业,产品质量、设计创新和售后服务是决定用户口碑的三大核心因素。通过对全国范围内200余家生产企业的实地考察、用户调研及产品测试,我们筛选出五家综合表现突出的企业。本次评选特别关注市场…

2026年口碑好的45#钢材/Cr12钢材厂家推荐及采购参考

在模具钢和特种钢材领域,45钢材和Cr12钢材作为两种基础但关键的材料,其质量稳定性、供货能力和价格合理性是采购决策的核心考量因素。本文基于2026年行业调研数据、终端用户反馈及供应链稳定性评估,筛选出五家表现突…

游戏ROM存储优化:从格式困境到高效解决方案

游戏ROM存储优化&#xff1a;从格式困境到高效解决方案 【免费下载链接】romm A beautiful, powerful, self-hosted rom manager 项目地址: https://gitcode.com/GitHub_Trending/rom/romm 你是否遇到过这样的情况&#xff1a;精心整理的游戏库突然提示存储空间不足&…

2026年靠谱的高精度油压机/液压校直油压机行业内知名厂家排行榜

在液压设备制造领域,选择一家可靠的供应商对于企业生产效率和产品质量至关重要。本文基于设备精度、技术创新能力、市场口碑、售后服务体系和行业应用案例五个维度,对国内高精度油压机/液压校直油压机制造商进行客观…

2026年靠谱的不锈钢定制网/不锈钢鹦鹉笼高评价厂家推荐榜

在金属制品行业,选择一家可靠的不锈钢定制网和不锈钢鹦鹉笼生产厂家需要考虑多个维度:技术实力、生产工艺、材料品质、定制能力以及市场口碑。经过对华南地区不锈钢制造企业的实地调研和客户反馈分析,我们筛选出5家…

2026年评价高的碳化硅耐磨涂层/浆液泵耐磨涂层厂家推荐及选购参考榜

在工业设备维护领域,碳化硅耐磨涂层和浆液泵耐磨涂层的选择直接影响设备寿命和运行效率。本文基于产品性能、技术研发实力、客户口碑及实际应用效果等维度,筛选出5家值得关注的厂家。其中,襄阳市百盾防护涂层材料有…

当AI遇见交易:NOFX平台如何重构投资决策逻辑

当AI遇见交易&#xff1a;NOFX平台如何重构投资决策逻辑 【免费下载链接】nofx NOFX: Defining the Next-Generation AI Trading Operating System. A multi-exchange Al trading platform(Binance/Hyperliquid/Aster) with multi-Ai competition(deepseek/qwen/claude)self-ev…

通俗解释L298N驱动直流电机PWM调速原理

以下是对您提供的博文内容进行 深度润色与结构重构后的技术文章 。整体风格更贴近一位资深嵌入式工程师在技术社区中自然、扎实、略带教学口吻的分享—— 去AI感、强逻辑性、重实践细节、有个人见解 ,同时严格遵循您提出的全部优化要求(如:删除模板化标题、禁用“首先/其…

自由视角视频转换工具:打破设备壁垒的3D内容通用方案

自由视角视频转换工具&#xff1a;打破设备壁垒的3D内容通用方案 【免费下载链接】VR-reversal VR-Reversal - Player for conversion of 3D video to 2D with optional saving of head tracking data and rendering out of 2D copies. 项目地址: https://gitcode.com/gh_mir…

REINVENT 4:高效掌握AI驱动分子设计工具的新手入门指南

REINVENT 4&#xff1a;高效掌握AI驱动分子设计工具的新手入门指南 【免费下载链接】REINVENT4 AI molecular design tool for de novo design, scaffold hopping, R-group replacement, linker design and molecule optimization. 项目地址: https://gitcode.com/gh_mirrors…

多语言阅读新范式:如何让语言障碍成为过去?

多语言阅读新范式&#xff1a;如何让语言障碍成为过去&#xff1f; 【免费下载链接】MouseTooltipTranslator Mouseover Translate Any Language At Once - Chrome Extension 项目地址: https://gitcode.com/gh_mirrors/mo/MouseTooltipTranslator 为什么传统翻译工具总…

5步突破AI编程限制:Cursor Pro免费使用全攻略

5步突破AI编程限制&#xff1a;Cursor Pro免费使用全攻略 【免费下载链接】cursor-free-everyday 完全免费, 自动获取新账号,一键重置新额度, 解决机器码问题, 自动满额度 项目地址: https://gitcode.com/gh_mirrors/cu/cursor-free-everyday 在AI驱动开发的时代&#x…

反推提示词太耗显存?Z-Image-Turbo_UI界面这样用更稳

反推提示词太耗显存&#xff1f;Z-Image-Turbo_UI界面这样用更稳 Z-Image-Turbo、UI界面使用、图生图洗图、显存优化、8G显存友好、反推提示词替代方案、本地AI图像生成、Gradio界面、高清修复、LoRA加载 作为一个每天和显存打交道的AI工具实践者&#xff0c;我最近在本地部署Z…

3步打造开源无人机:ESP32从零开始的DIY实战指南

3步打造开源无人机&#xff1a;ESP32从零开始的DIY实战指南 【免费下载链接】esp-drone Mini Drone/Quadcopter Firmware for ESP32 and ESP32-S Series SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-drone 想亲手制作一架属于自己的无人机&#xff0c;…

3步实现游戏存档自由:XGP-save-extractor让进度永不丢失

3步实现游戏存档自由&#xff1a;XGP-save-extractor让进度永不丢失 【免费下载链接】XGP-save-extractor Python script to extract savefiles out of Xbox Game Pass for PC games 项目地址: https://gitcode.com/gh_mirrors/xg/XGP-save-extractor 作为Xbox Game Pas…

社交媒体内容归档完整指南:数字资产保护的专业实践

社交媒体内容归档完整指南&#xff1a;数字资产保护的专业实践 【免费下载链接】Speechless 把新浪微博的内容&#xff0c;导出成 PDF 文件进行备份的 Chrome Extension。 项目地址: https://gitcode.com/gh_mirrors/sp/Speechless 在数字化时代&#xff0c;社交媒体内容…

如何高效实现CAJ转PDF?轻量工具3分钟上手指南

如何高效实现CAJ转PDF&#xff1f;轻量工具3分钟上手指南 【免费下载链接】caj2pdf 项目地址: https://gitcode.com/gh_mirrors/caj/caj2pdf 你是否也曾遇到这样的窘境&#xff1a;导师发来的CAJ文献在手机上无法打开&#xff0c;电脑里堆满了各种格式的学术论文却难以…

macOS效率工具:窗口切换提速指南

macOS效率工具&#xff1a;窗口切换提速指南 【免费下载链接】alt-tab-macos Windows alt-tab on macOS 项目地址: https://gitcode.com/gh_mirrors/al/alt-tab-macos 在现代工作环境中&#xff0c;窗口切换效率直接影响多任务处理能力。macOS系统虽然以流畅著称&#…

快速理解电力电子系统中二极管的伏安特性曲线特征

以下是对您提供的博文《快速理解电力电子系统中二极管的伏安特性曲线特征:原理、参数与工程实践》进行 深度润色与专业重构后的终稿 。本次优化严格遵循您的全部要求: ✅ 彻底去除AI痕迹,语言自然如资深工程师现场授课 ✅ 摒弃“引言/概述/总结”等模板化结构,全文以逻…