整合SpringSecurity

目录

前言

数据库设计

用户表

角色表

用户角色表

权限表

角色权限表

插入数据

表的实体类

用户表实体类

角色表实体类

权限表实体类

mapper层接口

UserMapper

RoleMapper

AuthorityMapper

封装登录信息

统一响应结果

上下文相关类

jwt令牌工具类

依赖导入

属性类

yml配置文件

jwt令牌工具类

SpringContextUtils工具类 

实现接口UserDetailsService

登录接口LoginController

自定义token验证过滤器

配置过滤器链

SecurityConfig完整配置

测试接口

开始测试

登录测试

权限测试

测试管理员权限访问

测试用户权限访问

总结


前言

接着《初识SpringSecurity》来看如何在项目中整合SpringSecurity这个安全框架。

https://blog.csdn.net/qq_74312711/article/details/134978245?spm=1001.2014.3001.5501

上次我们是将用户添加到内存中,实际开发中肯定是要存储在数据库里的。先来看数据库是如何设计的,以及如何把用户信息交给Security处理。

数据库设计

用户表

存放用户信息,主要是存放用户名和密码。

create table if not exists tb_user
(
    id       bigint auto_increment comment '主键'
        primary key,
    username varchar(16) not null comment '用户名',
    password varchar(64) not null comment '密码',
    constraint username
        unique (username)
)
    comment '用户表';

角色表

存放角色信息,角色是用户的身份。

create table if not exists tb_role
(
    id   bigint auto_increment comment '主键'
        primary key,
    role varchar(8) not null comment '角色',
    constraint role
        unique (role)
)
    comment '角色表';

用户角色表

用户和角色的关系:一个用户可以有多个角色身份,一个角色身份可以有多个用户对应。

create table if not exists tb_user_role
(
    id       bigint auto_increment comment '主键'
        primary key,
    username varchar(16) not null comment '用户名;不唯一',
    role     varchar(8)  not null comment '角色;不唯一'
)
    comment '用户角色表';

权限表

存放权限信息,角色拥有权限。

create table if not exists tb_authority
(
    id        bigint auto_increment comment '主键'
        primary key,
    authority varchar(16) not null comment '权限',
    constraint authority
        unique (authority)
)
    comment '权限表';

角色权限表

角色和权限的关系:一个角色可以拥有多个权限,一个权限可以被多个角色拥有。

create table if not exists tb_role_authority
(
    id        bigint auto_increment comment '主键'
        primary key,
    role      varchar(8)  not null comment '角色;不唯一',
    authority varchar(16) not null comment '权限;不唯一'
)
    comment '角色权限表';

插入数据

注意密码不能明文存储,要先经过编码处理。

@Test
void getEncodePassword() {
    String password = "123abc";
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    String encode = bCryptPasswordEncoder.encode(password);
    System.out.println(encode); // $2a$10$3OF9ij55dB7X2ffXby16Qu8n6Y96NV.RtHcza4vWO1EjoFO2JrsiW
}
insert into tb_user (id, username, password)
values (null, '艾伦', '$2a$10$3OF9ij55dB7X2ffXby16Qu8n6Y96NV.RtHcza4vWO1EjoFO2JrsiW');

insert into tb_role (id, role)
values (null, '管理员'),
       (null, '用户');

insert into tb_user_role (id, username, role)
values (null, '艾伦', '管理员'),
       (null, '艾伦', '用户');

insert into tb_authority (id, authority)
values (null, '权限1'),
       (null, '权限2'),
       (null, '权限3');

insert into tb_role_authority (id, role, authority)
values (null, '管理员', '权限1'),
       (null, '管理员', '权限2'),
       (null, '管理员', '权限3'),
       (null, '用户', '权限1'),
       (null, '用户', '权限2');

表的实体类

用户表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class UserEntity {
    private Long id;
    private String username;
    private String password;
}

