Spring Security 安全框架NOTE

news2024/11/25 16:51:06

目录

1、什么是 Spring Security 安全框架?

2、关于 SpringSecurity 中的认证

3、关于 SpringSecurity 中的授权

3.1 从数据库中查询用户的权限信息

4、关于自定义失败处理

5、跨域问题


前提引入:

随着科技的完善,现在几乎所有的网站以及软件都需要进行授权认证,使之更加的安全可靠

认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

授权:经过认证后判断当前用户是否有权限进行某个操作

1、什么是 Spring Security 安全框架?

定义:Spring Security 是一个功能强大且灵活的身份验证和授权框架,用于保护基于 Spring 的应用程序;它提供了一套综合的安全性解决方案,可以用于 Web 应用程序、REST API、微服务等各种应用场景

Spring Security 的主要功能和作用如下:

  1. 身份验证(Authentication):Spring Security 提供了多种身份验证方式,包括基于表单、HTTP 基本认证、LDAP、OAuth2 等。它可以集成到应用程序中,通过验证用户提供的凭据(如用户名和密码)来验证用户身份

  2. 授权(Authorization):Spring Security 支持基于角色和权限的授权机制。它允许您定义细粒度的访问控制规则,例如指定哪些用户具有访问某些受保护资源的权限

  3. 攻击防护(Attack Protection):Spring Security 提供了一系列的防护机制来应对常见的安全攻击,例如跨站点请求伪造(CSRF)攻击、会话固定攻击、点击劫持等。它通过配置合适的安全措施来保护应用程序免受这些攻击的风险

  4. 集成第三方认证系统:Spring Security 可以与其他身份认证系统(如LDAP、OAuth2)进行集成,以便使用这些系统中已有的用户凭据进行身份验证

  5. 定制化和扩展性:Spring Security 提供了丰富的配置选项和可插拔的拦截器机制,使开发人员可以根据应用程序的需求进行灵活的定制和扩展

  6. 审计和日志记录:Spring Security 可以记录关于身份验证和授权过程的审计日志,为应用程序的安全性监控和追踪提供支持


2、关于 SpringSecurity 中的认证

登录校验流程图解:

SpringSecurity 安全框架其实是多个过滤器(过滤器链)所组成的,内部包含了各种功能

SpringSecurity 中过滤器详解图:

UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要由它负责

ExceptionTranslationFilter:处理过滤器链中抛出的任何 AccessDeniedException 和AuthenticationException  异常(即 用户授权异常 和 用户认证异常)

FilterSecurityInterceptor:负责权限校验的过滤器

认证流程详解图:

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口;里面定义了一个根据用户名查询用户信息的方法

UserDetails接口:提供核心用户信息;通过 UserDetailsService 根据用户名获取处理的用户信息要封装成 UserDetails 对象返回;然后将这些信息封装到 Authentication 对象中

这里设定一个 UserLogin 类,来继承 UserDetails 接口,用来表示登录用户的信息,便于之后的调用(User 为用户类)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

    private User user;


    /**
     * 获取权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        
        return null;
    }

    @Override
    public String getPassword() {

        return user.getPassword();
    }

    @Override
    public String getUsername() {

        return user.getUserName();
    }

    /**
     * 判断用户账号是否过期
     */
    @Override
    public boolean isAccountNonExpired() {

        return true;
    }

    /**
     * 判断用户是否被锁定
     */
    @Override
    public boolean isAccountNonLocked() {

        return true;
    }

    /**
     * 判断该用户的认证凭证是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {

        return true;
    }

    /**
     * 用于判断用户是否启用
     */
    @Override
    public boolean isEnabled() {

        return true;
    }

}

由于认证的时候,存在自定义的登录接口,需要让 SpringSecurity 将其放行,使用户在不需要登录的时候也能够访问;同时,可以设置过滤器的前后位置(这里将JWT认证过滤器放在用户登录过滤器之前)

代码如下:

这里是用户登录验证的接口

