Spring Boot + Spring Security基础入门教程

news2024/11/14 18:49:40

Spring Security简介

Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Security 致力于为 Java 应用程序提供身份验证和授权的能力。

Spring Security 两大重要核心功能:用户认证(Authentication)用户授权(Authorization)

用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。

用户授权:验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,有的用户既能读取,又能修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

准备工作

创建Spring Boot项目

pom.xml文件(根据自己所需引入)

    <dependencies>
        <!-- security(安全认证) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- mybatis-plus(数据库操作) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- redis(缓存) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- swagger(api接口文档) -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!-- jjwt(token生成与校验) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- fastjson2(JSON处理) -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.23</version>
        </dependency>

        <!-- mysql(连接驱动) -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- druid(mysql连接池) -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

认证(Authentication)

登陆校验流程

原理初探

SpringSecurity 的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。

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

ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。

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

我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。

在控制台处点击Evaluate Expression或Alt+F8,如下图:

 然后输入 run.getBean(DefaultSecurityFilterChain.class) 进行过滤,可以看到 run 容器中的 15 个过滤器:

Spring Security配置类

import com.zm.springsecurity.common.filter.CustomAuthenticationFilter;
import com.zm.springsecurity.common.security.CustomAuthenticationFailureHandler;
import com.zm.springsecurity.common.security.CustomAuthenticationSuccessHandler;
import com.zm.springsecurity.common.security.CustomLogoutSuccessHandler;
import com.zm.springsecurity.service.impl.CustomUserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String URL_WHITELIST[] ={
            "/v2/api-docs", "/swagger-resources/configuration/ui",
            "/swagger-resources", "/swagger-resources/configuration/security",
            "/swagger-ui.html", "/webjars/**", // swagger不需要授权即可访问的路径

            "/login",
            "/logout",
            "/my/login",
            "/my/logout",
            "/captcha",
            "/password",
            "/image/**",
            "/test/**"
    };

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

    @Bean
    protected CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter authenticationFilter = new CustomAuthenticationFilter();
        authenticationFilter.setFilterProcessesUrl("/my/login");
        authenticationFilter.setUsernameParameter("username");
        authenticationFilter.setPasswordParameter("password");
        authenticationFilter.setAuthenticationManager(super.authenticationManager());
        authenticationFilter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
        authenticationFilter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());
        return authenticationFilter;
    }

//    @Override
//    @Bean
//    public AuthenticationManager authenticationManagerBean() throws Exception {
//        return super.authenticationManagerBean();
//    }
//
//    @Override
//    protected AuthenticationManager authenticationManager() throws Exception {
//        return super.authenticationManager();
//    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable() // 开启跨域请求和关闭csrf攻击
                .userDetailsService(new CustomUserDetailsServiceImpl())
