微服务OAuth 2.1认证授权Demo方案(Spring Security 6)

文章目录

  • 一、介绍
  • 二、auth微服务代码
      • 1. SecurityConfig
      • 2. UserDetailsService
      • 3. 总结
  • 三、gateway微服务代码
      • 1. 统一处理CORS问题
  • 四、content微服务代码
      • 1. controller
      • 2. SecurityConfig
      • 3. 解析JWT Utils
      • 4. 总结
  • 五、一些坑

书接上文
微服务OAuth 2.1认证授权可行性方案(Spring Security 6)

一、介绍

三个微服务

  • auth微服务作为认证服务器,用于颁发JWT
  • gateway微服务作为网关,用于拦截过滤。
  • content微服务作为资源服务器,用于校验授权。

以下是授权相关数据库。

  • user表示用户表
  • role表示角色表
  • user_role关联了用户和角色,表示某个用户是是什么角色。一个用户可以有多个角色
  • menu表示资源权限表。@PreAuthorize("hasAuthority('xxx')")时用的就是这里的code
  • permission关联了角色和资源权限,表示某个角色用于哪些资源访问权限,一个角色有多个资源访问权限。
    在这里插入图片描述

当我们知道userId,我们就可以知道这个用户可以访问哪些资源,并把这些权限(也就是menu里的code字段)写成数组,写到JWT的负载部分的authorities字段中。当用户携带此JWT访问具有@PreAuthorize("hasAuthority('xxx')")修饰的资源时,我们解析出JWT中的authorities字段,判断是否包含hasAuthority指定的xxx权限,以此来完成所谓的的“授权”。

二、auth微服务代码

1. SecurityConfig

