springboot listener、filter登录实战

news2024/11/14 14:00:26

转载自: www.javaman.cn
博客系统访问: http://175.24.198.63:9090/front/index
在这里插入图片描述

登录功能

1、前端页面

采用的是layui-admin框架,文中的验证码内容,请参考作者之前的验证码功能

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>ds博客</title>
    <div th:replace="common/link::header"></div>
    <link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all">
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
    <div class="layadmin-user-login-main">
        <div class="layadmin-user-login-box layadmin-user-login-header">
            <h2>ds博客</h2>
            <p>后台登录</p>
        </div>
        <div class="layadmin-user-login-box layadmin-user-login-body layui-form">
            <div class="layui-form-item">
                <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
                <input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
            </div>
            <div class="layui-form-item">
                <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
                <input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
            </div>
            <div class="layui-form-item">
                <div class="layui-row">
                    <div class="layui-col-xs7">
                        <label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label>
                        <input type="text" name="code"  lay-verify="required" placeholder="图形验证码" class="layui-input">
                    </div>
                    <div class="layui-col-xs5">
                        <div style="margin-left: 10px;">
                            <img id="codeImg" class="layadmin-user-login-codeimg">
                        </div>
                    </div>
                </div>
            </div>
            <div class="layui-form-item" style="margin-bottom: 20px;">
                <input type="checkbox" name="remember-me" lay-skin="primary" title="记住密码">
            </div>
            <div class="layui-form-item">
                <button class="layui-btn layui-btn-fluid layui-bg-blue"  lay-submit lay-filter="login">登 录</button>
            </div>
        </div>
    </div>

<!--    <div class="layui-trans layadmin-user-login-footer">-->
<!--        <p>版权所有 © 2022 <a href="#" target="_blank">济南高新开发区微本地软件开发工作室</a> 鲁ICP备20002306号-1</p>-->
<!--    </div>-->
</div>
<div th:replace="common/script::footer"></div>
<script th:inline="javascript">
    layui.config({
        base: '/static/layuiadmin/' //静态资源所在路径
    }).extend({
        index: 'lib/index' //主入口模块
    }).use(['index', 'user'], function(){
        let $ = layui.$,
            form = layui.form;
        // 初始化
        getImgCode();
        form.render();
        //提交
        form.on('submit(login)', function(obj) {
            // 打开loading
            let loading = layer.load(0, {
                shade: false,
                time: 2 * 1000
            });
            // 禁止重复点击按钮
            $('.layui-btn').attr("disabled",true);
            //请求登入接口
            $.ajax({
                type: 'POST',
                url:  ctx + '/login',
                data: obj.field,
                dataType: 'json',
                success: function(result) {
                    if (result.code === 200) {
                        layer.msg('登入成功', {
                             icon: 1
                            ,time: 1000
                        }, function(){
                            window.location.href = '/';
                        });
                    } else {
                        layer.msg(result.message);
                        // 刷新验证码
                        getImgCode();
                        // 关闭loading
                        layer.close(loading);
                        // 开启点击事件
                        $('.layui-btn').attr("disabled", false);
                    }
                }
            });
        });
        $("#codeImg").on('click', function() {
            // 添加验证码
            getImgCode();
        });
        $(document).keydown(function (e) {
            if (e.keyCode === 13) {
                $('.layui-btn').click();
            }
        });
        // 解决session过期跳转到登录页并跳出iframe框架
        $(document).ready(function () {
            if (window != top) {
                top.location.href = location.href;
            }
        });
    });
    /**
     * 获取验证码
     */
    function getImgCode() {
        let url = ctx + '/getImgCode';
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = "blob";
        xhr.onload = function() {
            if (this.status === 200) {
                let blob = this.response;
                document.getElementById("codeImg").src = window.URL.createObjectURL(blob);
            }
        }
        xhr.send();
    }
</script>
</body>
</html>
2、后端处理/login请求

在这里插入图片描述

通过springsecurity的.loginProcessingUrl(“/login”)处理,处理逻辑如下:

.loginProcessingUrl("/login") 用于指定处理登录操作的URL地址,而具体的验证逻辑是由 Spring Security 提供的认证过滤器链负责的。在Spring Security中,主要的认证过程是由UsernamePasswordAuthenticationFilter来完成的。

