SpringSecurity(五):前后端分离认证总结案例。

news2024/11/18 16:49:08

前后端分离认证总结案例

    • 前言
    • 难点分析
    • Controller层
    • eneity层
      • Role
      • User
    • dao层
    • service层
    • config层
      • LoginFilter
      • SecurityConfig
    • resources
      • mapper
    • properties
    • pom.xml
    • 结尾

前言

和上一篇一样,从上倒下复制粘贴,所有代码贴完再运行,代码没有问题,只要贴对,都可以顺利跑出来的。

难点分析

前端系统给后端传递的数据为json,就会导致后端系统不能再用request.getParameter获取用户数据。
所以我们要将请求中json格式转换为对象,提取用户数据,然后进行认证。
在web传统项目进行认证请求时,底层调用的是FormLoginConfigurer,里面用UsernamePasswordAuthenticationFilter过滤器中的attempAuthentication试图认证的方法进行处理。下面看一下关键代码:

	if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);

我们可以看到,在前后端分离项目中,上面获取用户名和密码的方式肯定是不可以的(基于request请求获取)。
所以我们就不能再用传统的formLogin中的AuthenticationFilter,我们 要把这个filter做一个重写。

重写的重点是如何获取参数的信息,而且要想有认证功能,最后还是要调用return this.getAuthenticationManager().authenticate(authRequest);这行代码。

实现的思路:
创建一个UsernamePasswordAuthenticationFilter的子类实现,让字类去替换attempAuthentication

我们确定了UsernamePasswordAuthenticationFilter不适合作为我们要用的过滤器,所以我们自己创建一个名为LoginFilter的过滤器。
但是security里面是有一系列的过滤器的,我们要确保LoginFilter替换UsernamePasswordAuthenticationFilter后位置不变。
请添加图片描述

Controller层

TestController方法:

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

@RestController
public class test {

    @GetMapping("/test")
    public String test(){
        System.out.println("test.....");
        return "test OK!";
    }
}

eneity层

Role



public class Role {
    private Integer id;
    private String name;
    private String nameZh;


    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }
}

User



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

import java.util.*;


