当前位置: 首页 > news >正文

Spring Security+JWT+Redis实现项目级前后端分离认证授权

1. 整体概述

        权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制到资源,用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

1.1 认证概述

 
认证是确认用户身份的过程,确保用户是谁。

1.1.1 认证组件

Spring Security的认证过程涉及以下组件

AuthenticationManager:作为认证的核心,AuthenticationManager负责处理用户的认证请求。它会委托不同的认证提供者(例如DaoAuthenticationProvider)来验证用户身份。AuthenticationManager接收一个包含用户凭证(如用户名、密码)的Authentication对象,然后通过认证提供者验证这些凭证的正确性。如果验证通过,AuthenticationManager会返回一个包含认证信息的Authentication对象。

Authentication:Authentication表示用户的认证信息,通常包括用户名、密码以及认证后得到的角色和权限。它是Spring Security中用户身份验证的核心数据结构。Authentication接口通常由UsernamePasswordAuthenticationToken等实现类来表示。认证完成后,Authentication对象将包含用户的认证状态和权限信息。

SecurityContextHolder:SecurityContextHolder是Spring Security中的核心类之一,它用来存储当前用户的认证信息。认证成功后,Authentication对象会被保存在SecurityContext中,SecurityContext会由SecurityContextHolder管理。在每个请求周期内,SecurityContextHolder提供对当前用户认证信息的访问,使得后续的请求可以通过SecurityContextHolder.getContext().getAuthentication()来获取当前用户的身份信息。

 1.1.2 认证过滤器链

1.1.3 认证流程步骤

1.2 授权概述


授权是根据用户身份信息判断用户是否有权限访问某些资源的过程。Spring Security的授权过程涉及以下组件:

AccessDecisionManager:AccessDecisionManager负责根据用户的认证信息和请求的资源,做出是否允许访问的决策。它会根据配置的权限要求以及用户的角色信息,决定用户是否能够访问特定的资源。AccessDecisionManager会使用多个AccessDecisionVoter来进行投票,综合多个投票结果后,做出最终的访问决策。

AccessDecisionVoter:AccessDecisionVoter是负责对用户访问权限进行投票的组件。它根据用户的Authentication对象和访问资源的ConfigAttribute(例如角色或权限要求)进行匹配。每个AccessDecisionVoter会判断用户是否符合特定的访问要求,并给出投票结果。所有投票的结果会由AccessDecisionManager汇总,最终决定是否允许访问。

ConfigAttribute:ConfigAttribute用于描述受保护资源的权限要求。它定义了访问某个资源所需的权限(如角色或操作权限)。ConfigAttribute通常与AccessDecisionManager结合使用,它告知AccessDecisionVoter该资源需要哪些权限,AccessDecisionVoter则根据这些要求判断用户是否具备访问该资源的权限。

1.3. 引入依赖

        一旦引入Spring security依赖后,系统中所有的资源都受保护起来,必须进行认证之后才能够访问,没有认证直接访问资源时,会跳转到Spring security默认的登录页面:http://localhost:8080/login

<!--Spring security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

        Spring security会默认提供一个用户和密码,用户是user,密码是启动时的一个字符串:

 如果觉得启动时生成的默认密码比较长不方便登录,也可以使用配置文件配置固定的用户名和密码,密码前缀{noop}表示密码是明文。

2. 实现思路

        整个基于security框架实现认证授权思路如上所示,绿色框是框架已经实现的内容,白色框需要用户改写和实现的内容。 

2.1 认证登陆

        完成上图中白色框中的内容,具体步骤如下所示。

2.1.1 查询数据库用户

步骤1: 通过登录用户名查数据库中用户信息

通过自定义UserDetailsService,改写里面的loadUserByUsername方法,利用mybatis或其他框架从数据库中查询出用户信息。

除此之外,改写UserDetails接口中的方法,把数据库中查出的用户信息封装成UserDetails进行返回。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserAccount,username);User user = userMapper.selectOne(queryWrapper);if(user==null){throw  new RuntimeException("此用户"+username+" 不存在");}//TODO 查询对应的权限信息// 把数据封装成UserDetailsreturn new LoginUser(user);}
}
@Data // get set 方法
@NoArgsConstructor // 空参构造器
@AllArgsConstructor // 全参构造器
public class LoginUser implements UserDetails {private User user;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return user.getPassWord();}@Overridepublic String getUsername() {return user.getUserAccount();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