当用户提交登录表单,请求到达.loginProcessingUrl("/login")配置的URL时,UsernamePasswordAuthenticationFilter会拦截这个请求,然后进行以下主要步骤:

  1. 获取用户名和密码:从请求中获取用户输入的用户名和密码。

  2. 创建认证令牌:使用获取到的用户名和密码创建一个认证令牌(UsernamePasswordAuthenticationToken)。

  3. 将认证令牌传递给认证管理器:将认证令牌传递给配置的认证管理器(AuthenticationManager)进行认证。

  4. 执行认证逻辑:认证管理器会调用已配置的身份验证提供者(AuthenticationProvider)来执行实际的身份验证逻辑。通常,会使用用户提供的用户名和密码与系统中存储的用户信息进行比对。

  5. 处理认证结果:认证提供者返回认证结果,如果认证成功,则将认证令牌标记为已认证,并设置用户权限等信息。如果认证失败,会抛出相应的异常。

  6. 处理认证成功或失败:根据认证的结果,UsernamePasswordAuthenticationFilter将请求重定向到成功或失败的处理器,执行相应的操作,比如跳转页面或返回错误信息。

这个整个过程是由 Spring Security 提供的默认配置完成的,通常情况下,开发者只需要配置好认证管理器、用户信息服务(UserDetailsService),以及成功和失败的处理器,Spring Security 就会负责处理登录验证的整个流程。

package com.ds.core.config;

import com.ds.blog.system.service.SysUserService;
import com.ds.core.security.CustomAccessDeniedHandler;
import com.ds.core.security.DefaultAuthenticationFailureHandler;
import com.ds.core.security.DefaultAuthenticationSuccessHandler;
import com.ds.core.security.filter.ValidateCodeFilter;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
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.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 放过
                .antMatchers("/loginPage", "/getImgCode").permitAll()
                .antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll()
                // 剩下的所有的地址都是需要在认证状态下才可以访问
                .anyRequest().authenticated()
                .and()
                // 过滤登录验证码
                .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                // 配置登录功能
                .formLogin()
                .usernameParameter("userName")
                .passwordParameter("passWord")
                // 指定指定要的登录页面
                .loginPage("/loginPage")
                // 处理认证路径的请求
                .loginProcessingUrl("/login")
                .successHandler(defaultAuthenticationSuccessHandler)
                .failureHandler(defaultAuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(accessDeniedHandler)
                .and()
                // 登出
                .logout()
                .invalidateHttpSession(true)
                .deleteCookies("remember-me")
                .logoutUrl("/logout")
                .logoutSuccessUrl("/loginPage")
                .and()
                .rememberMe()
                // 有效期7天
                .tokenValiditySeconds(3600 * 24 * 7)
                // 开启记住我功能
                .rememberMeParameter("remember-me")
                .and()
                //禁用csrf
                .csrf().disable()
                // header response的X-Frame-Options属性设置为SAMEORIGIN
                .headers().frameOptions().sameOrigin()
                .and()
                // 配置session管理
                .sessionManagement()
                //session失效默认的跳转地址
                .invalidSessionUrl("/loginPage");
    }
}
3、登录成功监听器(记录登录日志)

创建监听器,在登录成功的时候记录登录日志。

  1. @Slf4j

    • @Slf4j 是 Lombok 提供的注解,用于自动生成日志对象,这里是为了方便使用日志。
  2. @Component

    • @Component 注解将类标识为一个 Spring 组件,使得 Spring 能够自动扫描并将其纳入容器管理。
  3. AuthenticationSuccessListener 实现 ApplicationListener 接口

    • AuthenticationSuccessListener 类实现了 ApplicationListener<AuthenticationSuccessEvent> 接口,表明它是一个事件监听器,监听的是用户认证成功的事件。
  4. SysLoginLogService 注入

    • SysLoginLogService 是一个服务类,通过 @Autowired 注解注入到当前类中。该服务类用于对登录日志的持久化操作。
  5. onApplicationEvent 方法

    • onApplicationEvent 方法是实现 ApplicationListener 接口的回调方法,在用户认证成功的时候会被触发。
    • 通过 authenticationSuccessEvent.getAuthentication().getPrincipal() 获取登录的用户信息,这里假设用户信息是 User 类型。
    • 通过 ServletUtil.getClientIP 获取客户端的IP地址,这里使用了 ServletUtil 工具类,可以通过请求上下文获取IP地址。
    • 创建一个 SysLoginLog 对象,将登录成功的相关信息设置进去,包括账号、登录IP、备注等。
    • 调用 sysLoginLogService.save(sysLoginLog) 将登录日志持久化存储。

总的来说,这段代码的作用是在用户成功登录后,通过监听 Spring Security 的认证成功事件,记录用户的登录日志信息,包括登录账号、登录IP和登录成功的备注。这样可以实现登录成功后的自定义操作,例如记录登录日志等。