角色表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class RoleEntity {
    private Long id;
    private String role;
}

权限表实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class AuthorityEntity {
    private Long id;
    private String authority;
}

mapper层接口

UserMapper

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    UserEntity selectUserByUsername(String username);
}
<?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="com.example.mapper.UserMapper">
    <!--查询用户-->
    <select id="selectUserByUsername" resultType="UserEntity">
        select id, username, password
        from tb_user
        where username = #{username};
    </select>
</mapper>

RoleMapper

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface RoleMapper {
    List<RoleEntity> selectRoleByUsername(String username);
}
<?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="com.example.mapper.RoleMapper">
    <!--查询角色-->
    <select id="selectRoleByUsername" resultType="RoleEntity">
        select tr.id, tr.role
        from tb_user_role tur
                 left join tb_role tr on tur.role = tr.role
        where tur.username = #{username};
    </select>
</mapper>

AuthorityMapper

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface AuthorityMapper {
    List<AuthorityEntity> selectAuthorityByRole(String role);
}
<?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="com.example.mapper.AuthorityMapper">
    <!--查询权限-->
    <select id="selectAuthorityByRole" resultType="AuthorityEntity">
        select ta.id, ta.authority
        from tb_role_authority tra
                 left join tb_authority ta on tra.authority = ta.authority
        where tra.role = #{role};
    </select>
</mapper>

封装登录信息

登录请求必须要提供用户名、密码和角色,后面都会用到。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class UserLogin {
    private String username;
    private String password;
    private String role;
}

统一响应结果

import lombok.Data;

import java.io.Serializable;

@Data
public class Result<T> implements Serializable {

    // 响应码:1代表成功,0代表失败
    private Integer code;
    // 提示信息
    private String message;
    // 响应数据
    private T data;

    public static <T> Result<T> success() {
        Result<T> result = new Result<>();
        result.code = 1;
        return result;
    }

    public static <T> Result<T> success(T object) {
        Result<T> result = new Result<>();
        result.code = 1;
        result.data = object;
        return result;
    }

    public static <T> Result<T> error(String message) {
        Result<T> result = new Result<>();
        result.code = 0;
        result.message = message;
        return result;
    }
}

上下文相关类

ThreadLocal线程局部变量,将信息放入上下文,后面要用可以直接取出。

public class BaseContext {
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void setContext(String context) {
        threadLocal.set(context);
    }

    public static String getContext() {
        return threadLocal.get();
    }

    public static void removeContext() {
        threadLocal.remove();
    }
}

jwt令牌工具类

依赖导入

jwt令牌依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

配置处理器依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

以下依赖必须导入,否则jwt令牌用不了。 

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>
<dependency>
    <groupId>javax.activation</groupId>
    <artifactId>activation</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.3</version>
</dependency>

属性类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "token.jwt")
public class JwtTokenProperties {
    // 签名密钥
    private String signingKey;

    // 有效时间
    private Long expire;
}

yml配置文件

token:
  jwt:
    signing-key: jwt-token-signing-key #签名密钥
    expire: 7200000 #有效时间

jwt令牌工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class JwtTokenUtils {

    private final JwtTokenProperties jwtTokenProperties;

    public String getJwtToken(Map<String, Object> claims) {

        String signingKey = jwtTokenProperties.getSigningKey();
        Long expire = jwtTokenProperties.getExpire();

        return Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signingKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
    }

    public Claims parseJwtToken(String jwtToken) {

        String signingKey = jwtTokenProperties.getSigningKey();

        return Jwts.parser()
                .setSigningKey(signingKey)
                .parseClaimsJws(jwtToken)
                .getBody();
    }
}

SpringContextUtils工具类 

SpringContextUtils工具类用于在过滤器中获取Bean,因为在过滤器中无法初始化Bean组件,所以使用上下文获取。

import jakarta.annotation.Nonnull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        if (applicationContext == null) {
            return null;
        }
        return (T) applicationContext.getBean(name);
    }
}

