springboot集成springsecurity

转载自:www.javaman.cn

1、整合springsecurity

添加pom.xml

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、springsecurity认证授权流程

img

认证管理

在这里插入图片描述

流程图解读:

1、用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。

2、然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。

3、认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。

4、SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。

授权管理

在这里插入图片描述

访问资源(即授权管理),访问url时,会通过FilterSecurityInterceptor拦截器拦截,其中会调用SecurityMetadataSource的方法来获取被拦截url所需的全部权限,再调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的投票策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则决策通过,返回访问资源,请求放行,否则跳转到403页面、自定义页面。

根据上面的认证授权流程,具体的实现步骤从3-8

1、首先定义一个我们自己的实现类集成SpringSecurity的UserDetailsService,实现loadUserByUsername方法,就是下面的步骤3,当抛出AccessDeniedException时,就要进行处理,也就是步骤4

2、接着编写SpringSecurityConfig配置文件,就是下面的步骤7,需要进行认证成功后的处理,就是下面的步骤5

3、认证失败后,对认证失败进行处理,就是下面的步骤6

5、通过auth.userDetailsService(sysUserService),配置 AuthenticationManagerBuilder 来使用 sysUserService 加载用户的详细信息,并使用密码编码器来处理密码。这样,当应用程序需要验证用户的身份时,它会使用这些配置来检查用户提供的凭据(通常是用户名和密码)是否正确。如果凭据正确,用户将被允许访问受保护的资源;如果凭据不正确,将拒绝访问

6、接下来通过步骤7的安全配置:

定义哪些URL不需要身份验证(如/loginPage和/getImgCode)。
配置登录页面、登录处理URL、成功和失败的处理程序等。
添加一个自定义的验证码过滤器。
配置“记住我”功能。
禁用CSRF保护(通常不推荐这样做,但在这里它被禁用了)。
设置响应头中的X-Frame-Options属性。
配置会话管理,例如定义会话失效时的跳转URL。

3、创建SysUserService集成UserDetailsService

定义一个名为SysUserService的服务类,该类主要用于处理与系统用户相关的业务逻辑:

  1. 服务类定义SysUserService继承了ServiceImpl并实现了UserDetailsService接口,这意味着它提供了与用户详细信息相关的服务。
  2. 依赖注入:使用@Autowired注解注入了多个mapper(数据访问对象)和一个密码编码器。这些mapper可能用于访问数据库中的用户、菜单、用户角色和用户岗位信息。
  3. 根据用户名加载用户信息loadUserByUsername方法根据提供的用户名从数据库中检索用户信息。如果找不到用户,则抛出UsernameNotFoundException异常。
  4. 菜单和角色权限的分配
    • 如果用户是管理员(由ConfigConsts.ADMIN_USER定义),则为其分配所有的菜单和角色。
    • 对于普通用户,根据其关联的角色ID从数据库中检索菜单和角色。如果用户没有分配任何角色,则抛出AccessDeniedException异常。
  5. 创建并返回Spring Security的用户对象:使用从数据库中检索到的用户信息(如用户名、密码等)以及分配的菜单和角色创建一个Spring Security的User对象,并返回。

为Spring Security框架提供用户的详细信息和权限设置,确保用户在系统中的访问和操作都是基于其分配的权限进行的。

package com.ds.blog.system.service;

import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ds.blog.system.entity.SysUser;
import com.ds.blog.system.entity.SysUserPost;
import com.ds.blog.system.entity.SysUserRole;
import com.ds.blog.system.entity.dto.ModifyPassDTO;
import com.ds.blog.system.entity.dto.ResetPassDTO;
import com.ds.blog.system.entity.dto.UserParamDTO;
import com.ds.blog.system.mapper.*;
import com.ds.common.constant.ConfigConsts;
import com.ds.common.domain.XmSelectNode;
import com.ds.common.enums.ResultStatus;
import com.ds.core.exception.MyGlobalException;
import com.ds.core.util.CommonUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.thymeleaf.util.ArrayUtils;

import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Optional.ofNullable;