 完成以上代码后,在数据库中存入用户,打开security默认登录页面测试是否能够使用数据库中存入的用户进行页面登录。

注意:因为目前还未做用户密码加密,密码是按明文存储的,所以在密码前需要加上{noop}标识

弹出以上界面说明登录成功,第一步操作完成。 

2.1.2 密码加密功能

步骤2: 用户密码加密功能

增加security配置,指定加密方式,从而自动实现用户密码按加密方式匹配,并要求用户新增接口和修改接口中,密码字段要调用配置中的加密方式进行明文加密,然后再存储数据库中。

增加security配置,定义密码加密方式。

@Configuration
@EnableWebSecurity
public class SecurityConfig  {// 配置密码加密方式,全局自动按这个方式加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}
}

增加此配置后,所有密码均采用此方式进行加密和匹配,当用户输入明文密码登录时,security框架会自动进行此方法进行加密后和数据库密码进行匹配。

注意:此时数据库存储的密码不应该是明文了,在用户注册或修改密码时,也应该调用此加密方法对明文进行加密并存储到数据库中。

此接口提供了两个方法,一个是明文加密,一个是密文匹配,测试方法如下:

 2.1.3 登录接口编写

步骤3: 登录接口实现

        首先在security配置类中配置认证管理器AuthenticationManager,此组件的作用是通过传入前端输入的用户名和密码,调用UserDetailsServicel去后台数据库比对用户信息,如果认证成功返回数据库用户的详情信息,失败返回null。

        传入用户名和密码前需要封装成authenticationToken对象,根据认证结果编写代码处理逻辑。如果认证失败,抛出异常报错给前端,如果认证成功,返回UserDetailsServicel的LoginUser用户,通过LoginUser获取userID,生成jwt返回给前端 

// 配置认证管理器@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}

编写登录接口controller以及service实现类

@RestController
@CrossOrigin
@RequestMapping("api/v1")
@Tag(name= "用户登录接口文档") // 描述controller类的作用
@Slf4j
public class LoginController {@AutowiredLoginService loginService;@PostMapping("/authentication/login")@Operation(summary = "用户登陆接口")public R login(@RequestBody UserLoginVo user){try {Map<String, String> userLogin = loginService.login(user);return R.ok("登录成功",userLogin);}catch (Exception e){log.error(e.getMessage());return R.error(500,e.getMessage());}}
}
@Override
public Map<String, String> login(UserLoginVo user) {// AuthenticationManger authenticate 进行用户认证UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(user.getUserAccount(),user.getPassWord()); // 用户名和密码封装成 authenticationToken对象Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 通过authenticationManager进行验证// 如果认证没通过,给出对应提示if(Objects.isNull(authenticate)){throw new RuntimeException("登录失败");}// 如果认证通过了,使用userid生成一个jwt, jwt存入ResponseResult返回LoginUser loginUser = (LoginUser) authenticate.getPrincipal();String userId = loginUser.getUser().getUserId().toString();String jwt = JwtUtil.createJWT(userId);// 把用户信息存入redis
//        redisService.setValue("login:" + userId, JSON.toJSONString(loginUser));redisService.setObject("login:" + userId,loginUser);// 把jwt封装成map返回前端HashMap<String, String> map = new HashMap<>();map.put("token",jwt);return map;
}

值得注意的是,由于登录接口是匿名登录的,需要进行放行,否则接口需要认证,无法访问。具体配置如下:

在配置中建议登录接口以及注册接口使用.anonymous(),仅对未认证(匿名)用户开放,已认证用户不可访问此资源。

在一些公开资源、静态资源、开放接口等建议使用.permitAll(),不需要任何身份验证,即允许已认证和未认证的用户。

// 3.设置路径权限
http.authorizeHttpRequests().requestMatchers("/api/v1/authentication/login").anonymous()  //对于登录接口,允许匿名访问.requestMatchers("/doc.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() // 允许匿名访问这些路径.anyRequest().authenticated(); // 除上面外的所有请求全部需要鉴权认证

 通过如上步骤,最终接口登录成功后会返回token

2.2 校验

2.2.1 定义jwt认证过滤器

        步骤1:获取token, 解析token获取其中的userid

        步骤2:通过userid 从 redis中获取用户信息

        步骤3: 将用户信息存入securityContextHolder