@Slf4j
@Component
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
    @Autowired
    private SysLoginLogService sysLoginLogService;

    @Override
    public void onApplicationEvent(AuthenticationSuccessEvent authenticationSuccessEvent) {
        // 登录账号
        User user = (User) authenticationSuccessEvent.getAuthentication().getPrincipal();
        // 请求IP
        String ip = ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
        SysLoginLog sysLoginLog = new SysLoginLog();
        sysLoginLog.setAccount(user.getUsername());
        sysLoginLog.setLoginIp(ip);
        sysLoginLog.setRemark("登录成功");
        sysLoginLogService.save(sysLoginLog);
    }
}
4、登录失败监听器(记录登录日志)

创建监听器,在登录失败的时候记录异常登录日志。

@Slf4j
@Component
public class AuthenticationFailureListener implements ApplicationListener<AbstractAuthenticationFailureEvent>  {

    @Autowired
    private SysLoginLogService sysLoginLogService;

    @Override
    public void onApplicationEvent(AbstractAuthenticationFailureEvent abstractAuthenticationFailureEvent) {
        // 登录账号
        String username = abstractAuthenticationFailureEvent.getAuthentication().getPrincipal().toString();
        // 登录失败原因
        String message ;
        if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureBadCredentialsEvent) {
            //提供的凭据是错误的,用户名或者密码错误
            message = "提供的凭据是错误的,用户名或者密码错误";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureCredentialsExpiredEvent) {
            //验证通过,但是密码过期
            message = "验证通过,但是密码过期";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureDisabledEvent) {
            //验证过了但是账户被禁用
            message = "验证过了但是账户被禁用";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureExpiredEvent) {
            //验证通过了,但是账号已经过期
            message = "验证通过了,但是账号已经过期";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureLockedEvent) {
            //账户被锁定
            message = "账户被锁定";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProviderNotFoundEvent) {
            //配置错误,没有合适的AuthenticationProvider来处理登录验证
            message = "配置错误";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProxyUntrustedEvent) {
            // 代理不受信任,用于Oauth、CAS这类三方验证的情形,多属于配置错误
            message = "代理不受信任";
        } else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureServiceExceptionEvent) {
            // 其他任何在AuthenticationManager中内部发生的异常都会被封装成此类
            message = "内部发生的异常";
        } else {
            message = "其他未知错误";
        }
        // 请求IP
        String ip = ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
        SysLoginLog sysLoginLog = new SysLoginLog();
        sysLoginLog.setAccount(username);
        sysLoginLog.setLoginIp(ip);
        sysLoginLog.setRemark(message);
        sysLoginLogService.save(sysLoginLog);
    }
}
5、认证成功处理器

下面是一个认证成功处理器,登录成功后,会返回响应的信息给前端

@Component
@Slf4j
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
        log.info("-----login in success----");
        response.setCharacterEncoding("utf-8");
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString(Result.success()));
        writer.flush();
    }
}
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
6、认证失败处理器

下面是一个认证成功处理器,登录成功后,会返回响应的信息给前端

@Component
@Slf4j
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("login in failure : " +  exception.getMessage());

        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        PrintWriter writer = response.getWriter();
        String message;
        if (exception instanceof BadCredentialsException) {
            message =  "用户名或密码错误,请重试。";
            writer.write(JSON.toJSONString(Result.failure(message)));
        }else{
            writer.write(JSON.toJSONString(Result.failure(exception.getMessage())));
        }
        writer.flush();
    }
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
7、前端页面

返回200,就代表成功,跳转到/请求,进去index或者main页面