/**
 * 【用户的验证】
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private RedisCache redisCache;

    @Override
    public ResponseResult login(User user) {

        //1.使用 authenticate 方法进行用户的验证
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());

        Authentication authenticateResult = authenticationManager.authenticate(authenticationToken);

        //2.如果认证没通过,则进行提示
        if(Objects.isNull(authenticateResult)){

            throw new RuntimeException("用户名或密码错误!");
        }

        //3.如果认证通过,使用 userId 生成一个 jwt
        LoginUser loginUser = (LoginUser) authenticateResult.getPrincipal(); //当前登录用户信息
        Long userId = loginUser.getUser().getId();
        String jwt = JwtUtil.createJWT(userId.toString());  //使用 jwt 工具类进行生成

        HashMap<String, String> map = new HashMap<>();
        map.put("token",jwt);

        //4.把完整的用户信息存入 redis ,其中 userId 作为 key
        redisCache.setCacheObject("login:"+userId,loginUser);

        return new ResponseResult(200,"登录成功!",map);
    }

}

这里是认证放行接口,同时设置了过滤器的前后顺序

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    /**
     * 【这里是认证放行接口】
     */
    @Bean
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http

                //关闭csrf保护机制
                .csrf().disable()

                //不通过Session获取SecurityContext,而是通过 Token 进行获取
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()

                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()

                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //将 jwt认证过滤器 放在 用户登录过滤器 之前
        http.addFilterBefore(jwtAuthenticationTokenFilter,             
                             UsernamePasswordAuthenticationFilter.class);

    }

}

这里是 JWT认证过滤器,通过请求请求头中发送过来的 Token, 对 Token 中进行解析,从而取出其中的 userId;使用 userId 去 redis 中获取相应的 LoginUser 对象,最后存入 SecurityContextHolder 中(这里,Token 中存放的是用户 ID)

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        //1.从请求头中获取 token
        String token = request.getHeader("token");
        //1.1 若 token 不存在则直接放行(token 不存在说明不需要认证)
        if(!StringUtils.hasLength(token)){
            chain.doFilter(request,response);
            return;
        }

        //2.解析 token
        String userId ;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token 非法");
        }

        //3.从 redis 中获取用户信息
        String redisKey = "login:"+ userId;

        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){

            throw new RuntimeException("该用户不存在!");
        }

        //4.存入 SecurityContextHolder
        //TODO 获取权限信息
        UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken(loginUser,null,null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);


        //5.进行放行
        chain.doFilter(request,response);
    }

前面已经有了用户登录接口,所以现在需要退出登录接口

由于之前在 JwtAuthenticationTokenFilter 过滤器中,已经将登录成功的用户信息放入了 SecurityContextHolder 对象中,所以这里需要将其从中取出;

然后通过用户信息获取到用户 ID,最后在 Redis 中根据对应的用户 ID 删除对应用户的 Redis 缓存信息,这样就可以在下一次登录的时候使之前已经退出的用户 Token 失效,从而需要重新登录,才可访问其他需要授权的页面

/**
     * 【用户退出登录】
     */
    @Override
    public ResponseResult logout() {

        //1.由于用户信息已经保存到了 SecurityContextHolder  中
        //  所以从 SecurityContextHolder 中获取用户 ID
        UsernamePasswordAuthenticationToken authentication
                = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        //1.1 通过 UserDetails 获取用户登录信息
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        User user = loginUser.getUser();
        Long userId = user.getId();

        //2.根据用户 id 删除 redis 中对应的 key 所对应的 value 值
        redisCache.deleteObject("login:"+userId);

        return new ResponseResult(200,"退出登录成功!");
    }

3、关于 SpringSecurity 中的授权

前文引入:

        例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能;但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能

所以说,设置权限这一项,对于一个完整的软件而言尤为重要......

第一步:首先在认证放行接口 SecurityConfig 处开启权限控制

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)  //【开启权限控制】
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  ...............

}

第二步:这里创建一个测试权限的接口,同时设置权限的信息

需要说明的是,@PreAuthorize 注解用于在方法上面设置权限的信息 ,SpEL 表达式定义权限规则,这里的表达式 hasAuthority('test') 表示该方法需要具有"test"权限

