Spring Security——结合JWT实现令牌的验证与授权

目录

JWT(JSON Web Token)

项目总结

新建一个SpringBoot项目

pom.xml

PayloadDto

JwtUtil工具类

MyAuthenticationSuccessHandler(验证成功处理器)

JwtAuthenticationFilter(自定义token过滤器)

WebSecurityConfig配置类

WebSecurityController控制器

 index.html

项目测试


  • 本文以Spring Security项目为基础,此处只给出有关JWT的核心代码,其他代码可查看文章:http://t.csdnimg.cn/hLyKB
  • 参考文章:JWT 基础概念详解 | JavaGuide

JWT(JSON Web Token)

  • 基于token的认证方式相较于Session认证方式的好处:
    • 可以节省服务器端的开销,因为每个认证用户占一个Session对象是需要消耗服务器内存资源的,而token是在每次请求时传递给服务器端的
    • 当服务器端做集群部署时,基于token的认证方式也更容易扩展
    • 无须考虑CSRF,由于不同依赖cookie,所以采用token认证方式不会发生CSRF
    • 更适合于移动端,当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会更简单
  • JWT就是token的一种实现方式
  • JWT可以用于认证和信息交换,流程:
    • 在Web应用程序中,当用户成功登录后,服务器可以生成一个JWT并将其发送回客户端
    • 客户端存储该令牌,并在后续每次请求服务器接口时,都在请求报头中携带JWT
    • 服务器校验JWT签名,得到用户信息,如果验证通过,则根据授权规则返回前端请求的数据
  • JWT是一种用于在网络上安全传输信息的开放标准(RFC 7519),通过使用数字签名(或加密)来验证消息的发送者以及确保消息在传输过程中没有被篡改
  • JWT由三部分组成(用点号(.)分隔开)
    • Header(头部):是一个描述JWT元数据的JSON对象
      • 令牌的类型(JWT)
      • 正在使用的签名算法(如HMAC SHA256或者RSA)
    • Payload(载荷):是一个JSON对象,存放有效信息
      • 三种声明类型,关于用户和附加数据的陈述
      • 1、registered
        • iss(issuer):签发人
        • sub(subject):主题
        • aud(audience):受众
        • exp(expiration time):过期时间
        • nbf(not before):生效时间
        • iat(issued at):签发时间
        • jti(jwt id):JWT的唯一身份标识 
      • 2、public
      • 3、private
    • Signature(签名):对头部和载荷采用单向散列算法生成一个哈希码,以确保数据不会被篡改。签名用于验证消息在此过程中是否被更改,并且对于使用私钥签名的令牌还可以验证JWT的发送者是不是它所声称的
    • JWT=Header.Payload.Signature
  • 优点:
    • 简洁:由于其信息是以JSON对象的形式存储的,因此JWT是非常紧凑的
    • 自包含:JWT包含了所有必要的信息,因此不需要查询数据库来验证令牌
    • 可扩展性:由于其灵活的结构,JWT可以包含任意的额外信息
  • 缺点:
    • 信息是以Base64编码的形式存储的,所以不适合存储敏感信息,例如密码
    • 使用时要小心JWT被盗用或篡改
  • 应用场景:
    • 授权:一旦用户登录,每个后续请求就都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录(Single Sign On)是目前被广泛使用JWT的一个功能,因为它开销小,并且能够轻松地跨不同的域
    • 信息交换:可以在各方之间安全传输信息。因为可以对JWT进行签名(例如使用公钥或私钥对),所以可以确定发送者是特定的人。此外,由于签名是使用标头和有效载荷计算的,因此还可以验证内容是否被篡改

项目总结

  • 工作流程:

  • 1、用户注册和登录:

    • 用户通过注册接口提交注册信息,系统保存用户信息到数据库。
    • 用户通过登录接口提交用户名和密码,系统验证用户信息,成功后生成并返回JWT令牌。
  • 2、请求保护资源:

    • 客户端在每次请求时将JWT令牌放在请求头中。
    • JwtAuthenticationFilter 拦截请求,从请求头中提取JWT令牌,并通过 getAuthentication(token) 方法验证和解析令牌。
    • 若验证成功,将 UsernamePasswordAuthenticationToken 设置到 SecurityContextHolder 中,使当前线程拥有安全上下文。
    • 后续处理链和控制器可以通过安全上下文获取用户信息进行权限验证。
  • 3、安全考量:

    • JWT加密:使用HMAC算法对JWT进行签名,确保令牌的完整性和真实性。
    • 过期时间:设置JWT的有效期,减小令牌被盗用的风险。
    • 角色和权限:通过角色和权限控制资源访问,确保不同用户只能访问其权限范围内的资源。