@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> implements UserDetailsService {

    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private SysUserPostMapper sysUserPostMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser user = sysUserMapper.findByUserName(username);
        if (ObjectUtil.isNull(user)) {
            throw new UsernameNotFoundException("用户不存在");
        }
        List<String> menuRole;
        if (ConfigConsts.ADMIN_USER.equals(username)) {
            menuRole = sysMenuMapper.findMenuRole();
        } else {
            String roleIds = user.getRoleIds();
            if (StringUtils.isBlank(roleIds)) {
                throw new AccessDeniedException("用户未分配菜单");
            }
            Long[] ids = CommonUtil.getId(user.getRoleIds());
            menuRole = sysMenuMapper.findMenuRoleByRoleIds(ids);

        }
        return new User(user.getUserName(), user.getPassWord(), ConfigConsts.SYS_YES.equals(user.getEnabled()),
                true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", menuRole)));
    }
}
4、自定义AccessDeniedHandler

在步骤3中抛出 AccessDeniedException(“用户未分配菜单”),我们需要自定义处理器来处理该异常。

这段代码定义了一个名为CustomAccessDeniedHandler的类,该类实现了AccessDeniedHandler接口。这主要是用于Spring Security框架中,当用户尝试访问他们没有权限的资源时,自定义如何处理这种访问被拒绝的情况。

以下是代码的详细解释:

  1. @Component:这是一个Spring的注解,它表示CustomAccessDeniedHandler是一个Spring组件。这意味着Spring会在启动时自动检测、注册并管理这个类的实例。

  2. 实现AccessDeniedHandler接口AccessDeniedHandler是Spring Security中的一个接口,它要求实现一个handle方法。当在Spring Security中发生AccessDeniedException异常时,这个handle方法会被调用。

  3. handle方法

    • 参数

      :此方法有三个参数:

      • HttpServletRequest request:代表HTTP请求,可以用来获取请求相关的信息,如请求头、请求参数等。
      • HttpServletResponse response:代表HTTP响应,可以用来设置响应的状态码、响应头、响应体等。
      • AccessDeniedException e:是触发此处理程序的异常。可以提供有关为什么访问被拒绝的信息。
    • 方法体

      • response.setCharacterEncoding("utf-8"):设置响应的字符编码为UTF-8。
      • response.setContentType("application/json;charset=utf-8"):设置响应的内容类型为JSON,并确保字符编码为UTF-8。
      • response.getWriter().write(new ObjectMapper().writeValueAsString(new MyGlobalException(ResultStatus.TEST_USER_LIMIT))):使用Jackson库的ObjectMapperMyGlobalException对象序列化为JSON字符串,并将其写入响应体。从代码中可以看出,当访问被拒绝时,会返回一个具有特定状态的全局异常信息。

总的来说,这段代码的主要作用是为Spring Security提供一个自定义的访问拒绝处理器,当用户尝试访问他们没有权限的资源时,它会返回一个具有特定状态的JSON格式的错误信息。

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(new ObjectMapper().writeValueAsString(e.getMessage()));
    }
}
5、自定义AuthenticationSuccessHandler(认证成功处理)

定义一个名为DefaultAuthenticationSuccessHandler的类,该类是Spring Security框架中用于处理成功认证后的逻辑的组件。以下是代码的主要功能点:

  1. @Component:这是一个Spring的注解,表示该类是一个Spring组件,Spring会在启动时自动检测、注册并管理其实例。
  2. @Slf4j:这是Lombok库提供的注解,它为这个类自动生成了一个SLF4J的logger实例,名为log。这允许你在类中方便地记录日志。
  3. 扩展SavedRequestAwareAuthenticationSuccessHandler:该类继承了SavedRequestAwareAuthenticationSuccessHandler,这是Spring Security提供的一个处理器,用于在用户成功认证后重定向他们到最初请求的页面(如果存在的话)。
  4. onAuthenticationSuccess方法:这个方法覆盖了父类中的同名方法。当用户成功认证时,该方法会被调用。在这个方法中,你可以定义成功后想要执行的逻辑。这里的逻辑包括:
    • 记录一个表示成功登录的日志。
    • 设置HTTP响应的字符编码为UTF-8。
    • 设置HTTP响应的内容类型为JSON。
    • 使用响应的PrintWriter对象将一个表示成功的JSON字符串写入响应体,并刷新输出流。

