小明的Spring Security入门到深入实战

news/2025/11/25 8:46:02/文章来源:https://www.cnblogs.com/sun-10387834/p/19256676

小 明 的 摄 影 网 站 已 经 用 Spring Boot 搭 建 完 成 , 访 客 越 来 越 多 。 他 决 定 给 网 站 加 上 完 整 的 登 录 认 证 鉴 权 功 能 , 让 不 同 角 色 的 用 户 ( 游 客 、 编 辑 、 管 理 员 ) 有 不 同 的 操 作 权 限 。 今 天 , 他 就 带 着 这 个 需 求 , 开 启 Spring Security 的 学 习 之 旅 。

** 第 一 步 : 搭 建 Spring Boot 项 目 , 引 入 Spring Security **

** 小 明 的 需 求 **

“ 先 让 网 站 有 个 最 基 础 的 登 录 功 能 , 能 拦 住 未 登 录 的 用 户 。 ”

** 实 操 步 骤 **

  1. ** 创 建 Spring Boot 项 目 ** : 用 https://start.spring.io/ , 选 Spring Web ( 搭 Web 服 务 ) 、 Spring Security ( 安 全 框 架 ) 、 Spring Data JPA ( 操 作 数 据 库 ) 、 MySQL Driver ( 数 据 库 驱 动 ) 。
  2. ** 添 加 依 赖 ** ( pom.xml 核 心 部 分 ) :
    <dependencies>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId>  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-security</artifactId>  <!--  Spring Security  依  赖  -->  </dependency>  <dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-data-jpa</artifactId>  </dependency>  <dependency>  <groupId>com.mysql</groupId>  <artifactId>mysql-connector-j</artifactId>  </dependency>  
    </dependencies>  
    
  3. ** 运 行 项 目 ** : 启 动 Spring Boot , 访 问 http://localhost:8080 , 会 自 动 跳 转 到 Spring Security 的 默 认 登 录 页 。

** 现 象 与 原 理 **

  • ** 默 认 用 户 ** : 控 制 台 会 打 印 一 行 密 码 , 格 式 如 Using generated security password: abcdef12-3456-7890-abcd-ef1234567890 , 用 户 名 固 定 为 user
  • ** 原 理 ** : Spring Security 自 动 配 置 了 默 认 的 InMemoryUserDetailsManager ( 内 存 用 户 存 储 ) , 生 成 随 机 密 码 。 所 有 接 口 默 认 需 要 USER 角 色 才 能 访 问 。

💡 小 明 的 疑 问 : “ 这 密 码 每 次 启 动 都 变 , 而 且 用 户 不 能 自 己 注 册 , 得 改 ! ”

** 第 二 步 : 自 定 义 用 户 存 储 —— 从 内 存 到 数 据 库 **

** 小 明 的 需 求 **

“ 用 户 信 息 存 在 自 己 的 数 据 库 里 , 支 持 注 册 、 登 录 , 密 码 用 BCrypt 加 密 。 ”

** 核 心 概 念 **

  • ** UserDetailsService ** : Spring Security 的 “ 用 户 查 询 接 口 ” , 开 发 者 实 现 它 , 告 诉 框 架 “ 怎 么 从 数 据 库 查 用 户 ” 。
  • ** UserDetails ** : “ 用 户 信 息 封 装 类 ” , 包 含 用 户 名 、 密 码 、 角 色 、 账 户 是 否 过 期 等 信 息 。
  • ** PasswordEncoder ** : “ 密 码 加 密 器 ” , 用 BCrypt 算 法 把 明 文 密 码 变 成 哈 希 值 存 储 。