实现接口UserDetailsService

实现UserDetailsService接口,重写loadUserByUsername方法。方法返回一个User对象,即为UserDetails对象。我们将用户的信息封装到User对象中,返回给Security处理。

import lombok.RequiredArgsConstructor;
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.stereotype.Service;

import java.util.List;
import java.util.StringJoiner;

@Service
@RequiredArgsConstructor
public class UserLoginService implements UserDetailsService {

    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final AuthorityMapper authorityMapper;

    @Override
    public UserDetails loadUserByUsername(String username) {

        // 查询用户
        UserEntity userEntity = userMapper.selectUserByUsername(username);
        if (userEntity == null) {
            throw new RuntimeException("用户不存在");
        }

        // 获取用户登录身份role
        String role = BaseContext.getContext();

        List<RoleEntity> roles = roleMapper.selectRoleByUsername(username);
        // 判断用户是否有role身份
        boolean flag = true;
        for (RoleEntity r : roles) {
            if (r.getRole().equals(role)) {
                flag = false;
                break;
            }
        }
        if (flag) {
            throw new RuntimeException("用户" + username + "没有" + role + "身份");
        }

        // 查询角色权限
        List<AuthorityEntity> authorities = authorityMapper.selectAuthorityByRole(role);
        // 权限之间用","分隔
        StringJoiner stringJoiner = new StringJoiner(",", "", "");
        authorities.forEach(authority -> stringJoiner.add(authority.getAuthority()));

        return new User(userEntity.getUsername(), userEntity.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(stringJoiner.toString())
        );
    }
}

登录接口LoginController

将角色信息放入上下文中,在UserLoginService中会用到角色信息。

import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;

@RestController
@RequiredArgsConstructor
public class LoginController {

    private final UserLoginService userLoginService;
    private final JwtTokenUtils jwtTokenUtils;

    @PostMapping("/login")
    public Result<String> login(@RequestBody UserLogin userLogin) {
        try {

            // 将登录用户角色放入上下文
            BaseContext.setContext(userLogin.getRole());

            UserDetails userDetails = userLoginService.loadUserByUsername(userLogin.getUsername());
            // 获取用户权限
            StringJoiner authorityString = new StringJoiner(",", "", "");
            Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                authorityString.add(authority.getAuthority());
            }

            Map<String, Object> claims = new HashMap<>();
            claims.put("username", userLogin.getUsername());
            claims.put("role", userLogin.getRole());
            claims.put("authorityString", authorityString.toString());
            String jwtToken = jwtTokenUtils.getJwtToken(claims);

            return Result.success(jwtToken);
        } catch (Exception e) {
            return Result.error(e.getMessage());
        }
    }
}

自定义token验证过滤器

注意:

1. 在过滤器中无法初始化Bean组件

2. 在过滤器中抛出的异常无法被全局异常处理器捕获

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException {
        try {
            // 获取请求路径
            String url = request.getRequestURL().toString();

            // 为登录请求放行
            if (url.contains("/login")) {
                filterChain.doFilter(request, response);
                return; // 结束方法
            }

            // 获取请求头中的token
            String jwtToken = request.getHeader("token");
            if (!StringUtils.hasLength(jwtToken)) {
                // token不存在,交给其他过滤器处理
                filterChain.doFilter(request, response);
                return; // 结束方法
            }

            // 过滤器中无法初始化Bean组件,使用上下文获取
            JwtTokenUtils jwtTokenUtils = SpringContextUtils.getBean("jwtTokenUtils");
            if (jwtTokenUtils == null) {
                throw new RuntimeException();
            }

            // 解析jwt令牌
            Claims claims;
            try {
                claims = jwtTokenUtils.parseJwtToken(jwtToken);
            } catch (Exception e) {
                throw new RuntimeException();
            }

            // 获取用户信息
            String username = (String) claims.get("username"); // 用户名
            String authorityString = (String) claims.get("authorityString"); // 权限

            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    username, null,
                    AuthorityUtils.commaSeparatedStringToAuthorityList(authorityString)
            );

            // 将用户信息放入SecurityContext上下文
            SecurityContextHolder.getContext().setAuthentication(authentication);

            // 将用户名放入线程局部变量
            BaseContext.setContext(username);

            filterChain.doFilter(request, response);
        } catch (Exception e) {
            // 过滤器中抛出的异常无法被全局异常处理器捕获,直接返回错误结果
            response.setCharacterEncoding("utf-8");
            response.setContentType("application/json; charset=utf-8");
            String value = new ObjectMapper().writeValueAsString(Result.error("token验证失败"));
            response.getWriter().write(value);
        }
    }
}