@RestController
public class HelloController {

    @GetMapping("hello")
    @PreAuthorize("hasAuthority('system:dept:list')")   //设置权限信息
    public String hello(){

        return "hello";
    }

}

第三步:我们需要重写 LoginUser(继承 UserDetals) 中的 getAuthorities() 方法,将继承 UserDetailService 的类 传过来的权限信息封装到 SimpleGrantedAuthority 对象中进行返回

这里是 UserDetailsServiceImpl 类(继承了 UserDetailsService 类),这里的用户权限信息先写死

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private UserMappper userMappper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(User::getUserName,username);

        //1.查询用户信息
        User user = userMappper.selectOne(queryWrapper);

        if(Objects.isNull(user)){
            log.error("用户名或密码错误!");
            throw new RuntimeException("用户名或密码错误!");
        }

        //2.将用户信息封装为 UserDetails 对象返回
        ArrayList<String> list = new ArrayList<>(Arrays.asList("test","admin"));    //这里权限信息先进行写死

        return new LoginUser(user,list);    //将用户以及权限信息传入 LoginUser 对象中
    }

}

  这里是 LoginUser 类(继承了 UserDetails 类 )

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;   //创建一个集合,用来封装权限信息

    @JSONField(serialize = false)   //敏感信息,让 JSON 字符串不包含该字段
    private List<SimpleGrantedAuthority> authorities;

    /**
     * 获取权限信息
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {

        //1.若不为空,则直接返回(说明之前已经存在权限信息)
        if(authorities!=null){
            return authorities;
        }

        //2.将 permission 中的权限信息封装到 GrantedAuthority 对象中进行返回
        authorities = permissions.stream()
                .map(new Function<String, SimpleGrantedAuthority>() {
                    @Override
                    public SimpleGrantedAuthority apply(String permission) {

                        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
                        return simpleGrantedAuthority;
                    }
                }).collect(Collectors.toList());

        return authorities;
    }

    @Override
    public String getPassword() {

        return user.getPassword();
    }

    @Override
    public String getUsername() {

        return user.getUserName();
    }

    /**
     * 判断用户账号是否过期
     */
    @Override
    public boolean isAccountNonExpired() {

        return true;
    }

    /**
     * 判断用户是否被锁定
     */
    @Override
    public boolean isAccountNonLocked() {

        return true;
    }

    /**
     * 判断该用户的认证凭证是否过期
     */
    @Override
    public boolean isCredentialsNonExpired() {

        return true;
    }

    /**
     * 用于判断用户是否启用
     */
    @Override
    public boolean isEnabled() {

        return true;
    }


    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }
}

第四步:由于这时候  LoginUser 中的  getAuthorities() 重写方法已存在用户的权限信息,所以经过 JWT 认证过滤器的时候,需要将其存入 SecurityContextHolder 对象中并进行返回,因为在整个 SpringSecurity 框架中,这个对象是连接整个认证流程的上下文

//4.将用户信息存入 SecurityContextHolder
//4.1获取权限信息
Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();

//4.2 将用户信息以及权限信息存入 SecurityContextHolder 对象中
UsernamePasswordAuthenticationToken authenticationToken
                    = new UsernamePasswordAuthenticationToken(loginUser,null,authorities);

SecurityContextHolder.getContext().setAuthentication(authenticationToken);

//5.进行放行
chain.doFilter(request,response);

第五步:在用户登录退出接口处,使用该上下文对象,获取当前用户权限信息,来进行后续的操作

这里由于用户的权限信息是写死的,平时通常是动态获取的,所以我们选择从数据库中进行动态的获取权限信息

3.1 从数据库中查询用户的权限信息

这里使用的是 RBAC 模型,即基于角色的权限控制

如图所示:

这里使用角色的关联表将其他的数据表关联起来

  由于存在关联表,所以需要进行多表联查

对应的 SQL 语句如下所示:

SELECT DISTINCT sm.perms
FROM sys_user_role sur  #用户角色关联表 user_role

LEFT JOIN sys_role sr  #角色表 role
ON sur.role_id = sr.id 