** 实 操 步 骤 **

  1. ** 建 用 户 表 ** ( MySQL ) :
    CREATE TABLE users (  id BIGINT AUTO_INCREMENT PRIMARY KEY,  username VARCHAR(50) UNIQUE NOT NULL,  --  用  户  名  password VARCHAR(100) NOT NULL,         --  BCrypt  哈  希  后  的  密  码  role VARCHAR(20) NOT NULL               --  角  色  :  ROLE_VISITOR  /  ROLE_EDITOR  /  ROLE_ADMIN  
    );  
    
  2. ** 实 现 UserDetailsService ** ( 查 数 据 库 用 户 ) :
    @Service  
    public class CustomUserDetailsService implements UserDetailsService {  @Autowired private UserRepository userRepo;  //  JPA  操  作  users  表  @Override  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {  //  1.  查  数  据  库  用  户  User user = userRepo.findByUsername(username)  .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));  //  2.  封  装  成  UserDetails  对  象  (  Spring Security  认  识  的  格  式  )  return org.springframework.security.core.userdetails.User.builder()  .username(user.getUsername())  .password(user.getPassword())  //  存  的  是  BCrypt  哈  希  值  .roles(user.getRole().replace("ROLE_", ""))  //  角  色  去  掉  ROLE_  前  缀  .build();  }  
    }  
    
  3. ** 配 置 PasswordEncoder ** ( 用 BCrypt 加 密 ) :
    @Configuration  
    public class SecurityConfig {  @Bean  public PasswordEncoder passwordEncoder() {  return new BCryptPasswordEncoder();  //  BCrypt  加  密  器  ,  自  带  随  机  盐  }  
    }  
    
  4. ** 注 册 用 户 ** ( 比 如 小 明 自 己 注 册 为 管 理 员 ) :
    @RestController  
    public class RegisterController {  @Autowired private UserRepository userRepo;  @Autowired private PasswordEncoder passwordEncoder;  @PostMapping("/register")  public String register(String username, String rawPassword, String role) {  User user = new User();  user.setUsername(username);  user.setPassword(passwordEncoder.encode(rawPassword));  //  明  文  密  码  →  BCrypt  哈  希  user.setRole("ROLE_" + role.toUpperCase());  //  角  色  加  ROLE_  前  缀  (  Spring Security  约  定  )  userRepo.save(user);  return "注册成功!";  }  
    }  
    

** 效 果 **

小 明 用 POST /register?username=xiaoming&rawPassword=123456&role=admin 注 册 后 , 数 据 库 users 表 会 存 一 条 记 录 , password 字 段 是 BCrypt 哈 希 值 ( 如 $2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx )。 登 录 时 , 框 架 会 用 BCrypt 验 证 输 入 密 码 是 否 匹 配 。

💡 小 明 的 体 会 : “ UserDetailsService 就 像 图 书 馆 的 ‘ 查 阅 员 ’ , 框 架 告 诉 他 ‘ 找 小 明 ’ , 他 就 去 数 据 库 把 小 明 的 书 ( 用 户 信 息 ) 拿 回 来 。 ”

** 第 三 步 : 配 置 权 限 控 制 —— 谁 能 干 啥 ? **

** 小 明 的 需 求 **

“ 游 客 只 能 看 照 片 , 编 辑 能 上 传 , 管 理 员 能 删 除 。 ”

** 核 心 概 念 **

  • ** HttpSecurity ** : Spring Security 的 “ Web 安 全 配 置 类 ” , 用 来 设 置 URL 访 问 权 限 、 登 录 / 注 销 页 、 CSRF 等 。
  • ** @PreAuthorize ** : “ 方 法 级 权 限 注 解 ” , 直 接 在 Controller 方 法 上 标 注 需 要 的 角 色 。

** 实 操 步 骤 **

  1. ** 配 置 URL 访 问 权 限 ** ( 在 SecurityConfig 中 ) :
    @Configuration  
    @EnableWebSecurity  //  开  启  Web  安  全  配  置  
    public class SecurityConfig {  @Autowired private CustomUserDetailsService userDetailsService;  @Autowired private PasswordEncoder passwordEncoder;  @Bean  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  http  .authorizeHttpRequests(auth -> auth  .requestMatchers("/photos/**").permitAll()  //  公  开  :  所  有  人  能  看  照  片  .requestMatchers("/upload/**").hasRole("EDITOR")  //  上  传  :  需  EDITOR  角  色  .requestMatchers("/delete/**").hasRole("ADMIN")  //  删  除  :  需  ADMIN  角  色  .anyRequest().authenticated()  //  其  他  接  口  需  登  录  )  .formLogin(form -> form  //  配  置  登  录  页  .loginPage("/my-login")  //  自  定  义  登  录  页  (  下  节  讲  )  .defaultSuccessUrl("/home")  //  登  录  成  功  跳  转  )  .logout(logout -> logout  //  配  置  注  销  .logoutUrl("/logout")  .logoutSuccessUrl("/photos")  );  return http.build();  }  //  配  置  认  证  管  理  器  (  用  自  定  义  UserDetailsService  和  PasswordEncoder  )  @Bean  public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {  return config.getAuthenticationManager();  }  
    }  
    
  2. ** 用 @PreAuthorize 做 方 法 级 权 限 ** ( 更 精 细 控 制 ) :
    @RestController  
    public class PhotoController {  //  游  客  能  看  @GetMapping("/photos")  public List<Photo> listPhotos() { ... }  //  编  辑  能  上  传  (  @PreAuthorize  需  开  启  @EnableMethodSecurity  )  @PostMapping("/upload")  @PreAuthorize("hasRole('EDITOR')")  //  直  接  标  注  需  EDITOR  角  色  public String uploadPhoto(MultipartFile file) { ... }  //  管  理  员  能  删  除  @DeleteMapping("/delete/{id}")  @PreAuthorize("hasRole('ADMIN') or hasRole('EDITOR')")  //  管  理  员  或  编  辑  都  能  删  public String deletePhoto(@PathVariable Long id) { ... }  
    }  
    
  3. ** 开 启 @PreAuthorize 支 持 ** : 在 SecurityConfig 上 加 @EnableMethodSecurity

** 效 果 **

  • 访 问 /upload 时 , 若 用 户 是 ROLE_VISITOR , 会 被 拦 截 并 返 回 403 错 误 ( 权 限 不 足 ) ;
  • @PreAuthorize 的 方 法 , 框 架 会 在 执 行 前 检 查 用 户 角 色 , 不 符 合 则 抛 异 常 。

💡 小 明 的 体 会 : “ HttpSecurity 像 ‘ 门 卫 ’ , 管 URL 级 的 大 门 ; @PreAuthorize 像 ‘ 房 间 锁 ’ , 管 方 法 级 的 小 门 , 双 保 险 ! ”

** 第 四 步 : 自 定 义 登 录 页 、 注 销 与 记 住 我 **

** 小 明 的 需 求 **

“ 默 认 登 录 页 太 丑 , 想 用 自 己 的 ; 加 个 ‘ 记 住 我 ’ 功 能 , 关 闭 浏 览 器 再 打 开 还 是 登 录 状 态 。 ”

** 实 操 步 骤 **

  1. ** 自 定 义 登 录 页 ** :
    • 写 一 个 HTML 页 面 my-login.html ( 放 resources/templates 目 录 ) , 包 含 username password 输 入 框 和 remember-me 复 选 框 ;
    • 在 SecurityConfig 中 配 置 .loginPage("/my-login") , 并 指 定 处 理 登 录 的 URL :
    .formLogin(form -> form  .loginPage("/my-login")  //  自  定  义  登  录  页  路  径  .loginProcessingUrl("/do-login")  //  处  理  登  录  的  URL  (  框  架  自  动  处  理  )  .defaultSuccessUrl("/home")  .failureUrl("/my-login?error")  //  登  录  失  败  跳  转  
    )  
    
  2. ** 开 启 “ 记 住 我 ” 功 能 ** :
    • 在 SecurityConfig 中 配 置 .rememberMe()
    .rememberMe(remember -> remember  .key("xiaoming-secret-key")  //  自  定  义  密  钥  (  防  篡  改  )  .tokenValiditySeconds(7 * 24 * 3600)  //  记  住  我  有  效  期  :  7  天  
    )  
    
    • 在 自 定 义 登 录 页 加 “ 记 住 我 ” 复 选 框 :
    <input type="checkbox" name="remember-me"> 记住我  
    
  3. ** 自 定 义 注 销 页 ** :
    • 在 SecurityConfig 中 配 置 .logout() , 默 认 注 销 URL 是 /logout , 可 自 定 义 :
    .logout(logout -> logout  .logoutUrl("/my-logout")  //  自  定  义  注  销  URL  .logoutSuccessUrl("/photos")  //  注  销  成  功  跳  转  .invalidateHttpSession(true)  //  注  销  时  清  空  Session  .deleteCookies("JSESSIONID")  //  删  除  Cookie  
    )  
    

** 效 果 **

  • 访 问 需 登 录 的 页 面 时 , 跳 转 到 小 明 自 己 写 的 my-login.html
  • 勾 选 “ 记 住 我 ” 后 , 关 闭 浏 览 器 , 7 天 内 再 访 问 网 站 仍 是 登 录 状 态 ;
  • 点 击 注 销 链 接 , 清 空 Session 和 Cookie , 跳 回 公 开 照 片 页 。

💡 小 明 的 体 会 : “ 自 定 义 登 录 页 让 网 站 更 好 看 , ‘ 记 住 我 ’ 像 ‘ 长 期 饭 卡 ’ , 不 用 每 次 都 刷 临 时 卡 。 ”

** 第 五 步 : 集 成 JWT —— 支 持 APP 与 微 服 务 **

** 小 明 的 需 求 **

“ 我 做 了 个 手 机 APP , 用 Session - Cookie 不 方 便 , 想 用 JWT Token 做 认 证 。 ”

** 核 心 概 念 **

  • ** JWT ** : “ 自 带 信 息 的 身 份 证 ” , 服 务 器 不 存 Session , 用 户 登 录 后 拿 Token , 后 续 请 求 带 Token 即 可 。
  • ** JwtAuthenticationFilter ** : 自 定 义 过 滤 器 , 从 Authorization 头 取 JWT , 验 证 后 设 置 Security Context 。

** 实 操 步 骤 **

  1. ** 添 加 JWT 依 赖 ** :
    <dependency>  <groupId>io.jsonwebtoken</groupId>  <artifactId>jjwt-api</artifactId>  <version>0.11.5</version>  
    </dependency>  
    <dependency>  <groupId>io.jsonwebtoken</groupId>  <artifactId>jjwt-impl</artifactId>  <version>0.11.5</version>  <scope>runtime</scope>  
    </dependency>  
    <dependency>  <groupId>io.jsonwebtoken</groupId>  <artifactId>jjwt-jackson</artifactId>  <version>0.11.5</version>  <scope>runtime</scope>  
    </dependency>  
    
  2. ** 写 JWT 工 具 类 ** ( 生 成 / 验 证 Token ) :
    @Component  
    public class JwtUtil {  @Value("${jwt.secret}") private String secret;  //  配  置  在  application.properties  中  @Value("${jwt.expiration}") private long expiration;  //  Token  有  效  期  (  如  3600000  毫  秒  )  //  生  成  Token  public String generateToken(UserDetails userDetails) {  Map<String, Object> claims = new HashMap<>();  claims.put("roles", userDetails.getAuthorities().stream()  .map(GrantedAuthority::getAuthority).collect(Collectors.toList()));  return Jwts.builder()  .setClaims(claims)  .setSubject(userDetails.getUsername())  .setIssuedAt(new Date())  .setExpiration(new Date(System.currentTimeMillis() + expiration))  .signWith(SignatureAlgorithm.HS512, secret)  //  用  HS512  算  法  签  名  .compact();  }  //  验  证  Token  并  取  用  户  名  public String getUsernameFromToken(String token) {  return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();  }  
    }  
    
  3. ** 写 JWT 认 证 过 滤 器 ** :
    public class JwtAuthenticationFilter extends OncePerRequestFilter {  @Autowired private JwtUtil jwtUtil;  @Autowired private CustomUserDetailsService userDetailsService;  @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {  //  1.  从  Header  取  Token  String token = request.getHeader("Authorization");  if (token != null && token.startsWith("Bearer ")) {  token = token.substring(7);  //  2.  验  证  Token  ,  取  用  户  名  String username = jwtUtil.getUsernameFromToken(token);  if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {  //  3.  查  用  户  信  息  ,  设  置  Security Context  UserDetails userDetails = userDetailsService.loadUserByUsername(username);  UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(  userDetails, null, userDetails.getAuthorities());  SecurityContextHolder.getContext().setAuthentication(auth);  }  }  chain.doFilter(request, response);  //  继  续  处  理  请  求  }  
    }  
    
  4. ** 修 改 SecurityConfig , 用 JWT 替 换 Session ** :
    @Bean  
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  http  .csrf(csrf -> csrf.disable())  //  JWT  无  状  态  ,  关  闭  CSRF  .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))  //  无  Session  .authorizeHttpRequests(auth -> auth  .requestMatchers("/api/photos/**").permitAll()  .requestMatchers("/api/upload/**").hasRole("EDITOR")  .anyRequest().authenticated()  )  .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)  //  加  JWT  过  滤  器  .formLogin(form -> form.disable())  //  禁  用  默  认  表  单  登  录  (  APP  用  JWT  )  .logout(logout -> logout.disable());  //  禁  用  默  认  注  销  return http.build();  
    }  
    

** 效 果 **

  • APP 用 户 登 录 时 , 调 POST /api/login ( 自 定 义 接 口 , 验 证 用 户 名 密 码 后 返 JWT ) ;
  • 后 续 APP 请 求 API 时 , 在 Authorization 头 带 Bearer <JWT> , 框 架 自 动 认 证 。

💡 小 明 的 体 会 : “ JWT 像 ‘ 电 子 通 行 证 ’ , APP 拿 着 它 就 能 畅 通 无 阻 , 服 务 器 不 用 记 住 谁 来 过 , 轻 松 支 持 多 端 ! ”

** 第 六 步 : 深 入 : OAuth2 第 三 方 登 录 与 安 全 防 护 **

** 小 明 的 需 求 **

“ 想 让 用 户 用 GitHub 账 号 直 接 登 录 , 还 要 防 CSRF 、 XSS 攻 击 。 ”

** 核 心 概 念 **

  • ** OAuth2 ** : “ 第 三 方 登 录 协 议 ” , 用 户 授 权 GitHub 告 诉 小 明 的 网 站 “ 这 是 我 ” , 无 需 注 册 。
  • ** Spring Security OAuth2 Client ** : 框 架 内 置 OAuth2 客 户 端 , 支 持 GitHub 、 Google 等 登 录 。

** 实 操 步 骤 **

  1. ** 配 置 GitHub OAuth2 登 录 ** :
    • 在 GitHub 开 发 者 设 置 中 创 建 OAuth App , 获 取 client-id client-secret
    • application.properties 中 配 置 :
    spring.security.oauth2.client.registration.github.client-id=你的client-id  
    spring.security.oauth2.client.registration.github.client-secret=你的client-secret  
    spring.security.oauth2.client.registration.github.scope=user:email  #  申  请  邮  箱  权  限  
    
    • 在 SecurityConfig 中 开 启 OAuth2 登 录 :
    .oauth2Login(oauth2 -> oauth2  .loginPage("/my-login")  //  自  定  义  登  录  页  加  “  GitHub  登  录  ”  按  钮  .defaultSuccessUrl("/home")  
    )  
    
  2. ** 安 全 防 护 ** ( 框 架 默 认 开 启 , 无 需 额 外 配 置 ) :
    • ** CSRF 防 护 ** : 默 认 开 启 , 框 架 自 动 生 成 CSRF 令 牌 , 表 单 提 交 时 验 证 ;
    • ** XSS 防 护 ** : Spring Boot 默 认 开 启 HTML 转 义 , 防 注 入 脚 本 ;
    • ** 会 话 固 定 攻 击 防 护 ** : 登 录 后 自 动 更 换 SessionID 。

** 效 果 **

  • 小 明 的 登 录 页 多 了 个 “ 用 GitHub 登 录 ” 按 钮 , 点 击 后 跳 转 GitHub 授 权 , 授 权 成 功 后 自 动 登 录 网 站 ;
  • 框 架 自 动 挡 住 CSRF 攻 击 , 即 使 黑 客 诱 骗 小 明 点 链 接 , 也 无 法 操 作 。

💡 小 明 的 体 会 : “ OAuth2 像 ‘ 找 朋 友 作 证 ’ , 让 GitHub 帮 忙 证 明 ‘ 这 是 小 明 ’ ; 框 架 的 安 全 防 护 像 ‘ 隐 形 铠 甲 ’ , 平 时 感 觉 不 到 , 关 键 时 刻 能 挡 刀 。 ”

** 小 明 的 学 习 总 结 **

通 过 这 几 步 , 小 明 彻 底 掌 握 了 Spring Security 配 Spring Boot 的 核 心 用 法 :

  1. ** 从 默 认 配 置 入 门 ** , 观 察 框 架 自 动 生 成 的 登 录 / 权 限 逻 辑 ;
  2. ** 自 定 义 UserDetailsService 和 PasswordEncoder ** , 连 接 自 己 的 数 据 库 , 用 BCrypt 加 密 密 码 ;
  3. ** 用 HttpSecurity 和 @PreAuthorize 配 置 权 限 ** , 实 现 URL 级 和 方 法 级 控 制 ;
  4. ** 自 定 义 登 录 / 注 销 页 、 加 “ 记 住 我 ” ** , 优 化 用 户 体 验 ;
  5. ** 集 成 JWT ** , 支 持 APP 和 微 服 务 无 状 态 认 证 ;
  6. ** 用 OAuth2 实 现 第 三 方 登 录 ** , 开 启 框 架 默 认 安 全 防 护 。

✨ 小 明 的 最 终 感 悟 :
Spring Security 不 是 一 堆 复 杂 的 配 置 , 而 是 一 个 “ 安 全 积 木 盒 ” 。 小 明 用 它 搭 建 了 摄 影 网 站 的 安 全 堡 垒 , 从 默 认 配 置 到 自 定 义 扩 展 , 从 Web 到 APP , 从 账 号 密 码 到 第 三 方 登 录 , 每 一 步 都 踩 在 “ 需 求 ” 和 “ 技 术 ” 的 结 合 点 上 。 现 在 , 他 可 以 专 心 拍 摄 更 美 的 照 片 , 而 网 站 的 安 全 , 交 给 Spring Security 就 够 了 !

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

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

相关文章

2025年国内全屋定制推荐优选榜

2025年国内全屋定制推荐优选榜行业背景与市场趋势近年来,随着消费升级和居住理念的转变,全屋定制行业在中国市场呈现出蓬勃发展的态势。据中国家居建材装饰协会最新数据显示,2024年中国全屋定制市场规模已突破5000亿…

广西一对一辅导机构权威榜单:2025年梧州、北海、防城港辅导机构综合实力榜

“耗的不是学费,是孩子转瞬即逝的升学关键期。”梧州万秀区的陈妈妈在家长群的这句吐槽,戳中了无数广西家长的痛点。她的孩子初三英语从110分跌至85分,半年内换了3家辅导机构,结果“专属教案”是网上随处可下的模板…

2025年靠谱的电缆厂家推荐及选购指南

2025年靠谱的电缆厂家推荐及选购指南行业背景与市场趋势随着我国基础设施建设的持续推进和新能源产业的快速发展,电线电缆行业迎来了新一轮增长机遇。据中国电器工业协会电线电缆分会最新数据显示,2024年我国电线电缆…

UDP敲门与零信任机制之间的关系 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

OpenHarmony后台服务开发指南:ServiceAbility与ServiceExtensionAbility全解析 - 指南

OpenHarmony后台服务开发指南:ServiceAbility与ServiceExtensionAbility全解析 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important;…

2025年比较好的光伏线缆厂家实力及用户口碑排行榜

2025年光伏线缆厂家实力及用户口碑排行榜光伏线缆行业背景与市场趋势随着全球能源结构转型加速推进,光伏产业已成为可再生能源领域的重要支柱。据国际能源署(IEA)最新数据显示,2025年全球光伏新增装机容量预计将达到…

完整教程:计算机3D视觉:Pytorch3d的环境配置与初步使用

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

.NET+AI | MEAI | ChatOptions 详解(5)

ChatOptions 详解:精准控制 AI 对话的配置利器 一句话简介 ChatOptions 是 Microsoft.Extensions.AI 中传递给 IChatClient 的统一配置容器,用于在单次请求中精准控制生成策略、工具调用和扩展特性。🎯 核心价值✅…

2025年评价高的高弹硬质棉厂家推荐及选择指南

2025年评价高的高弹硬质棉厂家推荐及选择指南行业背景与市场趋势随着全球纺织材料行业的快速发展,高弹硬质棉作为一种新型环保填充材料,近年来市场需求呈现稳定增长态势。据中国纺织工业联合会最新数据显示,2024年我…

2025年质量好的硬质棉厂家最新推荐排行榜

2025年质量好的硬质棉厂家最新推荐排行榜行业背景与市场趋势硬质棉作为一种新型环保填充材料,近年来在家居、工业及特殊应用领域展现出强劲的增长势头。根据中国纺织工业联合会最新发布的《2024-2025年中国纺织新材料…

25.11.22

QOJ8147 首先可以手算出这个序列满足:\(a_1=1\); \(a_{i+1}=-a_i\vee a_{i+1}=a_i+2\).但是还是不好数啊。接下来这一步纯数学,构造:\(b_i=\frac{|a_i|+1}{2}\),显然还是整数序列。 发现限制变成了:\(b_1=1\); \(…

CVE-2025-12870认证滥用漏洞分析:aEnrich eHRD系统高危安全风险

本文详细分析了CVE-2025-12870认证滥用漏洞,该漏洞影响aEnrich开发的a+HRD系统,允许未经认证的远程攻击者通过特制数据包获取管理员访问令牌,从而以提升的权限访问系统。概述 CVE-2025-12870是一个影响aEnrich开发的…

安装Docker(win11)

1 环境准备 1.1 开启Hyper-V Hyper-V功能仅在Windows专业版、企业版和教育版中可用,家庭版不支持Hyper-V功能。 为了解决这一问题,我们需要通过自行创建cmd指令执行。 操作步骤:创建cmd文件首先需要创建一个cmd文件…

2025年热门的不锈钢自攻螺钉厂家最新推荐权威榜

2025年热门的不锈钢自攻螺钉厂家最新推荐权威榜行业背景与市场趋势不锈钢自攻螺钉作为现代工业制造中不可或缺的基础紧固件,近年来随着全球制造业的复苏和建筑行业的持续发展,市场需求呈现稳定增长态势。根据中国五金…

2025年靠谱的盘头十字自攻螺钉厂家推荐及选择指南

2025年靠谱的盘头十字自攻螺钉厂家推荐及选择指南行业背景与市场趋势随着建筑装饰、工业制造和家电行业的持续发展,盘头十字自攻螺钉作为基础紧固件,市场需求呈现稳定增长态势。据中国五金制品协会2024年统计数据显示…

比 MySQL 轻,比 SQLite 强:终于有人把 AI 数据库做对了

前几天,我看到了一个来自 Turso 创始人 Pekka 的观点:SQLite 被认为是 AI agent 的理想数据库,因为它轻量级且适用于 AI agent 的各种场景,但仍然需要进化。评论区里也有意思,有人会和大家分享自己为了 SQLite 的…

PTA算法每日三题 - 详解

PTA算法每日三题 - 详解2025-11-25 08:16 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; fon…

如何解除 iPad 和 iPhone 文本消息的关联? - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年比较好的闸阀门厂家最新推荐权威榜

2025年比较好的闸阀门厂家最新推荐权威榜行业背景与市场趋势随着全球工业化进程的加速和基础设施建设的持续投入,阀门行业作为工业领域的关键配套产业,近年来保持了稳定增长态势。据《2024-2029年中国阀门行业市场调…

Visual Studio 2026 现已正式发布,更快、更智能!

前言 前不久 Visual Studio 官方博客宣布 Visual Studio 2026 正式发布!本次版本凝聚了广大开发者的宝贵反馈,博客中提及在此版本发布之前的一年里,Visual Studio 团队修复了 5000 多个用户报告的缺陷,并实现了 30…