配置过滤器链

注意给登录请求放行,要不然访问不了登录接口。

// 配置过滤器链
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
            .requestMatchers(HttpMethod.POST, "/login").permitAll() // 登录请求放行
            .requestMatchers(HttpMethod.GET, "/test1").hasAnyAuthority("权限1", "权限2")
            .requestMatchers(HttpMethod.GET, "/test2").hasAuthority("权限3")
    );
    httpSecurity.authenticationProvider(authenticationProvider());

    // 禁用登录页面
    httpSecurity.formLogin(AbstractHttpConfigurer::disable);
    // 禁用登出页面
    httpSecurity.logout(AbstractHttpConfigurer::disable);
    // 禁用session
    httpSecurity.sessionManagement(AbstractHttpConfigurer::disable);
    // 禁用httpBasic
    httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
    // 禁用csrf保护
    httpSecurity.csrf(AbstractHttpConfigurer::disable);

    // 通过上下文获取AuthenticationManager
    AuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");
    // 添加自定义token验证过滤器
    httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);

    return httpSecurity.build();
}

SecurityConfig完整配置

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final UserDetailsService userDetailsService;

    // 加载用户信息
    @Bean
    public UserDetailsService userDetailsService() {
        return userDetailsService;
    }

    // 身份验证管理器
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    // 处理身份验证
    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        return daoAuthenticationProvider;
    }

    // 密码编码器
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 配置过滤器链
    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
                .requestMatchers(HttpMethod.POST, "/login").permitAll() // 登录请求放行
                .requestMatchers(HttpMethod.GET, "/test1").hasAnyAuthority("权限1", "权限2")
                .requestMatchers(HttpMethod.GET, "/test2").hasAuthority("权限3")
        );
        httpSecurity.authenticationProvider(authenticationProvider());

        // 禁用登录页面
        httpSecurity.formLogin(AbstractHttpConfigurer::disable);
        // 禁用登出页面
        httpSecurity.logout(AbstractHttpConfigurer::disable);
        // 禁用session
        httpSecurity.sessionManagement(AbstractHttpConfigurer::disable);
        // 禁用httpBasic
        httpSecurity.httpBasic(AbstractHttpConfigurer::disable);
        // 禁用csrf保护
        httpSecurity.csrf(AbstractHttpConfigurer::disable);

        // 通过上下文获取AuthenticationManager
        AuthenticationManager authenticationManager = SpringContextUtils.getBean("authenticationManager");
        // 添加自定义token验证过滤器
        httpSecurity.addFilterBefore(new JwtAuthenticationFilter(authenticationManager), UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }
}

测试接口

用户有权限访问/test1,没有权限访问/test2。管理员有权限访问/test1和/test2。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/test1")
    public String demo1() {
        System.out.println("test1访问成功!");
        return "test1访问成功!";
    }

    @GetMapping("/test2")
    public String demo2() {
        System.out.println("test2访问成功!");
        return "test2访问成功!";
    }
}

开始测试

登录测试

测试成功

权限测试

测试管理员权限访问

/test1访问成功