LEFT JOIN sys_role_menu srm  #角色权限关联表 role_menu
ON srm.role_id = sur.role_id

LEFT JOIN sys_menu sm  #权限表 menu
ON sm.id = srm.menu_id

WHERE user_id = 2 
AND sr.`status` = 0 AND sm.`status` = 0

MyBatis-Plus 对应的 xml 文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="MyPro.Mapper2.MenuMapper">

    <!-- 根据用户 ID 获取用户的权限信息 -->
    <select id="selectPermsByUserId" resultType="java.lang.String">
        SELECT DISTINCT (sm.perms)
        FROM sys_user_role sur

                 LEFT JOIN sys_role sr
                           ON sur.role_id = sr.id

                 LEFT JOIN sys_role_menu srm
                           ON srm.role_id = sur.role_id

                 LEFT JOIN sys_menu sm
                           ON sm.id = srm.menu_id

        WHERE user_id = {#userId}
          AND sr.`status` = 0 AND sm.`status` = 0
    </select>

</mapper>

这里进行调用 Mapper 方法,动态的获取用户权限信息

@Slf4j
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(User::getUserName,username);

        //1.查询用户信息
        User user = userMapper.selectOne(queryWrapper);

        if(Objects.isNull(user)){
            log.error("用户名或密码错误!");
            throw new RuntimeException("用户名或密码错误!");
        }

        //2.将用户信息封装为 UserDetails 对象返回
//        ArrayList<String> list = new ArrayList<>(Arrays.asList("test","admin"));    //这里权限信息先进行写死

        List<String> list = menuMapper.selectPermsByUserId(user.getId()); //这里进行动态的获取用户权限信息

        return new LoginUser(user,list);    //将用户以及权限信息传入 LoginUser 对象中
    }

}


4、关于自定义失败处理

前言:

在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter 捕获到;在 ExceptionTranslationFilter 中会去判断是认证失败还是授权失败出现的异常

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

如果是授权过程中出现的异常会被封装成 AccessDeniedException 然后调用AccessDeniedHandler 对象的方法去进行异常处理

所以,我们需要进行自定义失败处理,以进行统一的异常处理

这里是【用户认证】的异常处理类:

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(),"用户认证失败!");

        String json = JSON.toJSONString(result);

        //处理异常
        WebUtils.renderString(response,json);
    }

}

这里是【用户授权】的异常处理类:

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {

        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(),"您的权限不足!");

        String json = JSON.toJSONString(result);

        //处理异常
        WebUtils.renderString(response,json);
    }

}

将上面的异常处理器在 Config 类中进行配置,让 SpringSecurity 框架使用自定义处理器:

protected void configure(HttpSecurity http) throws Exception {

        http
                //关闭csrf保护机制
                .csrf().disable()

                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()

                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()

                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //将 jwt认证过滤器 放在 用户登录过滤器 之前
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //这里是进行配置异常处理器
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)  //用户认证
                .accessDeniedHandler(accessDeniedHandler);  //用户授权
    }


5、跨域问题

前言:

浏览器出于安全的考虑,使用 XMLHttpRequest对象 发起 HTTP请求时必须遵守同源策略(要求源相同才能正常进行通信,即协议、域名、端口号都完全一致,否则就是跨域的HTTP请求,跨域默认情况下是被禁止的

前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题

所以我们就要处理一下,让前端能进行跨域请求

这里对 SpringBoot 配置,进行跨域请求的配置:

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);

    }

}

当然,以上只是对 Spring 进行了配置跨域的请求,还需要对 SpringSecurity 进行跨域的配置

这里,在 Config 配置类中进行跨域的配置:

@Override
    protected void configure(HttpSecurity http) throws Exception {

        http
                //关闭csrf保护机制
                .csrf().disable()
                //不通过Session获取SecurityContext,而是通过Token进行获取
       .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //将 jwt认证过滤器 放在 用户登录过滤器 之前
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //这里是进行配置异常处理器
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)  //用户认证
                .accessDeniedHandler(accessDeniedHandler);  //用户授权

        //允许跨域
        http.cors();
    }
}