package com.xuecheng.auth.config;import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;/*** 身份验证服务器安全配置** @author mumu* @date 2024/02/13*/
//@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
@EnableWebSecurity
public class AuthServerSecurityConfig {private static KeyPair generateRsaKey() {KeyPair keyPair;try {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);keyPair = keyPairGenerator.generateKeyPair();} catch (Exception ex) {throw new IllegalStateException(ex);}return keyPair;}/*** 密码编码器* 用于加密认证服务器client密码和用户密码** @return {@link PasswordEncoder}*/@Beanpublic PasswordEncoder passwordEncoder() {// 密码为明文方式// return NoOpPasswordEncoder.getInstance();// 或使用 BCryptPasswordEncoderreturn new BCryptPasswordEncoder();}/*** 授权服务器安全筛选器链* <br/>* 来自Spring Authorization Server示例,用于暴露Oauth2.1端点,一般不影响常规的请求** @param http http* @return {@link SecurityFilterChain}* @throws Exception 例外*/@Bean@Order(1)public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)throws Exception {OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);http.getConfigurer(OAuth2AuthorizationServerConfigurer.class).oidc(Customizer.withDefaults());    // Enable OpenID Connect 1.0http// Redirect to the login page when not authenticated from the// authorization endpoint.exceptionHandling((exceptions) -> exceptions.defaultAuthenticationEntryPointFor(new LoginUrlAuthenticationEntryPoint("/login"),new MediaTypeRequestMatcher(MediaType.TEXT_HTML)))// Accept access tokens for User Info and/or Client Registration.oauth2ResourceServer((resourceServer) -> resourceServer.jwt(Customizer.withDefaults()));return http.build();}/*** 默认筛选器链* <br/>* 这个才是我们需要关心的过滤链,可以指定哪些请求被放行,哪些请求需要JWT验证** @param http http* @return {@link SecurityFilterChain}* @throws Exception 例外*/@Bean@Order(2)public SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers(new AntPathRequestMatcher("/actuator/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/login")).permitAll().requestMatchers(new AntPathRequestMatcher("/logout")).permitAll().requestMatchers(new AntPathRequestMatcher("/wxLogin")).permitAll().requestMatchers(new AntPathRequestMatcher("/register")).permitAll().requestMatchers(new AntPathRequestMatcher("/oauth2/**")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.html")).permitAll().requestMatchers(new AntPathRequestMatcher("/**/*.json")).permitAll().requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll().anyRequest().authenticated()).csrf(AbstractHttpConfigurer::disable)//指定logout端点,用于退出登陆,不然二次获取授权码时会自动登陆导致短时间内无法切换用户.logout(logout -> logout.logoutUrl("/logout").addLogoutHandler(new SecurityContextLogoutHandler()).logoutSuccessUrl("http://www.51xuecheng.cn")).formLogin(Customizer.withDefaults()).oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())
//                .jwt(jwt -> jwt
//                        .jwtAuthenticationConverter(jwtAuthenticationConverter())
//                ));return http.build();}private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();return jwtConverter;}/*** 客户端管理实例* <br/>* 来自Spring Authorization Server示例** @return {@link RegisteredClientRepository}*/@Beanpublic RegisteredClientRepository registeredClientRepository() {RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()).clientId("XcWebApp").clientSecret(passwordEncoder().encode("XcWebApp")).clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC).authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN).authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).redirectUri("http://www.51xuecheng.cn").redirectUri("http://localhost:63070/auth/wxLogin").redirectUri("http://www.51xuecheng.cn/sign.html")
//                .postLogoutRedirectUri("http://localhost:63070/login?logout").scope("all").scope(OidcScopes.OPENID).scope(OidcScopes.PROFILE).scope("message.read").scope("message.write").scope("read").scope("write").clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofHours(2))  // 设置访问令牌的有效期.refreshTokenTimeToLive(Duration.ofDays(3))  // 设置刷新令牌的有效期.reuseRefreshTokens(true)                   // 是否重用刷新令牌.build()).build();return new InMemoryRegisteredClientRepository(registeredClient);}/*** jwk源* <br/>* 对访问令牌进行签名的示例,里面包含公私钥信息。** @return {@link JWKSource}<{@link SecurityContext}>*/@Beanpublic JWKSource<SecurityContext> jwkSource() {KeyPair keyPair = generateRsaKey();RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();JWKSet jwkSet = new JWKSet(rsaKey);return new ImmutableJWKSet<>(jwkSet);}/*** jwt解码器* <br/>* JWT解码器,主要就是基于公钥信息来解码** @param jwkSource jwk源* @return {@link JwtDecoder}*/@Beanpublic JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);}@Beanpublic AuthorizationServerSettings authorizationServerSettings() {return AuthorizationServerSettings.builder().build();}/*** JWT定制器* <BR/>* 可以往JWT从加入额外信息,这里是加入authorities字段,是一个权限数组。** @return {@link OAuth2TokenCustomizer}<{@link JwtEncodingContext}>*/@Beanpublic OAuth2TokenCustomizer<JwtEncodingContext> jwtTokenCustomizer() {return context -> {Authentication authentication = context.getPrincipal();if (authentication.getPrincipal() instanceof UserDetails userDetails) {List<String> authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());context.getClaims().claim("authorities", authorities);}};}
}

这里需要注意几点

  • 使用BCryptPasswordEncoder密码加密,在设置clientSecret时需要手动使用密码编码器。
  • jwtTokenCustomizer解析UserDetails然后往JWT中添加authorities字段,为了后面的授权。

2. UserDetailsService