/test2测试成功

测试用户权限访问

用户登录

/test1访问成功

/test2访问失败

可以看到测试的结果都是正确的,说明成功地实现了权限控制。

总结

以上就是如何在项目中整合SpringSecurity的基本用法,我们再来看一下官方的描述:

Spring Security是一个强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正力量在于它可以多么容易地扩展以满足自定义需求。

强大且高度可定制就是SpringSecurity受欢迎的关键,我们还可以对以上的案例进行优化。例如,我们不将用户角色的权限放在token令牌中,而是放在Redis中。在进行token验证的时候,解析出用户名,拿用户名去Redis中找对应的权限。又或者我们可以自定义处理器,处理用户未登录(未携带token),处理用户权限不足等。

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

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

相关文章

Win11极速安装Tensorflow-gpu+CUDA+cudnn

文章目录 0.pip/conda换默认源1.Anacondapython虚拟环境2.安装CUDA以及cudnn测试tensorflow的GPU版本安装成功的办法参考文献 不要使用官网版本&#xff0c;直接使用conda版本&#xff0c;有对应的包&#xff0c;安装很方便 0.pip/conda换默认源 为了高效下载&#xff0c;建议…

nodejs微信小程序+python+PHP邮件过滤系统的设计与实现-计算机毕业设计推荐

邮件过滤系统综合网络空间开发设计要求。该系统主要设计并完成了管理过程中的用户登录、个人信息修改、邮件信息、垃圾箱、意见反馈、论坛等功能。该系统操作简便&#xff0c;界面设计简洁&#xff0c;不但可以基本满足本行业的日常管理工作&#xff0c; 目的是将邮件过滤通过网…

Flink系列之:自定义函数

Flink系列之&#xff1a;自定义函数 一、自定义函数二、概述三、开发指南四、函数类五、求值方法六、类型推导七、自动类型推导八、定制类型推导九、确定性十、内置函数的确定性十一、运行时集成十二、标量函数十三、表值函数十四、聚合函数十五、表值聚合函数 一、自定义函数 …

HttpRunner接口自动化测试框架

简介 HttpRunner是一款面向 HTTP(S) 协议的通用测试框架&#xff0c;只需编写维护一份 YAML/JSON 脚本&#xff0c;即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。 项目地址&#xff1a;GitHub - httprunner/httprunner: HttpRunner 是一个开源的 API/UI…

【MySQL表的增删改查】

目录&#xff1a; 前言表的增删改查Create(创建)1.插入插入测试插入否则更新 2.替换 Retrieve(查找)1.SELECT 列全列查找指定列查找查询字段为表达式字段重命名结果去重 2.WHERE条件英语不及格的同学及英语成绩&#xff08;<60&#xff09;语文成绩在 [80, 90] 分的同学及语…

RTOS队列的写入与读出

我们在stm32f103c8t6单片机上验证RTOS队列的写入与读出&#xff0c;利用stm32cube进行RTOS的配置。在选择TIM2当做RTOS的时钟&#xff0c;裸机的时钟源默认是 SysTick&#xff0c;但是开启 FreeRTOS 后&#xff0c;FreeRTOS会占用 SysTick &#xff08;用来生成1ms 定时&#x…

ChatGLM基于LangChain应用开发实践(一)

一、概述 在使用大模型&#xff08;LLM&#xff09;做应用开发时&#xff0c;LangChain是一个主流的开发框架&#xff0c;通过它来构建Agent&#xff0c;根据用户查询访问企业私有数据&#xff0c;调用自定义或者第三方工具库&#xff0c;然后再调用LLM&#xff0c;利用其推理…

qt-C++笔记之std::tostring()、.toStdString()、.toLocal8Bit().constData()的使用场景

qt-C笔记之std::tostring()、.toStdString()、.toLocal8Bit().constData()的使用场景 参考博文&#xff1a;C笔记之system()用于在Qt中执行系统命令的习惯 code review! 注&#xff1a;之所以记录该笔记&#xff0c;是因为在Qt中自己经常使用C语言的int system( const char …