注意:如果使用 PostMan 进行测试是不会成功的,因为它的本质还是在 “同源策略” 中

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

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

相关文章

为大模型添加记忆体,GBASE南大通用驶入向量赛道

大数据产业创新服务媒体 ——聚焦数据 改变商业 理解、生成、逻辑、记忆是人工智能的四大核心能力。 一段人类的日常对话通常可以分解为引子、记忆、分析三个部分。计算机自然语言处理的解法&#xff0c;AI科学家归纳出一个CPV结构&#xff1a;以ChatGPT为代表的大模型承担“分…

Vmware 网络恢复断网和连接

如果你的 虚拟机无法联网了&#xff0c;比如&#xff1a; vmware 无法将网络更改为桥接状态: 没有未桥接的主机网络适配器 等各种稀奇古怪的问题&#xff1b; 按照下面操作 还远默认设置 包你解决各种问题&#xff01;

Pycharm----将Anaconda建立的环境导入

首先打开项目设置&#xff0c;点击添加 随后点击现有环境&#xff0c;点击三个。。。号进行添加 最后找到你Anaconda安装文件夹&#xff0c;envs找到你建立的环境名称&#xff0c;找到python.exe将它导入即可让现在的python环境为你建立的环境&#xff0c;同时还需要更改终端方…

接口自动化测试系列-接口测试

接口测试工具-postman 利用postman完成接口测试:官网。 接口一般包含&#xff1a; url:请求地址&#xff0c;如:https://www.baidu.com/ method:请求方式&#xff0c;get,post,update,delete等 headers:请求头 body/params:请求体&#xff0c;post一般存在body中。get请求放在…

苹果手机隔空投送怎么用?隔空投送的使用教程来了!

如何实现苹果设备之间快速高效地传输各种数据&#xff1f;相信我&#xff0c;使用【隔空投送】是大家的不二选择。苹果手机的【隔空投送】功能&#xff0c;即大家口中常说的“Airdrop”&#xff0c;能够让苹果用户实现近距离传输照片、视频、文件等。苹果手机隔空投送怎么用&am…

安科瑞电能质量监测与治理系统的解决方案-安科瑞黄安南

01 电能质量问题及现象 02 电能质量标准及选型 03 安科瑞电能质量产品及服务 04 经典案例分析

实现卓越供应链:RFID技术的革命性应用

在现代制造业中&#xff0c;供应链和物流的高效运作至关重要&#xff0c;它不仅影响着生产效率&#xff0c;还直接关系到企业的竞争力和客户满意度。为了应对这些挑战&#xff0c;越来越多的企业开始关注智能制造RFID智能设备&#xff0c;将其应用于供应链和物流管理&#xff0…

在OpenStack私有云上安装配置虚拟机

文章目录 零、学习目标一、登录大数据实训云二、创建网络三、创建路由四、添加接口五、创建端口六、添加安全组规则七、创建实例&#xff08;一&#xff09;实例规划&#xff08;二&#xff09;创建实例 - ied&#xff08;三&#xff09;创建实例 - master、slave1与slave2&…

Cesium 根据鼠标点击生成点击点的坐标信息

Cesium 根据鼠标点击生成点击点的坐标信息 一、需求二、分析1. 创建鼠标点击事件2. 点击生成坐标但不是经纬度&#xff0c;而是笛卡尔坐标系下的坐标&#xff0c;这个时候需要做一次转换3. 完整代码 三、数据保存 一、需求 在日常开发中 &#xff0c;会遇到根据鼠标点击生成对应…

MindSponge分子动力学模拟——使用迭代器进行系统演化(2023.09)

技术背景 在前面几篇博客中&#xff0c;我们已经介绍过使用MindSponge去定义一个系统以及使用MindSponge计算一个分子系统的单点能。这篇文章我们将介绍一下在MindSponge中定义迭代器Updater&#xff0c;并使用Sponge对系统进行演化&#xff0c;最后使用CallBack对输出结果进行…

Spring——Spring的控制反转IOC

