整合SpringSecurity

news2024/11/24 6:08:10

目录

前言

数据库设计

用户表

角色表

用户角色表

权限表

角色权限表

插入数据

表的实体类

用户表实体类

角色表实体类

权限表实体类

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.coloradmin.cn/o/1317419.html

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

相关文章

[Verilog] Verilog 基本格式和语法

主页&#xff1a; 元存储博客 全文 3000 字 文章目录 1. 声明格式1.1 模块声明1.2 输入输出声明1.3 内部信号声明1.4 内部逻辑声明1.5 连接声明1.6 数据类型声明1.7 运算符和表达式1.8 控制结构 2. 书写格式2.1 大小写2.2 换行2.3 语句结束符2.4 注释2.5 标识符2.6 关键字 1. 声…

docker入门小结

docker是什么&#xff1f;它有什么优势&#xff1f; 快速获取开箱即用的程序 docker使得所有的应用传输就像我们日常通过聊天工具文件传输一样&#xff0c;发送方将程序传输到超级码头而接收方也只需通过超级码头进行获取即可&#xff0c;就像一只鲸鱼拖着货物来回运输一样。…

linux一次性调度执行_at命令

........................................................................................................................................................... 9.1 一次性调度执行 Schedule one-time tasks with at. ............................................…

早上好,我的leetcode 【hash】(第二期)

写在前面&#xff1a;坚持才是最难的事情 C代码还是不方便写&#xff0c;改用python了&#xff0c;TAT 文章目录 1.两数之和49. 字母异位词分组128.最长连续序列 1.两数之和 你好&#xff0c;梦开始的地方~ https://leetcode.cn/problems/two-sum/description/?envTypestudy…

n维随机变量、n维随机变量的分布函数

设随机试验E的样本空间是&#xff0c;其中表示样本点。 设是定义在上的随机变量&#xff0c;由它们构成一个n维向量&#xff0c;叫做n维随机向量&#xff0c;也叫n维随机变量。 对于任意n个实数&#xff0c;n元函数 称为n维随机变量的分布函数&#xff0c;也叫联合分布函数。

qt 标准对话框的简单介绍

qt常见的标准对话框主要有,标准文件对话框QFileDialog,标准颜色对话框QColorDialog,标准字体对话框QFontDialog,标准输入对话框QInputDialog,标准消息框QMessageBox...... 1. 标准文件对话框QFileDialog,使用函数getOpenFileName()获取用户选择的文件. //qt 函数getOpenFileN…

geolife笔记:比较不同轨迹相似度方法

1 问题描述 在geolife 笔记&#xff1a;将所有轨迹放入一个DataFrame-CSDN博客中&#xff0c;已经将所有的轨迹放入一个DataFrame中了&#xff0c;我们现在需要比较&#xff0c;在不同的轨迹距离度量方法下&#xff0c;轨迹相似度的效果。 这里采用论文笔记&#xff1a;Deep R…

arthas 线上排查问题基本使用

一、下载 [arthas下载地址]: 下载完成 解压即可使用 二、启动 java -Dfile.encodingUTF-8 -jar arthas-boot.jar 如果直接使用java -jar启动 可能会出现乱码 三、使用 启动成功之后 arthas会自动扫描当前服务器上的jvm进程 选择需要挂载的jvm进程 假如需要挂在坐标【1】的…

【MySQL】(DDL) 数据类型 和 表操作-修改 删除

目录 介绍&#xff1a; 1.数值类型 3.日期类型 修改表&#xff1a; 示列&#xff1a; 介绍&#xff1a; 在之前建表语句内&#xff0c;用到了 int cvarchar &#xff0c;那么在mysql内除了 以上的数据类型 还有那些常见数据类型 mysql 中的数据类型有很多种 &#xff0c…

机器学习 | 决策树 Decision Tree

—— 分而治之&#xff0c;逐个击破 把特征空间划分区域 每个区域拟合简单模型 分级分类决策 1、核心思想和原理 举例&#xff1a; 特征选择、节点分类、阈值确定 2、信息嫡 熵本身代表不确定性&#xff0c;是不确定性的一种度量。 熵越大&#xff0c;不确定性越高&#xff0c;…

maui中实现加载更多 RefreshView跟ListView 跳转到详情页 传参(3)

效果如图 这里的很多数据是通过传参过来的的。 代码 例表页加入跳转功能&#xff1a; <ListView ItemsSource"{Binding Items}" ItemAppearing"OnItemAppearing" ItemTapped"OnItemTapped" RowHeight"70" Margin"20"…

【C++11特性篇】一文助小白轻松理解 C++中的【左值&左值引用】【右值&右值引用】

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.【左值&#xff06;左值引用】&…

【漏洞复现】CVE-2023-36076:smanga漫画阅读系统 远程命令执行 漏洞复现 附POC 附SQL注入和任意文件读取

漏洞描述 无需配置,docker直装的漫画流媒体阅读工具。以emby plex为灵感,为解决漫画阅读需求而开发的漫画阅读器。在windows环境部署smanga安装环境面板,首先安装小皮面板,下载smanga项目,导入数据库,登录smanga,windows部署smanga。 /php/manga/delete.php接口处存在未…

arthas获取spring bean

参考文章 arthas获取spring bean 写一个工具Util package com.example.lredisson.util;import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import o…

工具在手,创作无忧:一键下载安装Auto CAD工具,让艺术创作更加轻松愉悦!

不要再浪费时间在网上寻找Auto CAD的安装包了&#xff01;因为你所需的一切都可以在这里找到&#xff01;作为全球领先的设计和绘图软件&#xff0c;Auto CAD为艺术家、设计师和工程师们提供了无限的创作潜力。不论是建筑设计、工业设计还是室内装饰&#xff0c;Auto CAD都能助…

ES-组合与聚合

ES组合查询 1 must 满足两个match才会被命中 GET /mergeindex/_search {"query": {"bool": {"must": [{"match": {"name": "liyong"}},{"match_phrase": {"desc": "liyong"}}]}}…

Next.js 学习笔记(一)——安装

安装 系统要求&#xff1a; Node.js 18.17 或更高版本支持 macOS、Windows&#xff08;包括 WSL&#xff09;和 Linux 自动安装 我们建议使用 create-next-app 启动一个新的 Next.js 应用程序&#xff0c;该应用程序会自动为你设置所有内容。要创建项目&#xff0c;请运行&…

HPV治疗期间如何预防重复感染?谭巍主任讲述具体方法

众所周知&#xff0c;人乳头瘤病毒(HPV)是一种常见的性传播疾病&#xff0c;感染后可能会引起生殖器疣、宫颈癌等疾病。在治疗期间&#xff0c;预防重复感染非常重要。今日将介绍一些预防HPV重复感染的方法。 1. 杜绝不洁性行为 在治疗期间&#xff0c;患者应该避免与感染HPV…

SQL、Jdbc、JdbcTemplate、Mybatics

数据库&#xff1a;查询&#xff08;show、select&#xff09;、创建&#xff08;create)、使用(use)、删除(drop)数据库 表&#xff1a;创建&#xff08;【字段】约束、数据类型&#xff09;、查询、修改&#xff08;alter *add&#xff09;、删除 DML&#xff1a;增加(inse…

R语言|分面中嵌入趋势线

简介 关于分面的推文&#xff0c;小编根据实际科研需求&#xff0c;已经分享了很多技巧。例如&#xff1a; 分面中添加不同表格 分面中添加不同的直线 基于分面的面积图绘制 分面中的细节调整汇总 基于分面的折线图绘制 最近科研中又遇到了与分面相关的需求&#xff1a;…