c++11--左值,右值,移动语义,引用折叠,模板类型推断,完美转发

1.移动语义 移动构造和移动赋值均属于移动语义范畴。 移动语义的实现依赖于右值概念&#xff0c;右值引用。 1.1.一个移动构造的实例 #include <iostream> using namespace std; class HasPtrMem{ public:HasPtrMem():d(new int(3)){cout << "Construct: &qu…

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解

信号与线性系统翻转课堂笔记4——连续LTI系统的微分方程模型与求解 The Flipped Classroom4 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#x…

gitee提交代码步骤介绍(含git环境搭建)

1、gitee官网地址 https://gitee.com; 2、Windows中安装git环境 参考博客&#xff1a;《Windows中安装Git软件和TortoiseGit软件》&#xff1b; 3、设置用户名和密码 这里的用户名和密码就是登录gitee网站的用户名和密码如果设置错误&#xff0c;可以在Windows系统的“凭据管理…

Kubernetes (k8s) 快速认知

应用部署方式 传统部署时代 早期的时候&#xff0c;各个组织是在物理服务器上运行应用程序。缺点 资源分配问题&#xff1a; 无法限制在物理服务器中运行的应用程序资源使用 维护成本问题&#xff1a; 部署多个物理机&#xff0c;维护许多物理服务器的成本很高 虚拟化部署时…

论文修改润色算学术不端吗 快码论文

大家好&#xff0c;今天来聊聊论文修改润色算学术不端吗&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;论文修改润色是否算学术不端&#xff1f;专业软件…

U-boot启动流程与加载内核过程

目录 一、U-boot启动过程流程图二、U-boot启动过程函数简单注释 本篇文章梳理了一下对正点原子的驱动开发教程中u-boot启动流程的梳理&#xff0c;制作了一份流程图&#xff0c;并简单的记录了一下各函数的作用&#xff0c;方便回头翻阅。 一、U-boot启动过程流程图 二、U-boot…

git-lfs基本知识讲解

目录 1. 基本知识2. 安装 1. 基本知识 git-lfs 是 Git Large File Storage 的缩写&#xff0c;是 Git 的一个扩展&#xff0c;用于处理大文件的版本控制。 它允许你有效地管理和存储大型二进制文件&#xff0c;而不会使 Git 仓库变得过大和不稳定。以下是一些与 git-lfs 相关…

机器学习——自领域适应作业

任务 游戏里面的话有很多跟现实不一样的情况。 想办法让中间的特征更加的接近&#xff0c;让feat A适应feat B&#xff0c;产生相对正常的输出。 在有标签数据和没有数据的上面进行训练&#xff0c;并能预测绘画图像。 数据集 训练5000张总数&#xff0c;每类有500张测试100…

Jmeter实现服务器端后台接口性能测试!

实现目的 在进行服务器端后台接口性能测试时&#xff0c;需要连接到Linux服务器端&#xff0c;然后通过命令调用socket接口&#xff0c;这个过程就需要用到jmeter的SSH Command取样器实现了。 脚本实现 设置CSV Data Set ConFig配置元件&#xff0c;参数化测试数据 设置SSH…

【线性代数】期末速通!

1. 行列式的性质 1.1 求一个行列式的值 特殊地&#xff0c;对角线左下全为0&#xff0c;结果为对角线乘积。行 r 列 c 1.2 性质 某行&#xff08;列&#xff09;加上或减去另一行&#xff08;列&#xff09;的几倍&#xff0c;行列式不变某行&#xff08;列&#xff09;乘 …

118. 杨辉三角

给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRows 1 输出: [[1]]提示: 1 <…

Spring Boot 3 + Vue 3 整合 WebSocket (STOMP协议) 实现实时通信

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…