摘要 IoC 不是一种技术&#xff0c;只是一种思想&#xff0c;一个重要的面向对象编程的法则&#xff0c;它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象&#xff0c;从而导致类与类之间高耦合&#xff0c;难于测试&#xff1b;…

msvcp140.dll是什么东西?msvcp140.dll丢失的5个常用解决方法

今天&#xff0c;我将为大家带来计算机丢失msvcp140.dll修复教程。在我们的日常生活和学习中&#xff0c;计算机问题是无处不在的。有时候&#xff0c;我们可能会遇到一些困扰&#xff0c;比如计算机丢失msvcp140.dll文件。msvcp140.dll是Windows系统中非常重要的动态链接库文件…

代码随想录算法训练营第二十七天| 131.分割回文串

131.分割回文串 本题较难&#xff0c;大家先看视频来理解 分割问题&#xff0c;明天还会有一道分割问题&#xff0c;先打打基础。 代码随想录 视频讲解&#xff1a;带你学透回溯算法-分割回文串&#xff08;对应力扣题目&#xff1a;131.分割回文串&#xff09;| 回溯法精讲…

CS420 课程笔记 P6 - 游戏逆向中的虚拟内存

文章目录 IntroVirtual memoryExample!Static example Intro 在上个视频中&#xff0c;我们知道有些地址在你重进游戏时就会无效&#xff0c;有的有时有效&#xff0c;我们需要了解称为虚拟内存的东西 记住这些信息&#xff1a;当你双击打开 Squally.exe 游戏时&#xff0c;系…

【C++ 学习 ⑲】- 多态(下)

目录 一、虚函数表和多态的原理 1.1 - 虚函数表 1.2 - 多态的原理 二、单继承和多继承关系中的虚函数表 2.1 - 单继承关系中的虚函数表 2.2 - 多继承关系中的虚函数表 三、纯虚函数和抽象类 一、虚函数表和多态的原理 1.1 - 虚函数表 问&#xff1a;sizeof(b) 是多少&a…

使用docker部署db2

1.使用docker部署db2 1.1 拉db2镜像 将db2镜像拉起到本地。 docker pull ibmcom/db21.2启动容器 docker run -d -p 50000:50000 --name db2 --privilegedtrue -e DB2INST1_PASSWORDdbPassword DBNAMEjumpdb -e LICENSEaccept -v /usr/local/db2:/database ibmcom/db2实例化…

选择成都优优聚的优势是什么?

美团代运营是一种服务模式&#xff0c;旨在帮助商家提升线上销售业绩&#xff0c;并有效降低经营风险。通过专业团队的运营管理&#xff0c;商家可以获得更加稳定和可靠的线上业务经营。美团代运营提供了一整套解决方案&#xff0c;包括线上推广、店铺运营、商品管理、客户服务…

武汉旅游地

原文链接&#xff1a;https://www.cnblogs.com/MrFlySand/p/17678215.html 发表时间&#xff1a;2023年9月4日21:59:14 更新时间&#xff1a;2023年9月4日21:59:06 东湖飞鸟世界(动物园) 地址&#xff1a;东湖风景区沿湖大道20号时间&#xff1a;9:00-17:00交通&#xff1a;地铁…

远距离WiFi模组方案,实现移动设备之间高效通信,无人机远程图传应用

随着科技的不断进步&#xff0c;无线通信技术也在日新月异地发展。其中&#xff0c;WiFi技术已经成为现代生活中不可或缺的一部分。 从室内到室外&#xff0c;WiFi的应用场景正在不断扩大&#xff0c;为我们的日常生活和工业生产带来了极大的便利。 WiFi技术&#xff0c;即无…

斩获两大年度奖项,这家厂商如何决胜汽车智能化下半场

汽车智能化决战下半场的鼓声已经敲响。 一方面&#xff0c;智能座舱正在向3.0时代迈进&#xff0c;域集中式架构、多域融合已经成为了全新的市场趋势。 另一方面&#xff0c;软件正在成为车企构建差异化产品的重要手段&#xff0c;未来将成为车企盈利的重要组成部分。在这样的…