新建一个SpringBoot项目

 项目的结构

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.12.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.study</groupId>
	<artifactId>jwt</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>jwt</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity5</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<!--hutool是一个Java工具类库-->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.5.7</version>
		</dependency>
		<!--jwt相关依赖-->
		<dependency>
			<groupId>com.nimbusds</groupId>
			<artifactId>nimbus-jose-jwt</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

PayloadDto

package com.study.jwt.dto;

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.util.List;

/**
 * 用于封装JWT中的有效载荷
 */
@Data
//这个注解单独应用时会生成 equals 和 hashCode 方法,callSuper = false 表示生成的方法不会调用父类的 equals 和 hashCode 方法
@EqualsAndHashCode(callSuper = false)
//用于生成 Builder 模式相关的代码。它会生成一个静态内部类 ExampleBuilder,使得创建对象更加简洁和灵活
@Builder
public class PayloadDto {
    private String sub;//主题
    private Long iat;//签发时间
    private Long exp;//过期时间
    private String jti;//JWT的ID
    private String username;//用户名称
    private List<String> authorities;//用户拥有的权限
}

JwtUtil工具类

package com.study.jwt.util;

import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.study.jwt.dto.PayloadDto;

import java.text.ParseException;
import java.util.Date;

/**
 * 工具类,用于提供生成和验证JWT的方法
 */
public class JwtUtil {
    //默认密钥
    public static final String DEFAULT_SECRET="mySecret";

    /**
     * 使用HMAC SHA-256签名算法
     * @param payloadStr 有效载荷
     * @param secret 密钥
     * @return JWS串
     */
    public static String generateTokenByHMAC(String payloadStr,String secret) throws JOSEException {
        //创建JWS头,设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();
        //将载荷信息封装到Payload中
        Payload payload = new Payload(payloadStr);
        //创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        //创建HMAC签名器
        JWSSigner jwsSigner = new MACSigner(secret);
        //签名
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    /**
     * 验证签名,提取有效载荷,以PayloadDto对象形式返回
     * @param token JWS串
     * @param secret 密钥
     * @return PayloadDto对象
     */
    public static PayloadDto verifyTokenByHMAC(String token,String secret) throws ParseException, JOSEException {
        //从token中解析JWS对象
        JWSObject jwsObject = JWSObject.parse(token);
        //创建HMAC验证器
        JWSVerifier jwsVerifier = new MACVerifier(secret);
        if (!jwsObject.verify(jwsVerifier)) {
            throw new JOSEException("token签名不合法!");
        }
        String payload = jwsObject.getPayload().toString();
        PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
        if (payloadDto.getExp() < new Date().getTime()){
            throw new JOSEException("token已过期!");
        }
        return payloadDto;
    }
}

MyAuthenticationSuccessHandler(验证成功处理器)

package com.study.jwt.config.handler;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JOSEException;
import com.study.jwt.dto.PayloadDto;
import com.study.jwt.util.JwtUtil;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

/**
 * 修改验证成功处理器,用户成功登录后,在响应报头中发送token
 */
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        // 获取认证主体信息
        Object principal = authentication.getPrincipal();

        // 用户登录成功后,在响应头中发送 token
        if(principal instanceof UserDetails){
            // 如果主体信息是用户详情实例
            UserDetails user=(UserDetails)principal;

            // 获取用户的权限信息
            Collection<? extends GrantedAuthority> authorities=authentication.getAuthorities();
            List<String> authoritiesList = new ArrayList<>(authorities.size());

            // 将用户权限列表转换为字符串列表
            authorities.forEach(authority -> {
                authoritiesList.add(authority.getAuthority());
            });

            // 定义 token 的创建时间和过期时间
            Date now = new Date();
            DateTime exp = DateUtil.offsetSecond(now, 60 * 60); // 1小时后过期

            // 创建 payload 数据传输对象
            PayloadDto payloadDto=PayloadDto.builder()
                    .sub(user.getUsername())
                    .iat(now.getTime())
                    .exp(exp.getTime())
                    .jti(UUID.randomUUID().toString())
                    .username(user.getUsername())
                    .authorities(authoritiesList)
                    .build();
            String token=null;
            try {
                // 生成 JWT token
                token= JwtUtil.generateTokenByHMAC(
                        //nimbus-jose-jwt所使用的HMAC SHA256算法所需密钥长度为256位(32字节),因此先用md5加密
                        JSONUtil.toJsonStr(payloadDto),
                        SecureUtil.md5(JwtUtil.DEFAULT_SECRET)
                );

                // 设置响应头和响应内容类型
                response.setHeader("Authorization",token);
                response.setContentType("application/json;charset=UTF-8");
                PrintWriter out = response.getWriter();
                out.write("登录成功!");
                out.close();
            } catch (JOSEException e) {
                e.printStackTrace();
            }
        }
    }
}