总的来说,这段代码的主要作用是当用户成功认证后,记录一个日志,并向客户端发送一个表示成功的JSON响应。

@Component
@Slf4j
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        log.info("-----login in success----");
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(Result.success()));
        writer.flush();
    }
}
6、自定义AuthenticationFailureHandler(认证失败处理)

定义一个名为DefaultAuthenticationFailureHandler的类,该类是Spring Security框架中用于处理认证失败后的逻辑的组件。以下是代码的主要功能点:

  1. @Component:这是一个Spring的注解,表示该类是一个Spring组件,Spring会在启动时自动检测、注册并管理其实例。
  2. @Slf4j:这是Lombok库提供的注解,它为这个类自动生成了一个SLF4J的logger实例,名为log。这允许你在类中方便地记录日志。
  3. 扩展SimpleUrlAuthenticationFailureHandler:该类继承了SimpleUrlAuthenticationFailureHandler,这是Spring Security提供的一个处理器,用于处理认证失败的情况,并默认重定向到一个指定的失败URL。
  4. onAuthenticationFailure方法:这个方法覆盖了父类中的同名方法。当认证失败时,该方法会被调用。在这个方法中,你可以定义认证失败后想要执行的逻辑。这里的逻辑包括:
    • 记录一个表示登录失败的日志,并打印出具体的异常信息。
    • 设置HTTP响应的内容类型为JSON。
    • 设置HTTP响应的字符编码为UTF-8。
    • 使用响应的PrintWriter对象将错误信息写入响应体。
    • 如果异常是BadCredentialsException(通常表示用户名或密码不正确),则返回一个特定的错误消息“用户名或密码错误,请重试。”。
    • 对于其他类型的异常,直接返回异常的错误消息。

总的来说,这段代码的主要作用是当认证失败时,记录一个日志,并根据异常类型向客户端发送一个表示失败的JSON响应,其中包含具体的错误信息。

@Component
@Slf4j
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("login in failure : " +  exception.getMessage());

        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        String message;
        if (exception instanceof BadCredentialsException) {
            message =  "用户名或密码错误,请重试。";
            writer.write(JSON.toJSONString(Result.failure(message)));
        }else{
            writer.write(JSON.toJSONString(Result.failure(exception.getMessage())));
        }
        writer.flush();
    }
}
7、MySecurityConfig配置

这段代码是一个Spring Security的配置类,用于配置Web应用的安全性。Spring Security是一个功能强大且可定制的身份验证和访问控制框架。

以下是该代码的主要功能点:

  1. @Configuration:这是一个Spring的注解,表示该类是一个配置类,用于定义和注册beans。
  2. @EnableWebSecurity:这个注解告诉Spring Boot启用Spring Security的默认Web安全性。
  3. @EnableGlobalMethodSecurity(prePostEnabled = true):这个注解启用了全局方法安全性,允许你使用注解(如@PreAuthorize、@PostAuthorize等)在方法级别上定义访问控制。
  4. WebSecurityConfigurerAdapter:该类继承了WebSecurityConfigurerAdapter,允许你自定义Spring Security的配置。
  5. configure(AuthenticationManagerBuilder auth):在这个方法中,你可以配置AuthenticationManager,这是处理身份验证逻辑的核心组件。在这里,它配置了一个UserDetailsService和一个PasswordEncoder来处理用户的身份验证。
  6. configure(HttpSecurity http):这个方法用于配置HTTP安全性。其中包括:
    • 定义哪些URL不需要身份验证(如/loginPage/getImgCode)。
    • 配置登录页面、登录处理URL、成功和失败的处理程序等。
    • 添加一个自定义的验证码过滤器。
    • 配置“记住我”功能。
    • 禁用CSRF保护(通常不推荐这样做,但在这里它被禁用了)。
    • 设置响应头中的X-Frame-Options属性。
    • 配置会话管理,例如定义会话失效时的跳转URL。

总的来说,这段代码配置了Spring Security来处理Web应用的安全性,包括身份验证、访问控制、会话管理等。需要注意的是,其中禁用了CSRF保护,这通常是不安全的做法,除非有特定的原因。