package com.xuecheng.ucenter.service.impl;import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xuecheng.ucenter.mapper.XcMenuMapper;
import com.xuecheng.ucenter.mapper.XcUserMapper;
import com.xuecheng.ucenter.model.dto.AuthParamsDto;
import com.xuecheng.ucenter.model.dto.XcUserExt;
import com.xuecheng.ucenter.model.po.XcMenu;
import com.xuecheng.ucenter.model.po.XcUser;
import com.xuecheng.ucenter.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Component
@Slf4j
public class UserServiceImpl implements UserDetailsService {@Autowiredprivate MyAuthService myAuthService;@AutowiredXcMenuMapper xcMenuMapper;/*** 用户统一认证** @param s 用户信息Json字符串* @return {@link UserDetails}* @throws UsernameNotFoundException 找不到用户名异常*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {XcUserExt xcUserExt = myAuthService.execute(username);return getUserPrincipal(xcUserExt);}public UserDetails getUserPrincipal(XcUserExt user){//用户权限,如果不加报Cannot pass a null GrantedAuthority collectionList<XcMenu> xcMenus = xcMenuMapper.selectPermissionByUserId(user.getId());String[] permissions = {"read"};if (ObjectUtils.isNotEmpty(xcMenus)){permissions = xcMenus.stream().map(XcMenu::getCode).toList().toArray(String[]::new);log.info("权限如下:{}", Arrays.toString(permissions));}//为了安全在令牌中不放密码String password = user.getPassword();user.setPassword(null);//将user对象转jsonString userString = JSON.toJSONString(user);//创建UserDetails对象return User.withUsername(userString).password(password).authorities(permissions).build();}
}

这里需要注意几点

  • username就是前端/auth/login的时候输入的账户名。
  • myAuthService.execute(username)不抛异常,就默认表示账户存在,此时将password加入UserDetails 并返回,Spring Authorization Server对比校验两个密码。
  • myAuthService.execute(username)根据username获取用户信息返回,将用户信息存入withUsername中,Spring Authorization Server默认会将其加入到JWT中。
  • 现在Spring Authorization Server默认不会把authorities(permissions)写入JWT,需要配合OAuth2TokenCustomizer手动写入。

3. 总结

这样,auth微服务颁发的JWT,现在就会包含authorities字段。示例如下

{"active": true,"sub": "{\"cellphone\":\"17266666637\",\"createTime\":\"2024-02-13 10:33:13\",\"email\":\"1138882663@qq.com\",\"id\":\"012f3a90-2bc9-4a2c-82a3-f9777c9ac10a\",\"name\":\"xiamu\",\"nickname\":\"xiamu\",\"permissions\":[],\"status\":\"1\",\"updateTime\":\"2024-02-13 10:33:13\",\"username\":\"xiamu\",\"utype\":\"101001\",\"wxUnionid\":\"test\"}","aud": ["XcWebApp"],"nbf": 1707830437,"scope": "all","iss": "http://localhost:63070/auth","exp": 1707837637,"iat": 1707830437,"jti": "8a657c60-968f-4d98-8a4c-22a7b4ecd333","authorities": ["xc_sysmanager","xc_sysmanager_company","xc_sysmanager_doc","xc_sysmanager_log","xc_teachmanager_course_list"],"client_id": "XcWebApp","token_type": "Bearer"
}

三、gateway微服务代码

1. 统一处理CORS问题

@EnableWebFluxSecurity
@Configuration
public class SecurityConfig {//安全拦截配置@Beanpublic SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {return http.cors(cors -> cors.configurationSource(corsConfigurationSource())).authorizeExchange(exchanges ->exchanges.pathMatchers("/**").permitAll().anyExchange().authenticated()).oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())).csrf(ServerHttpSecurity.CsrfSpec::disable).build();}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration corsConfig = new CorsConfiguration();corsConfig.addAllowedOriginPattern("*"); // 允许任何源corsConfig.addAllowedMethod("*"); // 允许任何HTTP方法corsConfig.addAllowedHeader("*"); // 允许任何HTTP头corsConfig.setAllowCredentials(true); // 允许证书(cookies)corsConfig.setMaxAge(3600L); // 预检请求的缓存时间(秒)UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfig); // 对所有路径应用这个配置return source;}
}

这里需要注意几点

  • 书接上文,这里虽然用了oauth2.jwt(Customizer.withDefaults()),但实际上基于远程auth微服务开放的jwkSetEndpoint配置的JwtDecoder
  • .cors(cors -> cors.configurationSource(corsConfigurationSource()))一次性处理CORS问题。

四、content微服务代码

1. controller

	@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")@ApiResponse(responseCode = "200", description = "Successfully retrieved user")@Operation(summary = "查询课程信息列表")@PostMapping("/course/list")public PageResult<CourseBase> list(PageParams pageParams,@Parameter(description = "请求具体内容") @RequestBody(required = false) QueryCourseParamsDto dto){SecurityUtil.XcUser xcUser = SecurityUtil.getUser();if (xcUser != null){System.out.println(xcUser.getUsername());System.out.println(xcUser.toString());}return courseBaseInfoService.queryCourseBaseList(pageParams, dto);}

使用了@PreAuthorize("hasAuthority('xc_teachmanager_course_list')")修饰的controller资源。

2. SecurityConfig

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {//安全拦截配置@Beanpublic SecurityFilterChain defaultFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests((authorize) ->authorize.requestMatchers("/**").permitAll().anyRequest().authenticated()).csrf(AbstractHttpConfigurer::disable).oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));return http.build();}private JwtAuthenticationConverter jwtAuthenticationConverter() {JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter();jwtConverter.setJwtGrantedAuthoritiesConverter(jwt -> {// 从JWT的claims中提取权限信息List<String> authorities = jwt.getClaimAsStringList("authorities");if (authorities == null) {return Collections.emptyList();}return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());});return jwtConverter;}
}

需要注意几点

  • 使用@EnableMethodSecurity@PreAuthorize生效
  • gateway一样,需要基于远程auth微服务开放的jwkSetEndpoint配置JwtDecoder
  • 指定JwtAuthenticationConverter ,让anyRequest().authenticated()需要验证的请求,除了完成默认的JWT验证外,还需要完成JwtAuthenticationConverter 指定逻辑。
  • JwtAuthenticationConverter 中将JWTauthorities部分形成数组后写入GrantedAuthorities,这正是spring security6用于校验@PreAuthorize的字段。

3. 解析JWT Utils

@Slf4j
public class SecurityUtil {public static XcUser getUser(){Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication == null){return null;}if (authentication instanceof JwtAuthenticationToken) {JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) authentication;System.out.println(jwtAuth);Map<String, Object> tokenAttributes = jwtAuth.getTokenAttributes();System.out.println(tokenAttributes);Object sub = tokenAttributes.get("sub");return JSON.parseObject(sub.toString(), XcUser.class);}return null;}@Datapublic static class XcUser implements Serializable {private static final long serialVersionUID = 1L;private String id;private String username;private String password;private String salt;private String name;private String nickname;private String wxUnionid;private String companyId;/*** 头像*/private String userpic;private String utype;private LocalDateTime birthday;private String sex;private String email;private String cellphone;private String qq;/*** 用户状态*/private String status;private LocalDateTime createTime;private LocalDateTime updateTime;}
}