if (result.code === 200) {
    layer.msg('登入成功', {
         icon: 1
        ,time: 1000
    }, function(){
        window.location.href = '/';
    });
} else {

在这里插入图片描述

总结

AuthenticationSuccessEvent 是 Spring Security 中用于表示用户认证成功的事件。判断登录成功的主要依据是在认证过程中,用户提供的凭据(通常是用户名和密码)与系统中存储的凭据匹配。以下是判断登录成功的基本流程:

  1. 用户提交登录表单
    • 用户在浏览器中输入用户名和密码,然后点击登录按钮,提交登录表单。
  2. Spring Security 拦截登录请求
    • 配置的 .loginProcessingUrl("/login") 指定了登录请求的URL,Spring Security会拦截这个URL的请求。
  3. UsernamePasswordAuthenticationFilter处理登录请求
    • UsernamePasswordAuthenticationFilter 是 Spring Security 内置的过滤器之一,用于处理用户名密码登录认证。
    • 当用户提交登录表单时,UsernamePasswordAuthenticationFilter会拦截该请求,尝试进行身份验证。
  4. AuthenticationManager执行身份验证
    • UsernamePasswordAuthenticationFilter将用户名密码等信息封装成一个 UsernamePasswordAuthenticationToken
    • 通过 AuthenticationManager 进行身份验证,AuthenticationManager 是一个接口,实际的实现为 ProviderManager
    • ProviderManager通过配置的 AuthenticationProvider 来执行实际的身份验证逻辑。
  5. AuthenticationProvider处理身份验证
    • DaoAuthenticationProviderAuthenticationProvider 的默认实现之一,用于处理基于数据库的身份验证。
    • DaoAuthenticationProvider会从配置的 UserDetailsService 中获取用户信息,然后与用户提交的信息进行比对。
  6. 认证成功
    • 如果认证成功,AuthenticationProvider 会返回一个已认证的 Authentication 对象。
    • 这个已认证的 Authentication 对象包含了用户的信息,通常是 UserDetails 的实现。
  7. AuthenticationSuccessHandler处理认证成功
    • 在配置中,通过 .successHandler() 方法指定了处理认证成功的 AuthenticationSuccessHandler
    • 在这个处理器中,可以执行一些额外的逻辑,例如记录登录日志等。
  8. AuthenticationSuccessEvent被发布
    • 在处理成功的阶段,Spring Security 发布了 AuthenticationSuccessEvent 事件,表示认证成功。

在上述流程中,认证成功的判断主要是在 AuthenticationProvider 中完成的。DaoAuthenticationProvider 会检查用户提供的密码与数据库中存储的密码是否匹配。如果匹配,就认为认证成功。当认证成功后,后续的处理流程包括 AuthenticationSuccessHandler 的执行和 AuthenticationSuccessEvent 的发布。你可以通过监听 AuthenticationSuccessEvent 事件来执行一些额外的自定义逻辑,例如记录登录日志。

在这里插入图片描述

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

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

相关文章

如何通过VNC实现公网远程控制macOS设备

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Pinia无废话,快速上手

Pinia无废话&#xff0c;快速上手 Vue3 状态管理 - Pinia 1. 什么是Pinia Pinia 是 Vue 的专属的最新状态管理库 &#xff0c;是 Vuex 状态管理工具的替代品 2. 手动添加Pinia到Vue项目 后面在实际开发项目的时候&#xff0c;Pinia可以在项目创建时自动添加&#xff0c;现…

PDI/Kettle-9.4.0.0-343源码下载及编译

目录 &#x1f351;一、概要&#x1f34a;最新版本10.x&#xff08;2023-11-30&#xff09; &#x1f351;二、下载&#x1f351;三、编译&#x1f34a;3.1、导入开发工具&#x1f34a;3.2、开始编译&#x1f34a;3.3、编译报错&#x1f34a;3.4、报错原因&#xff1a;jdk版本低…

if - else 实现点击展开 / 折叠

在前端开发过程中&#xff0c;我们经常需要使用到点击展开/折叠的按钮。 此案例是一个数组嵌套数组的效果展示&#xff0c;使用的是v-if else 来实现的展开效果。 一、实现方法 if...else&#xff1a;当指定条件为真&#xff0c;if 语句会执行一段语句。如果条件为假&#x…

2023/12/11 作业

1.思维导图 2.作业 成果&#xff1a; 第一个头文件 #ifndef TEST3GET_H #define TEST3GET_H #include <QWidget> #include<QMessageBox> QT_BEGIN_NAMESPACE namespace Ui { class test3get; } QT_END_NAMESPACE class test3get : public QWidget { Q_OBJE…

用docker创建jmeter容器,如何实现性能测试?

用 docker 创建 jmeter 容器, 实现性能测试 我们都知道&#xff0c;jmeter可以做接口测试&#xff0c;也可以用于性能测试&#xff0c;现在企业中性能测试也大多使用jmeter。docker是最近这些年流行起来的容器部署工具&#xff0c;可以创建一个容器&#xff0c;然后把项目放到…

chrome浏览器使用flash player

今天用chrome打开学校校园网&#xff0c;显示不出来成绩单提示如下&#xff1a; 结果下了也没用。 Chrome浏览器在2020年12月已经停止支持Flash Player插件&#xff0c;所以无法在Chrome浏览器上使用Flash Player。 使用其他浏览器 如果之前安装了Flash Player插件的小伙伴&…

多维时序 | MATLAB实现SAO-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测

多维时序 | MATLAB实现SAO-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实现SAO-CNN-BiGRU-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现SAO-CNN-B…

关于引用unpkg.com的mars3d相关依赖文件报错无法请求的说明

问题来源&#xff1a; 1.关于引用unpkg.com的mars3d相关依赖文件报错无法请求的说明 说明&#xff1a; 1.最近npm、unpkeg都访问异常&#xff0c;可能是unpkg.com等国外的服务器不稳定导致的请求未响应。 解决方案&#xff1a; 1.请切换静态文件引入的方式请求相关资源。参…

Science Robotics 挖掘机升级智能机器人,充分使用当地材料自主搭建石墙和土墙

建筑业对人类生产力至关重要&#xff0c;但需要实质性创新来满足不断增长的需求并减少其对环境的严重影响。建筑业是世界上最大的经济部门之一&#xff0c;占全球国内生产总值的13%。推而广之&#xff0c;它几乎是所有其他行业的重要组成部分&#xff1a;建筑业负责运输和农业基…

定时补偿方案

1&#xff1a;需求描述 支持NVR升级后通道数变更&#xff0c;完成升级后&#xff0c;设备SDK上报通道数量给A平台&#xff0c;A平台将NVR通道数量同步给B平台&#xff0c;B平台自动调用C平台接口&#xff0c;同步通道数量给C平台&#xff0c;C平台重新生成通道序列号&#xff…

PHP 之道(PHP The Right Way 中文版)

PHP 之道&#xff08;PHP The Right Way 中文版&#xff09;

HeartBeat监控Mysql状态

目录 一、概述 二、 安装部署 三、配置 四、启动服务 五、查看数据 一、概述 使用heartbeat可以实现在kibana界面对 Mysql 服务存活状态进行观察&#xff0c;如有必要&#xff0c;也可在服务宕机后立即向相关人员发送邮件通知 二、 安装部署 参照章节&#xff1a;监控组件…

液态二氧化碳储存罐远程无线监测系统

二氧化碳强化石油开采技术&#xff0c;须先深入了解石油储层的地质特征和二氧化碳的作用机制。现场有8辆二氧化碳罐装车&#xff0c;每辆罐车上有4台液态二氧化碳储罐&#xff0c;每台罐的尾部都装有一台西门子S7-200 smart PLC。在注入二氧化碳的过程中&#xff0c;中控室S7-1…

租一台服务器多少钱决定服务器的价格因素有哪些

租一台服务器多少钱决定服务器的价格因素有哪些 大家好我是艾西&#xff0c;服务器这个名词对于不从业网络行业的人们看说肯定还是比较陌生的。在21世纪这个时代发展迅速的年代服务器在现实生活中是不可缺少的一环&#xff0c;平时大家上网浏览自己想要查询的信息等都是需要服…

terser

环境&#xff1a; 一、使用vueCli创建的项目的vue.config.js 添加terser配置 验证了在打包后生成的.js文件中确实没有了console.log() 这里的.js.map可以省略&#xff0c;公司里代码打包后就没有.js.map文件了 要配置下除去.js.map文件或者统一分到.map文件夹里 二、vite 安…

抓紧收藏!软考个税抵扣3600元详细操作流程

软考证书有很多项福利政策&#xff0c;“个税补贴 ”是很容易被忽略的一项&#xff0c;但其实这一项也是实打实的资金补贴。 持有软考证书可在线上申请&#xff0c;按照3600定额扣除。 具体操作流程&#xff0c;接下来详细说明。 01 政策依据 根据《个人所得税专项附加扣除暂…

极兔速递查询,极兔速递单号查询,筛选出指定派件员的单号

批量查询极兔速递单号的物流信息&#xff0c;并将指定派件员的单号筛选出来。 所需工具&#xff1a; 一个【快递批量查询高手】软件 极兔速递单号若干 操作步骤&#xff1a; 步骤1&#xff1a;运行【快递批量查询高手】软件&#xff0c;第一次使用的朋友记得先注册&#xff…

ThreadLocal 本地线程变量详解

概述 ThreadLocal 意为本地线程变量&#xff0c;即该变量只属于当前线程&#xff0c;对其他线程隔离 我们知道&#xff0c;一个普通变量如果被多线程访问会存在存在线程安全问题&#xff0c;这时我们可以使用 Synchronize 来保证该变量某一时刻只能有一个线程访问&#xff0c…

vector类

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟悉vector库 > 毒鸡汤&#xff1a;从人生低谷…