        如果在登录时存入 SecurityContextHolder,但应用是无状态的,每次请求时SecurityContextHolder 其实都是空的,无法保持状态。因为 Spring Security 的 SecurityContextHolder 只是一个线程级变量(ThreadLocal),它的生命周期仅限于当前请求的处理过程中。当一次 HTTP 请求到达服务器时:服务器分配一个线程处理请求。SecurityContextHolder 仅在该线程内存储 SecurityContext(认证信息)。当请求处理结束后,线程被回收,SecurityContextHolder 也被清空。
        正确的做法是在用户每次请求时,解析 Token,并动态地将用户信息存入 SecurityContextHolder。

@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Autowiredprivate RedisService redisService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {// 没有token就放行,让后面过滤器处理filterChain.doFilter(request, response);return;}// 解析tokenString userID;try {Claims claims = JwtUtil.parseJWT(token);userID = claims.getSubject();} catch (Exception e) {throw new RuntimeException("token非法:" + e.getMessage());}// 获取用户信息
//        Object userObj  = redisService.getValue("login:" + userID);
//        LoginUser user = JSON.parseObject((String) userObj, LoginUser.class);LoginUser user = redisService.getObject(("login:" + userID), LoginUser.class);if(user==null){throw new RuntimeException("用户未登录");}// 存入SecurityContextHolder//TODO 获取权限信息封装到Authentication中UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =new UsernamePasswordAuthenticationToken(user,null,null);  // 用户、密码、权限集合SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);// 放行filterChain.doFilter(request,response);}
}

2.2.2 添加到过滤器链中

先注入过滤器实例,然后在security配置中添加倒数第二行代码

// 4. 添加认证过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

2.2.3 登出接口编写

        因为认证过滤器对用户进行校验时,会根据token获取userid,并读取登录时存入redis中的用户信息,如果redis的用户信息读不到,就会被认证过滤器拦截,根据这一特性,可以设计登出接口,即删除用户在redis中的用户信息。

        用户访问登出接口后,在SecurityContextHolder中获取userid,并删除redis中的信息

@Override
public void logout() {// 获取SecurityContextHolder中的用户IDUsernamePasswordAuthenticationToken authentication =(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();LoginUser loginUser = (LoginUser) authentication.getPrincipal();Integer userId = loginUser.getUser().getUserId();// 删除redis中的值redisService.deleteValue("login:"+userId);
}

2.3 授权

2.3.1 设置资源权限

        通过注解的方式进行资源权限的标识,决定各个接口需要什么样的权限才能访问。

        步骤1:开启注解,在security配置类添加注解 @EnableMethodSecurity  // 开启权限注解

@EnableMethodSecurity  // 开启权限注解
public class SecurityConfig  {...
}

        步骤2: 在接口上添加注解配置权限 @PreAuthorize("hasAuthority('test')")

@PreAuthorize("hasAuthority('sys:device:getList')")
@GetMapping("/device/getList")
public R getDevice(@PathVariable("id") Integer id){... 
}

步骤2中除了可以在接口上添加注解的方式设置权限,还可以基于配置进行权限设置,在securityConfig配置类中添加如下代码等同于注解:

http
.authorizeHttpRequests()
.requestMatchers("/api/v1/device/getList")
.hasAuthority("sys:device:getList");

2.3.2 封装权限信息

        将用户的权限信息封装成security需要的对象,以便在登录和校验时,能够将权限信息输入到对应的结构中。

在LoginUser类中构造有参构造器,并重写getAuthorities方法;

public class LoginUser implements UserDetails {private User user;private List<String> permissions;/*** 有参构造器* */public LoginUser(User user,List<String> permissions){this.user = user;this.permissions = permissions;}/*** 获取权限对象* */@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {log.debug("permissions:"+permissions);ArrayList<GrantedAuthority> authorities = new ArrayList<>();// 把permissions中string类型的权限信息封装成simpleGrantedAuthority对象for (String permission : permissions) {SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);authorities.add(authority);log.debug("authorities:"+authorities);}return authorities;}
}

        在UserDetailsService中调用LoginUser的有参构造器,将用户信息和权限信息注入到LoginUser实例中,以供登录时AuthenticationManager认证管理器调用返回用户权限信息。登录接口获取到认证管理器的用户权限信息后会存入redis中,在下一步中,认证过滤器会取出相关权限信息存入到SecurityContextHolder中以供后续过滤器校验。

@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//查询用户信息LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getUserAccount,username);User user = userMapper.selectOne(queryWrapper);if(user==null){throw  new RuntimeException("此用户"+username+" 不存在");}//查询对应的权限信息List<PermissionSelectVo> permissions = userService.getUserByAccount(username).getPermissions();ArrayList<String> permissionList = new ArrayList<>();for (PermissionSelectVo permission:permissions){permissionList.add(permission.getPermissionCode());}// 把数据封装成UserDetailsreturn new LoginUser(user,permissionList);}

2.3.3 注入权限信息

        在用户登录时,需要把权限信息注入到LoginUser中并存入redis,因为在上一步中已经将权限信息放入LoginUser中,所以在登录后,直接将LoginUser存入redis中就自然存入了权限信息。