JwtAuthenticationFilter(自定义token过滤器)

package com.study.jwt.filter;

import cn.hutool.crypto.SecureUtil;
import com.nimbusds.jose.JOSEException;
import com.study.jwt.dto.PayloadDto;
import com.study.jwt.util.JwtUtil;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * JwtAuthenticationFilter是一个过滤器,拦截用户请求,验证token
 * 继承OncePerRequestFilter,保证在任何servlet容器上每次请求调度都能执行一次。
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    /**
     * 过滤器逻辑,提取和验证JWT,如果有效则设置认证信息。
     *
     * @param request     HttpServletRequest对象
     * @param response    HttpServletResponse对象
     * @param filterChain FilterChain对象
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader("Authorization"); // 从请求头中获取JWT
        if (token == null) {
            // 如果请求头中没有token,则直接放行
            filterChain.doFilter(request, response);
            return;
        }
        // 如果请求头中有token,则进行解析,并且设置认证信息
        try {
            // 验证并解析token,并将认证信息设置到SecurityContext中
            //它将经过认证的用户信息设置到当前的安全上下文中 (SecurityContext),从而使得后续的安全决策可以基于该用户的信息进行
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));
            filterChain.doFilter(request, response); // 继续执行过滤链
        } catch (ParseException | JOSEException e) {
            e.printStackTrace(); // 打印异常信息
        }
    }

    /**
     * 验证token,并解析token,返回以用户名和密码所表示的经过身份验证的主体的令牌。
     *
     * @param token JWT字符串
     * @return UsernamePasswordAuthenticationToken对象
     * @throws ParseException JSON解析异常
     * @throws JOSEException  JWT处理异常
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String token) throws ParseException, JOSEException {
        // 使用HMAC算法验证并解析JWT,获取PayloadDto对象
        PayloadDto payloadDto = JwtUtil.verifyTokenByHMAC(token, SecureUtil.md5(JwtUtil.DEFAULT_SECRET));
        String username = payloadDto.getUsername(); // 从Payload中获取用户名
        List<String> roles = payloadDto.getAuthorities(); // 从Payload中获取角色列表
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();

        // 将角色列表转换为Spring Security的SimpleGrantedAuthority集合
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role)));

        if (username != null) {
            return new UsernamePasswordAuthenticationToken(username, null, authorities);
        }
        return null; // 如果用户名为空,返回null
    }
}

WebSecurityConfig配置类

package com.study.jwt.config;

import com.study.jwt.filter.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity // 启用Spring Security的Web安全支持
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler; // 成功处理器注入
    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler; // 失败处理器注入
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    /**
     * 配置HTTP安全
     *
     * @param http HttpSecurity对象
     * @throws Exception 异常处理
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/home", "/login").permitAll() // 允许所有人访问
                .antMatchers("/admin/**").hasRole("ADMIN") // 只有具有 "ADMIN" 角色的用户才能访问以 "/admin/" 开头的路径
                .anyRequest().hasRole("USER") // 其他请求必须有USER角色
                .and()
            .formLogin()
                .loginPage("/login") // 指定登录页面
                .successHandler(authenticationSuccessHandler) // 登录成功处理器
                .failureHandler(authenticationFailureHandler) // 登录失败处理器
                .and()
            .logout() // 启用注销功能
                .and()
            // 添加自定义的 JWT 过滤器
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        http.csrf().disable(); // 禁用CSRF保护
    }

    /**
     * 配置密码编码器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(); // 使用BCrypt加密密码
    }
}

WebSecurityController控制器

package com.study.jwt.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * 1.将configure(HttpSecurity http)方法中设置的不同的URL映射到不同的页面
 * 2.方法返回的是视图名称,需要视图解析器将视图名称解析成实际的HTML文件
 * 然后访问url就可以跳转到HTML页面了,否则返回的只是一个字符串
 * 3.在application.properties配置文件中配置视图解析器,springboot已经默认配置好了,你不用写了
 */