package com.ds.core.config;

import com.ds.blog.system.service.SysUserService;
import com.ds.core.security.CustomAccessDeniedHandler;
import com.ds.core.security.DefaultAuthenticationFailureHandler;
import com.ds.core.security.DefaultAuthenticationSuccessHandler;
import com.ds.core.security.filter.ValidateCodeFilter;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler;
    @Autowired
    private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;
    @Autowired
    private ValidateCodeFilter validateCodeFilter;
    @Autowired
    private CustomAccessDeniedHandler accessDeniedHandler;

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(sysUserService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        // 不需要权限能访问的资源
        web.ignoring()
                // 接口放行
                .antMatchers("/api/**")
                .antMatchers("/front/**")
                // 静态资源
                .antMatchers("/static/**")
                .antMatchers("/favicon.ico");

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 放过
                .antMatchers("/loginPage", "/getImgCode").permitAll()
                .antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll()
                // 剩下的所有的地址都是需要在认证状态下才可以访问
                .anyRequest().authenticated()
                .and()
                // 过滤登录验证码
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                // 配置登录功能
                .formLogin()
                .usernameParameter("userName")
                .passwordParameter("passWord")
                // 指定指定要的登录页面
                .loginPage("/loginPage")
                // 处理认证路径的请求
                .loginProcessingUrl("/login")
                .successHandler(defaultAuthenticationSuccessHandler)
                .failureHandler(defaultAuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
                .and()
                // 登出
                .logout()
                .invalidateHttpSession(true)
                .deleteCookies("remember-me")
                .logoutUrl("/logout")
                .logoutSuccessUrl("/loginPage")
                .and()
                .rememberMe()
                // 有效期7天
                .tokenValiditySeconds(3600 * 24 * 7)
                // 开启记住我功能
                .rememberMeParameter("remember-me")
                .and()
                //禁用csrf
                .csrf().disable()
                // header response的X-Frame-Options属性设置为SAMEORIGIN
                .headers().frameOptions().sameOrigin()
                .and()
                // 配置session管理
                .sessionManagement()
                //session失效默认的跳转地址
                .invalidSessionUrl("/loginPage");
    }
}
8、登录界面
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>ds博客</title>
    <div th:replace="common/link::header"></div>
    <link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all">
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
    <div class="layadmin-user-login-main">
        <div class="layadmin-user-login-box layadmin-user-login-header">
            <h2>ds博客</h2>
            <p>后台登录</p>
        </div>
        <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
            <div class="layui-form-item">
                <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
                <input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
            </div>
            <div class="layui-form-item">
                <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
                <input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
            </div>
            <div class="layui-form-item">
                <div class="layui-row">
                    <div class="layui-col-xs7">
                        <label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label>
                        <input type="text" name="code"  lay-verify="required" placeholder="图形验证码" class="layui-input">
                    </div>
                    <div class="layui-col-xs5">
                        <div style="margin-left: 10px;">
                            <img id="codeImg" class="layadmin-user-login-codeimg">
                        </div>
                    </div>
                </div>
            </div>
            <div class="layui-form-item" style="margin-bottom: 20px;">
                <input type="checkbox" name="remember-me" lay-skin="primary" title="记住密码">
            </div>
            <div class="layui-form-item">
                <button class="layui-btn layui-btn-fluid layui-bg-blue"  lay-submit lay-filter="login">登 录</button>
            </div>
        </div>
    </div>