//                .formLogin().loginPage("/login_page")
//                .loginProcessingUrl("/my/login")
//                .usernameParameter("username").passwordParameter("password").permitAll()
//                .successHandler(new CustomAuthenticationSuccessHandler()) // 认证成功处理器
//                .failureHandler(new CustomAuthenticationFailureHandler()) // 认证失败处理器
//                .and()
                .logout()
                .logoutUrl("/my/logout")
                .logoutSuccessHandler(new CustomLogoutSuccessHandler()) // 退出登录成功处理器
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session禁用配置(无状态)
                .and()
                .authorizeRequests()  // 验证请求拦截规则
                .antMatchers(URL_WHITELIST).permitAll() // 配置访问认证白名单
                .antMatchers("/admin/**").hasRole("admin") // 要具有某种权限
                .antMatchers("/user/**").hasAnyRole("admin", "user") // 要具有某种权限中的一种
                .anyRequest().authenticated();

        http.addFilterAt(this.customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

使用数据库进行认证

注:本文采用 MyBatis-Plus 作为持久层框架,与 MyBatis-Plus 相关内容自行编写。

实现UserDetails接口

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class CustomUserDetails implements UserDetails {
    private User user;
    private List<SimpleGrantedAuthority> authorityList;

    public CustomUserDetails() {
    }

    public CustomUserDetails(User user, List<String> roleList) {
        this.user = user;
        this.authorityList = roleList.stream()
                .map(role -> new SimpleGrantedAuthority(role))
                .collect(Collectors.toList());
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorityList;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getPassword();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return user.getStatus();
    }
}

自定义UsernamePasswordAuthenticationFilter过滤器

若处理请求为表单类型的数据,则此步忽略并删除 Security 配置类中 CustomAuthenticationFilter 相关的内容。UsernamePasswordAuthenticationFilter 为认证过滤器,默认只能处理表单提交的数据,如需处理 JSON 数据,则需要重写 UsernamePasswordAuthenticationFilter 的 attemptAuthentication() 方法。

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
 * 登录认证过滤器,处理认证的请求体为 JSON 的数据
 */
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String contentType = request.getContentType();
        logger.info("contentType = " + contentType);
        if (!Objects.isNull(contentType) && (contentType.equals(MediaType.APPLICATION_JSON_VALUE) || contentType.equals(MediaType.APPLICATION_JSON_UTF8_VALUE))) {
            UsernamePasswordAuthenticationToken authRequest = null;
            try (InputStream inputStream = request.getInputStream()) {
                ObjectMapper mapper = new ObjectMapper(); // JSON数据映射器
                Map<String,String> params = mapper.readValue(inputStream, Map.class);
                authRequest = new UsernamePasswordAuthenticationToken(params.get("username"), params.get("password"));
            } catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken("", "");
            } finally {
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }
        else {
            return super.attemptAuthentication(request, response);
        }
    }
}

自定义处理器

JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import java.util.Date;

public class JWTUtils {
    private static final String tokenSignKey = "zm_sign_key"; // 私钥(盐),太短会报异常:secret key byte array cannot be null or empty.
    private static final Integer tokenExpiration = 60 * 60 * 24 * 14; // 14天