JWT的信息解析回XcUser ,相当于用户携带JWT访问后端,后端可以根据JWT获取此用户的信息。当然,你可以尽情的自定义,扩展。

4. 总结

当用户携带JWT访问需要权限的资源时,现在可以正常的校验权限了。

五、一些坑

  1. RegisteredClient时注册那么多redirectUri是因为debug了很久,才发现获取授权码和获取JWT时,redirect_uri参数需要一致。
  2. cors问题,spring secuity6似乎会一开始直接默认拒绝cors,导致跨域请求刚到gateway就寄了,到不了content微服务,即使content微服务配置了CORS的处理方案,也无济于事。

在这里插入图片描述

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

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

相关文章

Github 2024-02-14 开源项目日报 Top9

根据Github Trendings的统计&#xff0c;今日(2024-02-14统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Rust项目4TypeScript项目1PowerShell项目1Java项目1JavaScript项目1Jupyter Notebook项目1非开发语言项目1Pyth…

【制作100个unity游戏之25】3D背包、库存、制作、快捷栏、存储系统、砍伐树木获取资源、随机战利品宝箱4(附带项目源码)

效果演示 文章目录 效果演示系列目录前言快捷栏操作&#xff0c;并可切换手臂绘制快捷栏UI代码控制快捷栏切换 快捷栏显示选中效果绘制选中效果UI图代码重新定位选中效果图 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如…