<div th:replace="common/script::footer"></div>
<script th:inline="javascript">
    layui.config({
        base: '/static/layuiadmin/' //静态资源所在路径
    }).extend({
        index: 'lib/index' //主入口模块
    }).use(['index', 'user'], function(){
        let $ = layui.$,
            form = layui.form;
        // 初始化
        getImgCode();
        form.render();
        //提交
        form.on('submit(login)', function(obj) {
            // 打开loading
            let loading = layer.load(0, {
                shade: false,
                time: 2 * 1000
            });
            // 禁止重复点击按钮
            $('.layui-btn').attr("disabled",true);
            //请求登入接口
            $.ajax({
                type: 'POST',
                url:  ctx + '/login',
                data: obj.field,
                dataType: 'json',
                success: function(result) {
                    if (result.code === 200) {
                        layer.msg('登入成功', {
                             icon: 1
                            ,time: 1000
                        }, function(){
                            window.location.href = '/';
                        });
                    } else {
                        layer.msg(result.message);
                        // 刷新验证码
                        getImgCode();
                        // 关闭loading
                        layer.close(loading);
                        // 开启点击事件
                        $('.layui-btn').attr("disabled", false);
                    }
                }
            });
        });
        $("#codeImg").on('click', function() {
            // 添加验证码
            getImgCode();
        });
        $(document).keydown(function (e) {
            if (e.keyCode === 13) {
                $('.layui-btn').click();
            }
        });
        // 解决session过期跳转到登录页并跳出iframe框架
        $(document).ready(function () {
            if (window != top) {
                top.location.href = location.href;
            }
        });
    });
    /**
     * 获取验证码
     */
    function getImgCode() {
        let url = ctx + '/getImgCode';
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = "blob";
        xhr.onload = function() {
            if (this.status === 200) {
                let blob = this.response;
                document.getElementById("codeImg").src = window.URL.createObjectURL(blob);
            }
        }
        xhr.send();
    }
</script>
</body>
</html>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第1章 理解知识图谱(一)

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…

vue3默认插槽、具名插槽以及作用域插槽实现父子组件通信

默认插槽与具名插槽 父组件 <template><div><h1>我是父组件</h1><child><div style"color: red">我是从父元素插入的值(默认插槽)</div><template #juming><div style"color: green">我是从父元素…

【沁恒蓝牙mesh】CH58x 将RTC时钟切换为LSE外部低速时钟

本文主要记录了【沁恒蓝牙mesh】CH58x 如何将RTC时钟切换为外部时钟 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是喜欢记录零碎知识点的小菜鸟。&#x1f60e;&#x1f4dd; 个人主页&#xff1a;欢迎访问我的 Ethernet_Comm 博客主页&#x1f525;&#x1f389;…

SpringCloud之服务网关Gateway组件使用——详解

目录 一、网关介绍 1.什么是服务网关 2. 为什么需要网关 3.网关组件在微服务中架构 二、服务网关组件 1. zuul 1.x 2.x(netflix 组件) 1.1 zuul版本说明 2. gateway (spring) 2.1 特性 2.2 开发网关动态路由 2.2.1.创建项目引入网关依赖 2.2.2 快捷方式配置路由 2.2…

基于HTML+CSS+JavaScript的登录注册界面设计

一、界面效果: 二、HTML代码: 登录注册html: 登录成功html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title></title> </head> <body> <h1>登录成功!</h1> </body> <…

群晖NAS基础设置

群晖NAS基础设置 最近一直在玩群晖NAS系统&#xff0c;有一些基础的配置跟大家分享一下 开启ssh登录 1.开启方法 控制面板—>终端和SNMP—>终端机 2.使用ssh软件登录 这里我用SecureCRT登录 进入ssh 3.进入root用户 starstar-nas:~$ sudo su -l root Password: ro…

【学习草稿】pid控制基础实现--往水桶注水

pid 1&#xff09;非常通俗易懂的PID控制&#xff08;1&#xff09;https://zhuanlan.zhihu.com/p/37515841 球场上运动至指定地点&#xff08;比例控制&#xff09;&#xff1a;有图【很直观的帮助理解】&有文字分析 2&#xff09;初识PID-搞懂PID概念 https://zhuanlan.…

CMake构建一个转换为3d tile的开源代码成功

之前CMake构建一个转换为3d tile的开源代码&#xff0c;生成解决方案之后&#xff0c;从VS2019打开&#xff1b; 总是报一个错误&#xff0c;跟 mocs_compilation_Debug.cpp 这个QT相关文件有关&#xff0c;它生成的obj&#xff0c;总是报模块计算机x64和目标计算机x86冲突&am…

HTTP/3 为什么正迅速崛起