@Controller
public class WebSecurityController {

    /**
     * 登录后跳转到home.html页面
     */
    @GetMapping("/home")
    public String home(){
        return "home";
    }

    /**
     * 登录页面
     */
    @GetMapping("/login")
    public String login(){
        return "login";//login.html
    }

    /**
     * 当访问/resource时,会重定向到/login,登录后才可以访问受保护的页面resource.html
     */
    @GetMapping("/resource")
    public String resource(){
        return "resource";//resource.html
    }

    @GetMapping("/admin/index")
    public String index(){
        return "index";//index.html
    }
}

 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
这是网站的首页!
</body>
</html>

项目测试

1、访问网址:http://localhost:8080/login

下面的Headers里的Authorization里的Value即为token,它被点号(.)分成三个部分,第一部分为Header,第二部分为Payload,第三部分为Signature

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1dGhvcml0aWVzIjpbIlJPTEVfVVNFUiIsIlJPTEVfQURNSU4iXSwiZXhwIjoxNzE4NDM4NDY1NDM3LCJpYXQiOjE3MTg0MzQ4NjU0MzcsImp0aSI6ImQyNDFmMDlkLTgwNzYtNDZhZC1hYTg0LTc3MjQxNTIyZjUyZSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.izNsanv4m-UJiIH23m8A2FvqWIvMfy0AEbleanxnMt8

2、访问网址:http://localhost:8080/resource

携带token进行后续访问:把登录时的token复制到Headers里的新增的Authorization里,访问受保护资源 

3、访问网址:http://localhost:8080/admin/index

该地址需要角色为“ADMIN”,所以需要使用第二个账号"admin"登录

如果不用特定账号登录,则返回403错误

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

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

相关文章

ThinkPHP5.0 apache服务器配置URL重写,index.php去除

本地环境wamp .htaccess文件代码 <IfModule mod_rewrite.c>Options FollowSymlinks -MultiviewsRewriteEngine onRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] </IfModule> 踩过这个坑&a…

第12章.STM32标准库简介

目录 0. 《STM32单片机自学教程》专栏 12.1 CMSIS 标准 12.2 STM32标准库文件结构 12.2.1 主结构 12.2.2 Libraries固件库文件 CMSIS文件夹 1.core_cm3.c&core_cm3.h 2.startup启动文件 3.Stm32f10x.h 4.system_stm32f10x.c&system_stm32f10…

解决用Three.js实现嘴型和语音同步时只能播放部分部位的问题 Three.js同时渲染播放多个组件变形动画的方法

前言 参考这篇文章ThreeJSChatGPT 实现前端3D数字人AI互动&#xff0c;前面搭后端、训练模型组内小伙伴都没有什么问题&#xff0c;到前端的时候&#xff0c;脸部就出问题了。看我是怎么解决的。 好文章啊&#xff0c;可惜百度前几个都找不到&#xff0c;o(╥﹏╥)o 问题情况 …

【C++提高编程-05】----C++之Deque容器实战

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

ubuntu vnc

如何在Ubuntu 18.04安装VNC | myfreax sudo apt install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils sudo apt install tigervnc-standalone-server tigervnc-common vncserver sudo apt install xfce4 xfce4-goodies xorg dbus-x11 x11-xserver-utils sudo apt ins…

用Python向Word文档添加页眉和页脚

用Python向Word文档添加页眉和页脚 添加页眉和页脚效果代码 添加页眉和页脚 在本文中&#xff0c;我们将用python向文档中添加页眉和页脚。 效果 添加前的文档&#xff1a; 添加页眉和页脚后&#xff1a; 代码 from docx import Documentdef add_header_footer(doc_path…

【数据结构】——常见排序

文章目录 一、 冒泡排序二、 选择排序三、插入排序四、 快速排序1. hoare版本2. 优化版本3. 前后指针法4. 非递归版本 五、 堆排序六、 希尔排序七、 归并排序1. 递归版本2. 非递归版本 八、 计数排序 在开始之前先准备一个交换数据的函数&#xff0c;排序会经常用到 //交换位置…