public class User implements UserDetails {
        private Integer id;
        private String username;
        private String password;
        private Boolean enabled;
        private Boolean accountNonExpired;
        private Boolean accountNonLocked;
        private Boolean credentialsNonExpired;
        private List<Role> roles = new ArrayList();
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {

           Set<GrantedAuthority> authorities = new HashSet();
            roles.forEach(role->{
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(role.getName());
                authorities.add(simpleGrantedAuthority);
            });
            return authorities;
        }
        @Override
        public String getPassword() {
            return password;
        }
        @Override
        public String getUsername() {
            return username;
        }
        @Override
        public boolean isAccountNonExpired() {
            return accountNonExpired;
        }
        @Override
        public boolean isAccountNonLocked() {
            return accountNonLocked;
        }
        @Override
        public boolean isCredentialsNonExpired() {
            return credentialsNonExpired;
        }
        @Override
        public boolean isEnabled() {
            return enabled;
        }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Integer getId() {
        return id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getAccountNonExpired() {
        return accountNonExpired;
    }

    public void setAccountNonExpired(Boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public Boolean getAccountNonLocked() {
        return accountNonLocked;
    }

    public void setAccountNonLocked(Boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public Boolean getCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public List<Role> getRoles() {
        return roles;
    }
}


dao层


import com.wang.entity.Role;
import com.wang.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserDao {
    //提供根据用户名返回方法
    User loadUserByUsername(String username);

    //提供根据用户id查询用户角色信息方法
    List<Role> getRoleByUid(Integer id);
}

service层


import com.wang.dao.UserDao;
import com.wang.entity.Role;
import com.wang.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

@Service
public class MyUserDetailService implements UserDetailsService {

    private final UserDao userDao;
    @Autowired
    public MyUserDetailService (UserDao userDao){
        this.userDao = userDao;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //1. 查询用户
        User user = userDao.loadUserByUsername(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        //2. 查询权限信息
        List<Role> roles = userDao.getRoleByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

config层

LoginFilter

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
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.util.Map;

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //1. 判断是否是post方式请求。
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        //2. 判断是否是json格式请求类型。
        if(request.getContentType().equalsIgnoreCase(MediaType.APPLICATION_JSON_VALUE)){
            //3. 从json数据中获取用户输入用户名和密码进行认证{"uname":"xxx","password":"xxx"}
            try{
                Map<String,String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
                String username = userInfo.get(getUsernameParameter());
                String password =userInfo.get(getPasswordParameter());

                System.out.println("用户名:"+username + "密码:"+password);

                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,password);

                setDetails(request,authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            } catch (IOException e) {
              e.printStackTrace();
            }
        }
        return super.attemptAuthentication(request,response);
    }
}

SecurityConfig


import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.print.attribute.standard.Media;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final MyUserDetailService myUserDetailService;
    @Autowired
    public SecurityConfig(MyUserDetailService myUserDetailService){
        this.myUserDetailService = myUserDetailService;
    }
//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
//        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
//        return inMemoryUserDetailsManager;
//    }

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

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

    //自定义filter交给工厂管理
    @Bean
    public LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/doLogin"); //指定认证的url
        loginFilter.setUsernameParameter("uname"); //指定接受json 用户名key
        loginFilter.setPasswordParameter("passwd"); //指定接受json 密码key
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler(
                ((request, response, authentication) -> {
                    Map<String, Object> result = new HashMap<>();
                    result.put("msg", "登陆成功");
                    result.put("用户信息", authentication.getPrincipal());
                    response.setContentType("application/json;charset=UTF-8");
                    response.setStatus(HttpStatus.OK.value());
                    String s = new ObjectMapper().writeValueAsString(result);
                    response.getWriter().println(s);
                })
        ); //认证成功处理
        loginFilter.setAuthenticationFailureHandler(
                ((request, response, exception) -> {
                    Map<String, Object> result = new HashMap<>();
                    result.put("msg", "登陆失败:" + exception.getMessage());
                    response.setContentType("application/json;charset=UTF-8");
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                    String s = new ObjectMapper().writeValueAsString(result);
                    response.getWriter().println(s);
                })
        ); //认证失败处理
        return loginFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .anyRequest().authenticated() //所有请求必须认证
                .and()
                .formLogin()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(
                        ((request, response, authException) -> {
                            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                            response.setStatus(HttpStatus.UNAUTHORIZED.value());
                            response.getWriter().println("请认证后再去处理!");
                        })
                )
                .and()
                .logout()
                .logoutRequestMatcher(
                        new OrRequestMatcher(
                                new AntPathRequestMatcher("/logout", HttpMethod.DELETE.name()),
                                new AntPathRequestMatcher("/logout", HttpMethod.GET.name())
                        )
                )
                .logoutSuccessHandler(
                        ((request, response, authentication) -> {
                            Map<String, Object> result = new HashMap<>();
                            result.put("msg", "注销成功");
                            result.put("用户信息", authentication.getPrincipal());
                            response.setContentType("application/json;charset=UTF-8");
                            response.setStatus(HttpStatus.OK.value());
                            String s = new ObjectMapper().writeValueAsString(result);
                            response.getWriter().println(s);
                        })
                )
                .and()
                .csrf().disable();
        // at:用来某个filter替换过滤器链中的某个filter。
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

resources

mapper

<?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.wang.dao.UserDao">

    <!--        更具用户名查询用户方法-->
    <select id="loadUserByUsername" resultType="com.wang.entity.User">
        select id,
               username,
               password,
               enabled,
               accountNonExpired,
               accountNonLocked,
               credentialsNonExpired
        from user
        where username = #{username}
    </select>
    <!--        查询指定⾏数据-->
    <select id="getRoleByUid" resultType="com.wang.entity.Role">
        select r.id,
               r.name,
               r.name_zh nameZh
        from role r,
             user_role ur
        where r.id = ur.rid
          and ur.uid = #{uid}
    </select>
</mapper>

properties

server.port= 9092
# 关闭thymeleaf 缓存
spring.thymeleaf.cache= false


# 配置数据源
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/security?characterEncoding=UTF-8&useSSL=false&&serverTimezone=CST
spring.datasource.username= 你的账号
spring.datasource.password= 你的密码

# Mybatis配置
# 注意mapper目录必须用"/"
mybatis.mapper-locations= classpath:com/wang/mapper/*.xml
mybatis.type-aliases-package=com.example.eneity

# 日志处理
logging.level.com.example = debug

pom.xml

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </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>

结尾

所有代码都是测试过的,保证没有问题,如果存在错误,请仔细检查,欢迎留言讨论,结合编程不良人视频配套更佳。

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

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

相关文章

初步学习使用SpringBoot框架(手动插入数据模拟访问数据库)

对于SpringBoot框架介绍大家可以看看这个这篇文章&#xff0c;SpringBoot优缺点以及如何安装使用 以下我是按照老师给的安装方法进行安装使用SpringBoot框架&#xff1a; 大家安装SpringBoot框架时候&#xff0c;最好安装3.0以下的&#xff0c;不然需要对应较高版本的JDK版本&…

【Axure教程】拖动调整行高列宽的表格

表格是在系统软件中非常常用的工具。表格通常由行和列组成&#xff0c;用于以结构化的方式显示和组织数据。它们在各种场景中都有广泛的应用&#xff0c;包括数据分析、数据录入、报表生成、项目管理和数据可视化等领域。 今天作者就教大家如何在Axure里制作一个能通过鼠标拖动…

阿里巴巴最新 SpringCloudAlibaba 学习笔记,全程通俗易懂,一套搞懂!

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件&#xff0c;依托 Spring Cloud Alibaba&#xff0c;只需要添加一些注解和少量配置&#xff0c;就可以将 Spring Cloud 应用接入阿里微服务解决方案&#xff0c;通过阿里…

Visual C++中函数的覆盖和函数的隐藏

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来聊聊Visual C中函数的覆盖和函数的隐藏。 Visual C中函数的覆盖和函数的隐藏与JAVA有很大不同&#xff0c;有些容易搞错的点和概念&#xff0c;我们要特别注意。 首先&#xff0c;先满足一下急性子的…

【刷题笔记——链表专栏】牛客网:合并两个排序链表

【刷题笔记——链表专栏】牛客网&#xff1a;合并两个排序链表 一、题目描述 二、思路分析 1、创建新的头节点newHead&#xff08;注意这一步&#xff09; 2、循环遍历pHead1和pHead2并进行链表并进行合并&#xff0c;直到pHead1和pHead2其中有一个为null时结束循环 3、合并完…

Win10,Win11玩游戏找不到d3dx9怎么解决

相信有些朋友遇到了d3dx9_42.dll丢失的情况不知道怎么解决&#xff0c;而今日小编带来的这篇文章就是讲解关于d3dx9_42.dll丢失进行修复的操作内容&#xff0c;d3dx9_42.dll丢失怎么解决&#xff1f;&#xff08;修复方法&#xff09;d3dx9 42.dll文件是DirectX中必备文件,许多…

【期末不挂科 学习数据结构】

期末不挂科 学习数据结构 第一章绪论1.1数据结构的基本概念1.1.1基本概念和术语1.数据2.数据元素3.数据对象4.数据类型5.数据结构 1.1.2数据结构三要素1.数据的逻辑结构2.数据的存储结构3.数据的运算 第一章绪论 1.1数据结构的基本概念 1.1.1基本概念和术语 1.数据 数据是信…

Redis7【⑧ Redis集群(cluster)】

Redis集群 Redis 集群是 Redis 数据库的分布式解决方案&#xff0c;它可以将数据分散存储在多个节点上&#xff0c;以支持大规模数据存储和高并发访问。 Redis 集群使用的是基于槽的分区策略&#xff0c;即将数据分成固定数量的槽&#xff0c;每个槽由一个主节点和多个从节点组…

Python基础教程: 入门简介

Python 最初由 Guido van Rossum 在 1991 年开发&#xff0c;当时他在执行其他项目的时候需要编写一个简单的解释器。Python 最初只是一种简单的脚本语言&#xff0c;但在后来的发展中&#xff0c;它迅速成为一种流行的编程语言。 Python 不仅在大型科技企业中应用广泛&#x…

spring cloud 之 gateway

网关介绍 在微服务架构体系中&#xff0c;一个系统会被拆分为很多个微服务&#xff0c;那么作为客户端要如何去调用这么多的微服务呢&#xff1f;如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别调用&#xff0c;当然这样是不现实的…

Mysql事务原理

一、概述 事务&#xff1a;一组操作要么全部成功&#xff0c;要么全部失败&#xff0c;目的是为了保证数据最终的一致性。 数据库一般都会并发执行多个事务&#xff0c;多个事务可能会并发的对相同的一批数据进行增删改查操作&#xff0c;可能就会导致我们说的脏写、脏读、不可…

水站桶装水订水系统桶装水小程序,改变了桶装水行业传统的送水模式;

水站桶装水订水系统桶装水小程序&#xff0c;改变了桶装水行业传统的送水模式&#xff0c;客户通过线上下单&#xff0c;送水工实时接单配送&#xff0c;空桶更好的记录管理&#xff0c;财务数据在后台清晰明了&#xff0c;提高了水站的工作效率。 除此之外&#xff0c;还有很多…

网络系统集成实验(五)| 系统集成路由器OSPF动态、综合路由配置

一、前言 该系列文章将会对网络系统集成课程中相关实验进行更新&#xff0c;本篇为第五篇&#xff0c;有关路由器的OSPF、综合路由配置&#xff0c;包括了OSPF的配置实验、单臂路由实验、RIP配置实验、综合实验等。 注意&#xff1a;该实验的后半部分综合实验基于前面的实验&am…

java Collection集合使用笔记

1、Collection集合概述 它是单例集合的最顶层接口&#xff0c;它表示一组对象&#xff0c;这些对象也称之为Collection的元素JDK不提供此接口任何直接实现&#xff0c;但提供更具体的子接口&#xff08;如&#xff1a;List和Set&#xff09;实现创建Collection集合对象的方法&…

麒麟信安与飞腾携手共赴云端

当前各行业已进入全面云化时代&#xff0c;云桌面技术作为典型办公终端替代方案&#xff0c;凭借其数据安全、便捷运维、综合成本占优的优势正逐步在各行业应用实施。伴随十四五规划加速数字化转型及信息产业核心技术自主化发展&#xff0c;各政企组织迫切需要基于国产软硬件实…

gmap构建离线地图,用createCustomerTiledLayer方法,瓦片地址尾部多了 ?x={x}y={y}z{z} 导致无法显示地图。

gmap构建离线地图&#xff0c;用createCustomerTiledLayer方法&#xff0c;瓦片地址尾部多了 ?x{x}&y{y}&z&{z} 导致无法显示地图。 function initMap() {this.map new GL.Map(map, {center: "120.650847,31.3092434",zoom: 12,zoomControl: false,sca…

青大数据结构【2018】【算法设计】

关键词&#xff1a; 单链表递增、树的先序遍历 1&#xff09;带头结点的单链表L&#xff0c;从首结点开始&#xff0c;依次两两比较&#xff0c;如果前者大于等于后者&#xff0c;说明是非递增的&#xff1b;否则递增。 2&#xff09; 1&#xff09;先遍历二叉树&#xff0c;…

【mars3d】基于vue3的marsgis通用UI库 mars-ui 的使用

一名脑残程序员的mars-ui心酸使用记录。 通过mars3d的官网我们可以看到&#xff0c;有配套的UI库使用&#xff0c;那么我们如何使用到自己的项目中呢&#xff0c;跟着文章一步一步来吧&#xff01; 1、引入UI库 ① 安装ant-design-vue cnpm install ant-design-vue --save②…

如何实现功能插件化

本文将介绍两种方式来实现功能插件化&#xff1a; Java SPISpring factories 在整个插件化的方案中&#xff0c;会涉及到如下 3 个组成部分&#xff1a; 插件定义&#xff08;即将插件定义为一个接口&#xff09; 插件实现&#xff08;即对插件接口的实现&#xff09; 这里…

【需求实现】输入多少就输出多少的拟合任务如何实现(二):进度条简化

文章目录 导读普通的输出方式上下求索TensorBoard是个不错的切入点与Callback参数对应的Callback方法官方的内置Callback官方进度条简单的猜测与简单的验证拼图凑齐了&#xff01; 导读 在训练模型的过程中往往会有日志一堆一堆的困扰。我并不想知道&#xff0c;因为最后我会在…