超文本传输协议&#xff08;HTTP&#xff09;作为互联网的基石&#xff0c;一直在网页加载、视频流传输、应用获取数据等方方面面发挥重要作用。 去年&#xff0c;负责定义互联网技术的互联网工程任务组&#xff08;IETF&#xff09;将该协议的最新版本 HTTP/3 定为标准。在此…

leetcode:495. 提莫攻击

一、题目 链接&#xff1a;495. 提莫攻击 - 力扣&#xff08;LeetCode&#xff09; 函数原型&#xff1a;int findPoisonedDuration(int* timeSeries, int timeSeriesSize, int duration) 二、思路 遍历数组timeSeries&#xff0c;如果 元素值duration < 下一元素值 &#x…

反射、枚举以及lambda表达式

1. 反射 1.1 定义 java的.class文件在运行时会被编译为一个Class对象&#xff0c;既然是对象&#xff0c;那么我们就可以通过一定的方式取到这个对象&#xff0c;然后对于这个对象进行一系列操作&#xff08;改变原本类的属性、方法&#xff09;。 这个操作就是反射&#xf…

PyQt6 QPlainTextEdit纯文本控件

​锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计28条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话…

Nodejs+vue基于微信小程序的高校餐厅食品留样管理系统uniapp

任何系统都要遵循系统设计的基本流程&#xff0c;本系统也不例外&#xff0c;同样需要经过市场调研&#xff0c;需求分析&#xff0c;概要设计&#xff0c;详细设计&#xff0c;编码&#xff0c;测试这些步骤&#xff0c;基于nodejs小程序技术设计并实现了小程序。采用B/S结构,…

Vue中的过滤器了解吗?过滤器的应用场景有哪些?

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue-filter过滤器 目录 一、是什么 二、如何用 定义filter 小结&#xff1a; 三、应用场景 四…

[Linux]进程创建➕进程终止

文章目录 1.再谈fork()函数1.1fork()创建子进程 OS都做了哪些工作?1.2对上述问题的理解1.3写时拷贝进行父子进程分离的优势1.4了解eip寄存器和pc1.5了解进程的上下文数据1.6对计算机组成的理解1.7fork常规用法1.8fork调用失败的原因 2.进程终止2.1进程终止时操作系统要做的工作…

在Anaconda中用命令行安装环境以及安装包

一、下载Anaconda 下载地址 二、创建环境 1. 打开Anaconda命令行 2.创建环境 conda create -n 环境名称 python3.10(需要的python版本号) 3.激活环境 activate 环境名4.下载安装包 pip install 模块名 -i https://pypi.tuna.tsinghua.edu.cn/simple5.下载torch 官网&…

线程池在Java中的应用实践

摘要&#xff1a;在实际业务场景中&#xff0c;线程池发挥着重要作用。本文将详细解答在高并发、任务执行时间短、并发不高、任务执行时间长以及并发高、业务执行时间长的业务场景下&#xff0c;如何使用线程池进行优化。 一、高并发、任务执行时间短的业务场景 在高并发、任务…

AI 重构工业制造的故事 我们从大模型开始讲起

在数字化浪潮的推动下&#xff0c;工业制造领域正经历着一场前所未有的变革。人工智能&#xff08;AI&#xff09;作为这场变革的关键推动者之一&#xff0c;正以惊人的速度颠覆传统制造业。而大模型作为AI时代最先进的科技工具之一&#xff0c;或将成为引领这场变革的利器&…

js 获取数组的最大值与最小值

let arr [1, 2, 5, 8, 10, 100, -1] 1. 使用Math的静态方法max/min Math.max()函数返回给定的一组数中的最大值。 它的语法&#xff1a;Math.max(value1[, value2, ...]) 使用此方法&#xff0c;需要注意&#xff0c;如果没有参数的话&#xff0c;则返回-Infinity。如果有任一…

走近“中国深度-深海探索主题展”,带你探索深海远洋

深海&#xff0c;自古以来给予了人类无限的遐想&#xff0c;随着时代的进步和科学的发展&#xff0c;海洋的神秘面纱被一点点揭开&#xff0c;呈现在我们面前的是一个资源宝库&#xff0c;“可上九天揽月&#xff0c;可下五洋捉鳖”已经成为现实&#xff0c;21世纪是海洋的世纪…