YOLOv8改进 | Conv篇 | 利用FasterBlock二次创新C2f提出一种全新的结构(全网独家首发,参数量下降70W)

一、本文介绍 本文给大家带来的改进机制是利用FasterNet的FasterBlock改进特征提取网络,将其用来改进ResNet网络,其旨在提高计算速度而不牺牲准确性,特别是在视觉任务中。它通过一种称为部分卷积(PConv)的新技术来减少冗余计算和内存访问。这种方法使得FasterNet在多种设…

数据库基本操作2

一.DML&#xff08;Data Manipulation Language&#xff09; 用来对数据库中表的数据记录进行更新 关键字&#xff1a;增删改 插入insert 删除delete 更新update 1.数据插入 insert into 表&#xff08;列名1&#xff0c;列名2&#xff0c;列名3……&#xff09;values&a…

例39:使用List控件

建立一个EXE工程&#xff0c;在窗体上放一个文本框&#xff0c;一个列表框和三个按钮输入如下的代码&#xff1a; Sub Form1_Command1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)List1.AddItem(Text1.Text)End SubSub Form1_Command2_BN_Clicked(hWndForm As hWnd, h…

vue前端系统启动报错Module not found: Error: Can‘t resolve ‘sass-loader‘

1、确认项目中是否已安装 node-sass 包。sass-loader 是依赖于 node-sass 包的&#xff0c;如果没有安装 node-sass 包&#xff0c;也会导致无法找到 sass-loader 包。 npm ls node-sass安装 node-sass 包&#xff1a; npm install --save-dev node-sass2、确认项目中是否已安…

C#查找字符串中的所有数字: 面向对象的自定义方法 vs 使用char.IsDigit()方法

目录 一、涉及到的方法 1.面向对象的自定义方法 2.面向对象编程技术定义 3.使用char.IsDigit()方法 二、实例 1.源码 2.生成效果 一、涉及到的方法 1.面向对象的自定义方法 查找字符串中的所有数字时&#xff0c;首先将所有数字存储到一个字符串数组中&#xff0c;然后…

LeetCode、1268. 搜索推荐系统【中等,前缀树+优先队列、排序+前缀匹配】

文章目录 前言LeetCode、1268. 搜索推荐系统【中等&#xff0c;前缀树优先队列、排序前缀匹配】题目类型及分类思路API调用&#xff08;排序前缀匹配&#xff09;前缀树优先队列 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创…

最新wordpress外贸主题

日用百货wordpress外贸主题 蓝色大气的wordpress外贸主题&#xff0c;适合做日用百货的外贸公司搭建跨境电商网站使用。 https://www.jianzhanpress.com/?p5248 添加剂wordpress外贸建站主题 橙色wordpress外贸建站主题&#xff0c;适合做食品添加剂或化工添加剂的外贸公司…

Prompt Tuning:深度解读一种新的微调范式

阅读该博客&#xff0c;您将系统地掌握如下知识点&#xff1a; 什么是预训练语言模型&#xff1f; 什么是prompt&#xff1f;为什么要引入prompt&#xff1f;相比传统fine-tuning有什么优势&#xff1f; 自20年底开始&#xff0c;prompt的发展历程&#xff0c;哪些经典的代表…

js中事件循环的详解

文章目录 一、是什么二、宏任务与微任务微任务宏任务 三、async与awaitasyncawait 四、流程分析 一、是什么 首先&#xff0c;JavaScript是一门单线程的语言&#xff0c;意味着同一时间内只能做一件事&#xff0c;但是这并不意味着单线程就是阻塞&#xff0c;而实现单线程非阻…

【Ubuntu】在.bashrc文件中误设置环境变量补救方法

这里是vim也不在PATH中了&#xff0c;因为 解决方法就是在输入vim之后提示的vim路径下用vim打开该文件&#xff0c;然后改回来