牛客热题:兑换零钱(一)

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;兑换零钱(一)题目链接方法一&am…

解读ROS功能包模块的步骤

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录 系列文章目录前言解读ROS功能包模块的步骤前言 认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长! 推荐开发经验及方法博客专栏: [https:/…

抢占人工智能行业红利,前阿里巴巴产品专家带你15天入门AI产品经理

前言 当互联网行业巨头纷纷布局人工智能&#xff0c;国家将人工智能上升为国家战略&#xff0c;藤校核心课程涉足人工智能…人工智能领域蕴含着巨大潜力&#xff0c;早已成为业内共识。 面对极大的行业空缺&#xff0c;不少人都希望能抢占行业红利期&#xff0c;进入AI领域。…

软件工程实务:软件产品

目录 1、软件产品的基本概念 2、软件工程是什么&#xff1f; 为什么产生软件工程? 软件工程是做什么的? 3、定制软件和软件产品的工程比较 4 、软件产品的运行模式 5、软件产品开发时需要考虑的两个基本技术因素 6、产品愿景 7、软件产品管理 8、产品原型设计 9、小结…

如何区分人工智能生成的图像与真实照片(上)

随着最先进扩散模型&#xff08;如Midjourney、Stable Diffusion和Firefly&#xff09;生成的图像具有高度的逼真度&#xff0c;未经训练的我们很难区分真实照片和AI生成的图像。为了解决这个问题&#xff0c;这份指南&#xff0c;帮助读者培养更批判的眼光&#xff0c;识别AI生…

056、PyCharm 快速代码重构的方法

在实际的编程过程中&#xff0c;如果有一段代码需要在多个地方重复使用&#xff0c;我们应该将这段代码封装成一个函数。这样可以提高代码的可重用性和可维护性。 在PyCharm编辑器里&#xff0c;可以使用以下操作对代码块进行快速的重构。 &#xff08;1&#xff09;、选中一…

【数据分析】推断统计学及Python实现

各位大佬好 &#xff0c;这里是阿川的博客&#xff0c;祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 Python 初阶 Python–语言基础与由来介绍 Python–…

CCAA质量管理【学习笔记】​​ 备考知识点笔记(五)质量设计方法与工具

第五节 质量设计方法与工具 1 任 务 分 解 法 1.1 概念 任务分解法&#xff0c;又称工作分解结构 (Work Breakdown Structure, 简 称 WBS) 。WBS 指以可交付成果为 导向&#xff0c;对项目团队为实现项目目标并完成规定的可交付成果而执行的工作所进行的层次分解。W…

mysql 8 创建用户,并对用户授权

创建用户&#xff1a; 对MySQL创建新用户。命令如下&#xff1a; create user devuser% identified by 123456; 授予权限 grant all privileges on joolun_ry.* to devuser% with grant option; 参数说明&#xff1a; joolun_ry&#xff1a;表明对那个库进行授权&#xf…

C语言概述与历史

引言 C语言是一门历史悠久且影响深远的编程语言。它不仅为后继的许多编程语言奠定了基础&#xff0c;同时因其高效性和灵活性在系统编程和嵌入式开发领域得到了广泛应用。本篇文章将全面介绍C语言的起源与发展、设计目标与理念&#xff0c;以及C语言的标准演化历程&#xff0c;…

解决MyBatis获取刚插入数据的ID值

解决MyBatis获取刚插入数据的ID值 Mybatis获取刚插入数据的ID值有很多解决方法&#xff0c;目前采用以下方式进行获取。 添加完数据后直接返回刚添加数据的id // UserDao.java public static void addUser() throws Exception{InputStream resourceAsStream Resources.getR…

学习资料分析

学习资料分析 速算运算 √截位直除分数比较等比修正其他速算方法基期与现期基本概念求基期求现期增长率与增长量增长相关统计术语求一般增长率比较一般增长率增长量比重比重相关公式求比重平均数倍数间隔增长乘积增长率年增长率混合增长率资料分析:主要测查报考者对文字、数字…

N3 中文文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 前言 前面学习了相关自然语言编码&#xff0c;这周进行相关实战 导入依赖库和设置设备 import torch import torch.nn as nn import torchvision fro…