    public static String createToken(String username){
        String token = Jwts.builder()
                .setSubject("AUTH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("username", username)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compact();
        return token;
    }

    public static String createToken(Long userId, String username){
        String token = Jwts.builder()
                .setSubject("AUTH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("username", username)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compact();
        return token;
    }

    public static Long getUserId(String token) {
        try {
            if (!StringUtils.hasLength(token)) {
                return null;
            }

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return claims.get("userId", Long.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getUsername(String token) {
        try {
            if (!StringUtils.hasLength(token)) {
                return "";
            }

            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
            Claims claims = claimsJws.getBody();
            return claims.get("username", String.class);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

响应JSON数据信息

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class ResponseUtils {
    public static void response(HttpServletResponse response, String data) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter responseWriter = response.getWriter();
        responseWriter.write(data);
        responseWriter.flush();
        responseWriter.close();
    }
}

自定义认证成功处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.JWTUtils;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

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

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String customUserDetails = authentication.getPrincipal().toString();  // 继承UserDetails的对象
        String token = JWTUtils.createToken(username);
        String jsonString = JSON.toJSONString(ResultUtils.ok("登录成功", token));
        ResponseUtils.response(response, jsonString);
    }
}

为什么 authentication.getPrincipal() 的结果是继承 UserDetails 的对象:ProviderManager 中的 this.copyDetails(authentication, result); 语句。

    private void copyDetails(Authentication source, Authentication dest) {
        if (dest instanceof AbstractAuthenticationToken && dest.getDetails() == null) {
            AbstractAuthenticationToken token = (AbstractAuthenticationToken)dest;
            token.setDetails(source.getDetails());
        }

    }

自定义认证失败处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

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

public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        String message = exception.getMessage();
        if(exception instanceof BadCredentialsException){
            message = "用户名或密码错误!";
        }
        String jsonString = JSON.toJSONString(ResultUtils.fail(message));
        ResponseUtils.response(response, jsonString);
    }
}

自定义注销成功处理器

import com.alibaba.fastjson2.JSON;
import com.zm.springsecurity.utils.ResponseUtils;
import com.zm.springsecurity.utils.ResultUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

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

public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        String jsonString = JSON.toJSONString(ResultUtils.ok("退出登录成功!"));
        ResponseUtils.response(response, jsonString);
    }
}

授权(Authorization

未完待续... 

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

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

相关文章

pandas 使用loc和iloc读取行数据或列数据

文章目录 一、 使用loc方法读取数据1.1 读取某行某列的值1.2 读取某个区域1.3 按照条件筛选 二. 使用iloc方法读取数据2.1 读取某行某列的值2.2 读取某个区域的数据 创建一个DataFrame data {name:[张三, 李四, 王五, 赵六],age:[20, 21, 22, 23], gender: [0, 1, 1, 1], stat…

网络工程项目报价单应该怎么写?记住这6个步骤准没错!

作为一名网络工程师&#xff0c;你在向潜在客户提供服务时&#xff0c;编写一个清晰明了的项目报价单是至关重要的。一个好的报价单不仅能够让客户更好地了解你的服务内容&#xff0c;还可以为你的项目提供更高的转化率。在本文中&#xff0c;我们将探讨如何编写一个有效的网络…

一图看懂 xlwt 模块:读写 Excel 文件的数据和格式信息, 资料整理+笔记(大全)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 一图看懂 xlwt 模块&#xff1a;读写 Excel 文件的数据和格式信息, 资料整理笔记&#xff08;大全&#xff09; 摘要模块图类关系图模块全展开【xlwt】统计常量模块1 xlwt.compat2 x…

Linux系统之部署Linux管理面板1Panel

Linux系统之部署Linux管理面板1Panel 一、1Panel介绍1.1Panel简介2.1Panel特点 二、本地环境规划1.本此实践目的2.本地环境规划 三、检查本地环境1.检查操作系统版本2.检查系统内核版本 四、部署1Panel1.创建安装目录2.一键部署1Panel3.检查1Panel服务运行状态4.检查1Panel监听…

数据结构——二叉搜索树、平衡二叉树、红黑树

数据结构——二叉搜索树 一、二叉搜索树1.二叉搜索树的特性2.二叉搜索树的查找、插入和删除 二、平衡二叉树1.基本介绍2.AVL树的自平衡1&#xff09;自平衡的调整操作2&#xff09;自平衡调整的局面 3.AVL树的代码实现4.AVL树的特点 三、红黑树1.基本介绍2.红黑树的自平衡1&…

秒杀系统如何设计

思路&#xff1a;对于秒杀系统&#xff0c;两个架构优化思路&#xff1a; 1&#xff09;尽量将请求拦截在系统上游 2&#xff09;读多写少的常用多使用缓存 1、限制用户在x秒之内只能提交一次请求 2、同一个uid&#xff0c;或同一类查询&#xff08;例如车次&#xff09;。限制…

配电网光伏/储能双层优化配置模型(选址定容)

目录 1 主要内容 上层目标函数考虑光伏和储能的投资成本。 程序采用模块化编程&#xff0c;并有每个模块功能介绍&#xff0c;方便学习。 2 部分代码 3 程序结果 4 程序结果 1 主要内容 该程序主要方法复现《含高比例可再生能源配电网灵活资源双层优化配置》运行-规划联合…

【Maven 入门】第二章、Maven核心程序解压与配置

一、Maven 官网地址 首页&#xff1a; Maven – Welcome to Apache Maven(opens new window) 下载页面&#xff1a; Maven – Download Apache Maven(opens new window) 本文以maven-3.3.8为例 具体下载地址&#xff1a;https://dlcdn.apache.org/maven/maven-3/3.8.8/bina…

LeetCode刷题集(二)(LeetCode 2037使每位学生都有座位的最少移动次数)

学习目标&#xff1a; 掌握LeetCode2037使每位学生都有座位的最少移动次数 题目内容&#xff1a; 一个房间里有 n 个座位和 n 名学生&#xff0c;房间用一个数轴表示。给你一个长度为 n 的数组 seats &#xff0c;其中 seats[i] 是第 i 个座位的位置。同时给你一个长度为 n 的数…

数据结构-排序3(终章)

前言&#xff1a; 上一章&#xff0c;对交换排序的冒牌和快排做了复盘&#xff0c;这一章对&#xff0c;归并排序以及非比较排序中的计数排序做一个复盘。 目录 2.4归并排序 2.4.1规定递归 2.4.2归并非递归 2.5非比较排序 2.5.1计数排序 2.6排序的稳定性分析 2.6.1冒…

【Transformer系列(2)】注意力机制、自注意力机制、多头注意力机制、通道注意力机制、空间注意力机制超详细讲解

前言 注意力机制一直是一个比较热的话题&#xff0c;其实在很早之前就提出了&#xff0c;我们在学习图像分类时在SENet就见到过&#xff08;直通车&#xff1a;经典神经网络论文超详细解读&#xff08;七&#xff09;——SENet&#xff08;注意力机制&#xff09;学习笔记&…

金陵科技学院五年一贯制专转本管理学原理考试大纲

金陵科技学院五年一贯制专转本管理学原理考试大纲 一、考核对象 本课程的考核对象为五年一贯制高职专转本“旅游管理”专业入学考试考生。 二、考核方式 本课程考核采用闭卷笔试的方式。 三、命题依据及原则 1、命题依据 参考书目&#xff1a;《管理学——原理与方法》 …

Docker Swarm集群企业案例实战

1. Docker Swarm集群企业案例实战 Docker Swarm 和 Docker Compose 一样&#xff0c;都是 Docker 官方容器编排项目&#xff0c;但不同的是&#xff0c;Docker Compose 是一个在单个服务器或主机上创建多个容器的工具&#xff0c;而 Docker Swarm 则可以在多个服务器或主机上创…

驼峰式匹配-力扣1023-java

一、题目描述 如果我们可以将小写字母插入模式串 pattern 得到待查询项 query&#xff0c;那么待查询项与给定模式串匹配。&#xff08;我们可以在任何位置插入每个字符&#xff0c;也可以插入 0 个字符。&#xff09; 给定待查询列表 queries&#xff0c;和模式串 pattern&a…

未来技术方向——“乐高式”可组装式开发能力

技术正在改变各行各业的发展&#xff0c;Gartner的主要战略技术趋势一直是行业的技术风向标之一。近3年&#xff0c;Gartner在主要的战略技术趋势中都提到组装式技术&#xff0c;2021年首次提出组装式企业&#xff0c;2022年提出可组装式应用&#xff0c;2023年在2项主要战略技…

ModuleNotFoundError: No module named ‘d2l’

目录 1. 下载李沐老师分享的源代码 step1&#xff1a;下载李沐老师分享的源代码&#xff1a; step3&#xff1a;Anaconda Prompt中安装d2l(这个l是英文) step4&#xff1a;运行代码&#xff0c;成功&#xff1a; &#xff08;番外&#xff09;ModuleNotFoundError: No mod…

【微服务】5、声明式 HTTP 客户端 —— Feign

目录 一、RestTemplate 不好的地方二、Feign 是什么三、使用四、自定义 Feign 的配置(1) Feign 的几个常见配置(2) 配置 Feign 的日志级别① 通过配置文件② Java 代码配置日志级别 五、Feign 性能优化(1) 性能优化介绍(2) 修改 Feign 底层的 HTTP 请求客户端 六、Feign 的最佳…

C++:std::function模板类(前言):为什么有了函数指针还需要Functional

为什么有了函数指针还有 Functional 1: 函数指针定义2&#xff1a; 函数指针结论3&#xff1a;疑问4&#xff1a; Function来源5&#xff1a;Functional 特点 1: 函数指针定义 在C中可以使用指针指向一段代码&#xff0c;这个指针就叫函数指针&#xff0c;假设有下面一段代码 …

交友项目【首页推荐,今日佳人,佳人信息】

目录 1&#xff1a;首页推荐 1.1&#xff1a;接口地址 1.2&#xff1a;流程分析 1.3&#xff1a;代码实现 2&#xff1a;今日佳人 1.1&#xff1a;接口地址 1.2&#xff1a;流程分析 1.3&#xff1a;代码实现 3&#xff1a;佳人信息 1.1&#xff1a;接口地址 1.2&am…

计算机基础--MySQL--索引

参考文献 [MySQL索引连环18问&#xff01;] https://zhuanlan.zhihu.com/p/364041898[深入理解MySQL索引] https://www.infoq.cn/article/ojkwyykjoyc2ygb0sj2c[聚集索引和非聚集索引的区别] https://juejin.cn/post/7001094401858469918[索引分类] https://blog.csdn.net/dd2…