        在校验用户时,过滤器中从redis读出权限信息,传入UsernamePasswordAuthenticationToken中,生成带权限的用户信息存入SecurityContextHolder让其后续的过滤器进行权限校验。

// 存入SecurityContextHolder// 获取权限信息封装到Authentication中
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());  // 用户、密码、权限集合    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

3 其他功能

3.1  异常捕获

        如果在认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法进行一场处理。

        如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。所以如果我们需要自定义异常处理,给前端返回异常信息,只需要自定义AuthenticationEntryPoin 和 AccessDeniedHandler然后配置给Spring Security 即可。

3.1. 1 认证异常

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {// 处理异常response.setStatus(HttpStatus.UNAUTHORIZED.value()); // 设置状态码response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().write(JSON.toJSONString(R.error(401,"尚未认证,请进行认证操作!")));}
}

3.1.2 权限异常 

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setStatus(HttpStatus.FORBIDDEN.value()); // 设置状态码response.setContentType("application/json");response.setCharacterEncoding("utf-8");response.getWriter().write(JSON.toJSONString(R.error(403,"无权访问!")));}
}

3.2 跨域

        在Spring Security框架中,如果已经配置了Spring的跨域处理,通常还需要针对Spring Security进行跨域配置,两者都需要配置才能确保跨域请求能够顺利通过Spring Security的安全检查。一般情况下,为了保证配置的统一性,会把配置集中使用其中一个配置,另一个配置直接放行

3.2.1 Spring跨域配置

@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**")		//设置允许跨域的路径.allowedOrigins()		//设置允许跨域请求的域名,例如:allowedOrigins("http://localhost:3000", "http://example.com"),如果为空则是允许所有.allowCredentials(true) 	//是否允许发送凭证token.allowedMethods("GET","POST","PUT","DELETE")  //指定允许的 HTTP 方法.maxAge(3600 * 24);		//预检请求有效期}
}

3.2.2 Security跨域配置

// 跨域配置
http.cors().configurationSource(corsConfigurationSource()) //跨域解决方案
@Bean
CorsConfigurationSource corsConfigurationSource(){CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowedHeaders(Arrays.asList("*"));corsConfiguration.setAllowedMethods(Arrays.asList("*"));corsConfiguration.setAllowedOrigins(Arrays.asList("*"));corsConfiguration.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**",corsConfiguration);return source;}

3.3 csrf攻击

        前后端分离架构本身就是无状态token校验的,所以天然防范csrf攻击,无需进行设置,默认打开的,所以在前后端架构下需要设置为关闭。

http.csrf().disable(); // 关闭csrf

4 总结

本文通过引入security框架,在登录接口中通过传入页面用户登录信息(用户名、密码)给UsernamePasswordAuthenticationToken来验证用户身份,通过后返回token给前端并存入redis用户信息。其中验证的用户身份是通过实现接口UserDetailsService从数据库中获取用户信息并封装到LoginUser对象中。校验时,通过添加认证过滤器完成从token获取用户信息,并调用UsernamePasswordAuthenticationToken验证用户权限。

security的完整配置文件如下所示:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity  // 开启权限注解
public class SecurityConfig  {@Autowiredprivate JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;// 注入token校验过滤器@Autowiredprivate AuthenticationEntryPoint authenticationEntryPoint;  //注入授权异常处理器@Autowiredprivate AccessDeniedHandler accessDeniedHandler; //注入认证异常处理器// 配置密码加密方式,全局自动按这个方式加密@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 配置认证管理器@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}// security 配置@Beanpublic SecurityFilterChain httpSecurity(HttpSecurity http) throws Exception {// 1.前后端分离架构本身就是无状态token校验的,所以天然防范csrf攻击,可以关闭http.csrf().disable(); // 关闭csrf
//                .csrfTokenRepository(CookieCsrfTokenRepository. withHttpOnlyFalse());  // 将令牌保存到cookie中允许cookie前端获取// 2.前后端分离架构不通过Session获取SecurityContexthttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 3.设置路径权限http.authorizeHttpRequests().requestMatchers("/api/v1/authentication/login").anonymous()  //对于登录接口,允许匿名访问.requestMatchers("/doc.html", "/swagger-ui/**", "/v3/api-docs/**").permitAll() // 允许匿名访问这些路径.anyRequest().authenticated(); // 除上面外的所有请求全部需要鉴权认证// 4. 添加认证过滤器http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 5. 配置异常处理器http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)  // 配置认证失败处理器.accessDeniedHandler(accessDeniedHandler);  // 配置授权失败处理器// 6.跨域配置http.cors(); // 允许跨域return http.build();}
}

相关文章:

Spring Security+JWT+Redis实现项目级前后端分离认证授权

1. 整体概述 权限管理包括用户身份认证和授权两部分&#xff0c;简称认证授权。对于需要访问控制到资源&#xff0c;用户首先经过身份认证&#xff0c;认证通过后用户具有该资源的访问权限方可访问。 1.1 认证概述 认证是确认用户身份的过程&#xff0c;确保用户是谁。 1.1.1 …...

马斯克宣布Grok语音模式正式上线:早期测试版本 可能有一些问题

快科技2月23日消息&#xff0c;据报道&#xff0c;马斯克旗下xAI团队近期动作频频&#xff0c;继2月18日直播发布Grok最新版本Grok3后&#xff0c;马斯克又在社交平台X上宣布&#xff0c;Grok语音模式早期测试版现已在Grok应用程序上线&#xff0c;并对其表现给予了高度评价。 …...

P9631 [ICPC 2020 Nanjing R] Just Another Game of Stones Solution

Description 给定序列 a ( a 1 , a 2 , ⋯ , a n ) a(a_1,a_2,\cdots,a_n) a(a1​,a2​,⋯,an​)&#xff0c;有 m m m 个操作分两种&#xff1a; chmax ⁡ ( l , r , k ) \operatorname{chmax}(l,r,k) chmax(l,r,k)&#xff1a;对每个 i ∈ [ l , r ] i \in [l,r] i∈[l,…...

请求go构建缓存,go clean -cache

go clean -cache go 构建时会产生很多缓存&#xff0c; 一般是目录&#xff1a;/Users/xxx/Library/Caches/go-build 此目录README&#xff1a; This directory holds cached build artifacts from the Go build system. Run "go clean -cache" if the directory …...

安全面试4

文章目录 给的源码是ThinkPHP框架的话&#xff0c;审计起来和没有使用框架的有什么不同&#xff0c;从流程上或者从关注的点上有什么不同框架代码审计的流程无框架代码审计的流程 反序列的时候&#xff0c;unserialize()反序列一个字符串的时候&#xff0c;对象会有一些魔术方法…...

HTML之JavaScript DOM操作元素(1)

HTML之JavaScript DOM操作元素&#xff08;1&#xff09; 3.对元素进行操作1.操作元素的属性 元素名.属性名 ""2.操作元素的样式 元素.style.样式名 "" 样式名 "-" 要进行驼峰转换3.操作元素的文本 元素名.innerText 只识别文本元素名…...

SpringBoot+Vue+微信小程序的猫咖小程序平台(程序+论文+讲解+安装+调试+售后)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在当下这个高速发展的时代&#xff0c;网络科技正以令人惊叹的速度不断迭代更新。从 5G …...

【十一】Golang 指针

&#x1f4a2;欢迎来到张胤尘的开源技术站 &#x1f4a5;开源如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 指针指针定义指针初始化& 操作符new 函数初始…...

“conda”不是内部或外部命令,也不是可运行的程序或批处理文件

有的时候&#xff0c;我们发现在cmd黑框中输入conda时&#xff0c;cmd会显示“conda”不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件&#xff0c;那这时候该怎么解决呢&#xff1f; Step01&#xff1a;我们找到Anconda的安装目录。然后找到里面的bin文件夹&am…...

通过LM Studio本地私有化部署DeepSeek-R1模型,无网络也能用

打开LM Studio官网https://lmstudio.ai/ 选择适合自己的操作系统&#xff0c;下载LM Studio安装包 本地电脑安装成功后运行LM Studio&#xff0c;顶部文本框输入deepseek&#xff0c;点击搜索模型 在搜索结果中选择7B参数模型&#xff0c; 如上图右侧提示“No result found”说…...

GPU和FPGA的区别

GPU&#xff08;Graphics Processing Unit&#xff0c;图形处理器&#xff09;和 FPGA&#xff08;Field-Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;不是同一种硬件。 我的理解是&#xff0c;虽然都可以用于并行计算&#xff0c;但是GPU是纯计算的硬件…...

CMake管理依赖实战:多仓库的无缝集成

随着软件复杂度的增加&#xff0c;单个项目可能需要依赖多个外部库或模块。这些依赖项可能是来自不同的代码仓库&#xff0c;如ATest和BTest。为了实现高效的依赖管理&#xff0c;CMake提供了多种方式来处理这种多仓库的情况。下面我们将详细介绍几种常见的方法&#xff0c;并通…...

Windows系统第一次运行C语言程序,环境配置,软件安装等遇到的坑及解决方法

明确需要编辑器和编译器&#xff0c;并选择自己要用什么&#xff08;我选的编辑器是VSCode&#xff1a;Visual Studio Code&#xff1b;编译器是gcc&#xff09;下载VSCode并配置环境变量&#xff08;这里没啥问题&#xff09;&#xff0c;安装C/C的拓展安装Cygwin&#xff0c;…...

2025最新版!Fiddler抓包实战:深度解析短视频评论采集技术

2025最新版&#xff01;Fiddler抓包实战&#xff1a;深度解析短视频评论采集技术&#xff08;脱敏&#xff09; 声明&#xff1a; 本文仅供学习交流使用&#xff0c;请勿用于非法用途。 导语&#xff1a; 短视频数据采集又有新突破&#xff01;你是否好奇如何安全、高效地获…...

Linux信号

目录 1. 信号的概念搞定&#xff08;输出结论&#xff0c;支撑我们的理解&#xff09; 补充知识 2.信号的产生 补充知识 3.信号的保存 4.阻塞信号 1. 信号其他相关常见概念 2. 在内核中的表示 3. sigset_t 4. 信号集操作函数 sigprocmask sigpending 5. 信号的…...

git,bash - 从一个远端git库只下载一个文件的方法

文章目录 git,bash - 从一个远端git库只下载一个文件的方法概述笔记写一个bash脚本来自动下载get_github_raw_file_from_url.shreanme_file.shfind_key_value.sh执行命令 END git,bash - 从一个远端git库只下载一个文件的方法 概述 github上有很多大佬上传了电子书库&#xf…...

深度学习(5)-卷积神经网络

我们将深入理解卷积神经网络的原理&#xff0c;以及它为什么在计算机视觉任务上如此成功。我们先来看一个简单的卷积神经网络示例&#xff0c;它用干对 MNIST数字进行分类。这个任务在第2章用密集连接网络做过&#xff0c;当时的测试精度约为 97.8%。虽然这个卷积神经网络很简单…...

flex布局自定义一行几栏,靠左对齐===grid布局

模板 <div class"content"><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"…...

(五)趣学设计模式 之 建造者模式!

目录 一、 啥是建造者模式&#xff1f;二、 为什么要用建造者模式&#xff1f;三、 建造者模式怎么实现&#xff1f;四、 建造者模式的应用场景五、 建造者模式的优点和缺点六、 总结 &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方…...

【CentOS7】安装MinIO

下载rpm包 wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230809233022.0.0.x86_64.rpm 安装 rpm -ivh minio-20230809233022.0.0.x86_64.rpm 运行 server 后面跟着的使minio 的数据目录&#xff1b;console-address 后面跟着的是minio 的管理…...

vLLM学习1

调用方式 一、vLLM 提供的两种调用方式 1. Offline Batched Inference&#xff08;离线批处理&#xff09; 调用特点&#xff1a;一次性传入一批&#xff08;batch&#xff09;的请求&#xff0c;等待所有请求都处理完毕后&#xff0c;一次性返回推理结果。对用户而言&#x…...

ABC 385

目录 C. Illuminate Buildings D. Santa Claus E. Snowflake Tree C. Illuminate Buildings dp[ i ][ j ]&#xff1a;选择的 i 个建筑&#xff0c;间隔为 j。这样只需要两层循环就可以了&#xff0c;o&#xff08;n^2&#xff09; 其实本质只是个一维 dp&#xff0c;但我还需…...

綫性與非綫性泛函分析與應用_1.例題(下)-半母本

第1章 實分析與函數論:快速回顧(下) 五、基數;有限集和無限集相關例題 例題1:集合基數的判斷 判斷集合和集合B=\{a,b,c,d,e\}的基數關係。 解析: 可以構造一個雙射,例如,,,,。 所以,兩個集合具有相同的基數。 例題2:可數集的證明 證明整數集是可數集。 解析: …...

49 set与map的模拟实现

目录 一、源码及框架分析 二、模拟实现map和set &#xff08;一&#xff09;复用红黑树的框架&#xff0c;并支持insert &#xff08;二&#xff09;支持迭代器的实现 &#xff08;三&#xff09;map支持 [ ] &#xff08;四&#xff09;整体代码实现 一、源码及框架分析…...

鸿蒙NEXT应用App测试-通用测试

注意&#xff1a;大家记得学完通用测试记得再学鸿蒙专项测试 https://blog.csdn.net/weixin_51166786/article/details/145768653 注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章…...

LangChain 技术入门指南:探索语言模型的无限可能

在当今的技术领域&#xff0c;LangChain 正逐渐崭露头角&#xff0c;成为开发语言模型应用的强大工具。如果你渴望深入了解并掌握这一技术&#xff0c;那么就跟随本文一起开启 LangChain 的入门之旅吧&#xff01; (后续将持续输出关于LangChain的技术文章,有兴趣的同学可以关注…...

UE5销毁Actor,移动Actor,简单的空气墙的制作

1.销毁Actor 1.Actor中存在Destory()函数和Destoryed()函数 Destory()函数是成员函数&#xff0c;它会立即标记 Actor 为销毁状态&#xff0c;并且会从场景中移除该 Actor。它会触发生命周期中的销毁过程&#xff0c;调用 Destroy() 后&#xff0c;Actor 立即进入销毁过程。具体…...

STM32基础篇(二)------GPIO

GPIO简介 GPIO&#xff08;General Purpose Input Output&#xff09;通用输入输出口 可配置为8种输入输出模式 引脚电平&#xff1a;0V~3.3V&#xff0c;部分引脚可容忍5V 输出模式下可控制端口输出高低电平&#xff0c;用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等 输入…...

亲测Win11电脑可以安装LabVIEW的版本,及2015、2018、2020版本直接的区别

下面是我电脑的信息 设备名称 DESKTOP-04HHS8S 处理器 13th Gen Intel(R) Core(TM) i5-13500H 2.60 GHz 机带 RAM 16.0 GB (15.7 GB 可用) 设备 ID 82798104-C565-4167-A21E-5EB5DEFAA541 产品 ID 00331-20300-00000-AA678 系统类型 64 位操作系统, 基于 …...

Idea2024中搭建JavaFX开发环境并创建运行项目

Idea2024中搭建JavaFX开发环境并创建运行项目 本文以Java语言为例演示如何创建JavaFX开发项目和部署开发环境&#xff0c;读者可以根据个人实际灵活选择相关参数。 一、项目创建与环境搭建步骤 新建JavaFX项目&#xff0c;选择适合项目实际的语言、系统和JDK。 项目设置-设置…...

认知重构 | 自我分化 | 苏格拉底式提问

注&#xff1a;本文为 “认知重构 | 自我分化” 相关文章合辑。 心理学上有一个词叫&#xff1a;认知重构&#xff08;改变 “非黑即白&#xff0c;一分为二” 的思维方式&#xff09; 原创 心理师威叔 心理自救 2024 年 10 月 26 日 19:08 广东 你有没有过这样的时候&#x…...

MFC开发:如何创建第一个MFC应用程序

文章目录 一、概述二、MFC 的主要组件三、创建一个MFC窗口四、控件绑定消息函数 一、概述 MFC 是微软提供的一个 C 类库&#xff0c;用于简化 Windows 应用程序的开发。它封装了 Windows API&#xff0c;提供面向对象的接口&#xff0c;帮助开发者更高效地创建图形用户界面&am…...

nodejs:vue 3 + vite 作为前端,将 html 填入<iframe>,在线查询英汉词典

向 doubao.com/chat/ 提问&#xff1a; node.js js-mdict 作为后端&#xff0c;vue 3 vite 作为前端&#xff0c;编写在线查询英汉词典 后端部分&#xff08;express js-mdict &#xff09; 详见上一篇&#xff1a;nodejs&#xff1a;express js-mdict 作为后端&#xff…...

基于 Python Django 的校园互助平台(附源码,文档)

博主介绍&#xff1a;✌Java徐师兄、7年大厂程序员经历。全网粉丝13w、csdn博客专家、掘金/华为云等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不…...

玩转 Java 与 Python 交互,JEP 库来助力

文章目录 玩转 Java 与 Python 交互&#xff0c;JEP 库来助力一、背景介绍二、JEP 库是什么&#xff1f;三、如何安装 JEP 库&#xff1f;四、JEP 库的简单使用方法五、JEP 库的实际应用场景场景 1&#xff1a;数据处理场景 2&#xff1a;机器学习场景 3&#xff1a;科学计算场…...

【Linux】基于UDP/TCP服务器与客户端的实现

目录 一、UDP &#xff08;一&#xff09;Server.hpp &#xff08;二&#xff09;Server.cpp &#xff08;三&#xff09;Client.hpp &#xff08;四&#xff09;Client.cpp &#xff08;五&#xff09;User.hpp 二、TCP &#xff08;一&#xff09;多进程版本的服务器与…...

Unity for Python —— 强大的 Python 脚本支持提升 Unity 编辑器效率

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity for Python —— 强大的 Python 脚本支持提升 Unity 编辑器效率 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 Tec…...

【Dubbo+Zookeeper】——SpringBoot+Dubbo+Zookeeper知识整合

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…...

家用路由器的WAN口和LAN口有什么区别

今时今日&#xff0c;移动终端盛行的时代&#xff0c;WIFI可以说是家家户户都有使用到的网络接入方式。那么路由器当然也就是家家户户都不可或缺的设备了。而路由器上的两个实现网络连接的基础接口 ——WAN 口和 LAN 口&#xff0c;到底有什么区别&#xff1f;它们的功能和作用…...

Python--函数入门

1. 函数基础概念 1.1 什么是函数 定义&#xff1a;函数是一段可重复调用的代码块&#xff0c;用于封装特定功能。 核心作用&#xff1a; 代码重用&#xff1a;减少重复代码的编写。增强可读性&#xff1a;通过命名和模块化让代码逻辑更清晰。 1.2 函数的定义与调用 def 函…...

EasyRTC低延迟通信与智能处理:论嵌入式WebRTC与AI大模型的技术融合

在当今数字化时代&#xff0c;实时通信的需求日益增长&#xff0c;视频通话作为一种高效、直观的沟通方式&#xff0c;广泛应用于各个领域。WebRTC技术的出现&#xff0c;为实现浏览器之间的实时音视频通信提供了便捷的解决方案。而基于WebRTC技术的EasyRTC视频通话SDK&#xf…...

《操作系统 - 清华大学》 8 -6:进程管理:进程状态变化模型

进程状态及其转换全解析 在操作系统中&#xff0c;进程有着特定的生命周期和多种状态变化。不考虑进程结束时&#xff0c;进程主要有三个基本状态。 运行态&#xff1a;即进程正在占用CPU执行任务。总结&#xff1a;运行态表示进程当前正在使用CPU。就绪状态&#xff1a;进程…...

大语言模型中的 Token如何理解?

在大语言模型中&#xff0c;Token 是文本处理的基本单元&#xff0c;类似于“文字块”&#xff0c;模型通过将文本分割成Token来理解和生成内容。举一个形象一点的例子&#xff0c;可以理解为 AI 处理文字时的“最小积木块”。就像搭乐高时&#xff0c;每块积木是基础单位一样&…...

信息学奥赛一本通 1522:网络 | OpenJudge 百练 1144:Network

【题目链接】 ybt 1522&#xff1a;网络 OpenJudge 百练 1144:Network 【题目考点】 1. 图论&#xff1a;割点 【解题思路】 每个交换机是一个顶点&#xff0c;如果两地点之间有电话线连接&#xff0c;那么两顶点之间有一条无向边&#xff0c;该图是无向图。 初始时任何地…...

3分钟快速本地部署deepseek

DeepSeek简介 DeepSeek 是杭州深度求索人工智能基础技术研究有限公司开发的一系列大语言模型&#xff0c;背后是知名量化资管巨头幻方量化3。它专注于开发先进的大语言模型和相关技术&#xff0c;拥有多个版本的模型&#xff0c;如 DeepSeek-LLM、DeepSeek-V2、DeepSeek-V3 等…...

Linux系统管理与编程01:准备工作

0 准备工作 0.1 安装VMWare Workstation pro17 到百度搜一下&#xff0c;到处都是。安装好VMWare Workstation pro17&#xff08;以下简称VW&#xff09;。 图0- 1 安装过程略。 0.2下载CentOS7.6 图0- 2 选择minimal版本。 0.3下载yum库文件 下载阿里云yum库文件https:…...

常用的几种编码方式

常见的编码方式有多种&#xff0c;每种编码方式都有其特定的用途和特点。以下是几种常见的编码方式&#xff1a; ASCII&#xff08;美国信息交换标准代码&#xff09; 用途&#xff1a;主要用于表示英文字符及控制字符。特点&#xff1a;使用7位二进制数表示字符&#xff0c;能…...

WebXR教学 03 项目1 旋转彩色方块

一、项目结构 webgl-cube/ ├── index.html ├── main.js ├── package.json └── vite.config.js二、详细实现步骤 初始化项目 npm init -y npm install three vite --save-devindex.html <!DOCTYPE html> <html lang"en"> <head><…...

从零开始的网站搭建(以照片/文本/视频信息通信网站为例)

本文面向已经有一些编程基础&#xff08;会至少一门编程语言&#xff0c;比如python&#xff09;&#xff0c;但是没有搭建过web应用的人群&#xff0c;会写得尽量细致。重点介绍流程和部署云端的步骤&#xff0c;具体javascript代码怎么写之类的&#xff0c;这里不会涉及。 搭…...

netcore 启用gzip压缩及缓存

public void ConfigureServices(IServiceCollection services) {....// 配置gzip 与 br的压缩等级为最优services.Configure<BrotliCompressionProviderOptions>(options > {options.Level CompressionLevel.Optimal;});services.Configure<GzipCompressionProvid…...