springboot集成security(认证)

news2024/11/19 19:37:58

目录

  • 1. 依赖
  • 2. 自定义登录逻辑
    • 1. 数据库查询
    • 2. security认证
      • 1. loadUserByUsername
      • 2. PasswordEncorder(不加密)
      • 3. MD5加密数据库密码
      • 4. PasswordEncorder(加密)
      • 5. BCryptPasswordEncoder
      • 6. 认证流程(图)
  • 3. 自定义登录界面
    • 1. 界面
    • 2. security配置类
      • 1. 配置默认登录界面
      • 2. 配置拦截与放行
      • 3. 登录成功后的处理方案
      • 4. 登陆失败后的处理方案

1. 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

加入 spring-boot-starter-security 依赖之后,不需要任何配置,启动项目,就会出现一个登录界面:

  • 任何访问请求都会被拦截到 /login,并且要求认证后才能访问,请求方式是 Get

  • 默认 usernameuser,每次启动都会在控制台输出一个 128Byte 的 password

  • 可通过配置文件修改默认的 usernamepassword (静态用户,适用于内部网络认证)

    spring:
      # default login url: /login
      security:
        user:
          name: dev
          # default size: 128 Byte
          password: admin
    

2. 自定义登录逻辑


1. 数据库查询

表、实体类:

@Data
public class User {
    private Integer id;
    private String name;
    private String password;
}

配置文件:

# application name
spring:
  # mysql
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spbt?serverTimezone=Asia/Shanghai
    username: root
    password: admin
# web service port
server:
  port: 8088
mybatis-plus:
  type-aliases-package: com.chenjy.security_demo.dto
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

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.chenjy.security_demo.mapper.UserMapper">
    <select id="getUserByName" parameterType="string" resultType="user">
        select
               id,
               name,
               password
        from
             user
        <where>
            <if test="name != null and name != ''">
                name = #{name}
            </if>
        </where>
    </select>
</mapper>
@Mapper
public interface UserMapper {
    User getUserByName(String name);
    List<User> getUserByName();
}

启动类加上 @MapperScan 注解


service:

public interface UserService {
    User getUserByName(String name);
    List<User> getUserByName();
}
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper userMapper;

    @Override
    public User getUserByName(String name) {
        return userMapper.getUserByName(name);
    }

    @Override
    public List<User> getUserByName() {
        return userMapper.getUserByName();
    }
}

2. security认证

要自定义 security 认证逻辑,就需要定义一个服务对象来实现 UserDetailsService 接口,并重写 loadUserByUsername 方法。


1. loadUserByUsername

loadUserByUsername

  • security 唯一的认证方法
  • 查询用户失败,会抛出 UsernameNotFoundException
@Component
public class UserDetailsServiceImpl implements UserDetailsService {
    @Resource
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        com.chenjy.security_demo.dto.User user = userService.getUserByName(username);
        if (user == null) {
            throw  new UsernameNotFoundException("用户名错误");
        }
        // 匹配用户密码
        // org.springframework.security.core.userdetails.User
         User res = new User(username, user.getPassword(), AuthorityUtils.createAuthorityList());
        return res;
    }
}
  • org.springframework.security.core.userdetails.User

  • loadUserByUsername 需要返回一个 UserDetails 的实现类,可以直接使用 User,其有两个构造器:

    public User(String username, String password, Collection<? extends GrantedAuthority> authorities)
    public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) 
    
    • username 用户名
    • password 用户密码
    • authorities 权限集合
  • security 内部会自动进行密码匹配

这时候直接启动项目,会报错:

*java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"*

这是因为 security 5.X 需要提供一个PasswordEncorder的实例


2. PasswordEncorder(不加密)

security 内部进行密码匹配的时候,一定要进行加密和解密处理。要求 IOC 容器中,必须要存在一个 PasswordEncorder 对象,提供加密和解密逻辑。

  • 可以直接客户端明文,数据库解密来匹配验证
  • 也可以客户端加密,数据库直接密文来匹配验证

因为我的数据库密码并未进行加密,所以这里,我们就直接返回字符串进行明文比对就行了。

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.equals(encodedPassword);
    }
}

encode 对页面收集到的密码进行加密,然后将加密好的密文交给 matches,与数据库密码进行比对。


3. MD5加密数据库密码

自定义加密工具类

public class PwdEncode {

    /**
     * @Description 收集
     * @param pwd
     * @return String
    */
    public static String encode(String pwd) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
        char[] charArray = pwd.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++) {
            byteArray[i] = (byte) charArray[i];
        }
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16){
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * @Description 解密
     * 一次加密之后,需要两次解密
     * @param pwd
     * @return String
    */
    public static String decrypt(String pwd) {
        char[] a = pwd.toCharArray();
        for (int i = 0; i < a.length; i++) {
            a[i] = (char) (a[i] ^ 't');
        }
        String s = new String(a);
        return s;
    }

    public static void main(String[] args) {
        String s = "123456";
        System.out.println("原始:" + s);
        System.out.println("MD5后:" + encode(s));
        System.out.println("加密的:" + decrypt(s));
        System.out.println("解密的:" + decrypt(decrypt(s)));
    }
}

然后修改数据库密码。


4. PasswordEncorder(加密)

@Component
public class MyPasswordEncoder implements PasswordEncoder {
    /**
     * @Description 收集页面的密码
     * @param rawPassword
     * @return String
    */
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    /**
     * @Description 匹配逻辑
     * @param rawPassword 明文,页面收集的密码
     * @param encodedPassword 密文,存储在数据源中的密码
     * @return boolean
    */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return PwdEncode.encode(rawPassword.toString()).equals(encodedPassword);
    }
}

注意: MD5解密很简单——https://www.cmd5.com/,所以不推荐使用MD5加密。


5. BCryptPasswordEncoder

鉴于MD5加密的不安全性,所以建议使用 security 自带的加密工具类 —— BCryptPasswordEncoder

  • 同一明文,加密两次,输出不同

        public static void main(String[] args) {
            String s = "123456";
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();;
            System.out.println("原始: " + s);
            System.out.println("加密1: " + encoder.encode(s));
            System.out.println("加密1: " + encoder.encode(s));
        }
    
  • 可使用 matches 验证明文与密文是否相同:

    System.out.println(encoder.matches(s, encoder.encode(s)));
    

BCryptPasswordEncoder使用bcrypt算法对密码进行加密,同时会为密码加上“盐”,开发者不需要自己加“盐”,即使相同的明文字段生成的加密字符串也不同。匹配时,从密文中取出“盐”,用该盐值加密明文和最终密文作对比。


BCryptPasswordEncoder的默认强度为10,开发者可以根据自己服务器的速度进行调整,以确保密码验证的时间约为1秒(官方建议)

BCryptPasswordEncoder encoder_pro = new BCryptPasswordEncoder(15);
System.out.println("加密2: " + encoder_pro.encode(s));

注册 BCryptPasswordEncoder

  • 取消MyPasswordEncoder的 @Component 注解

  • 新建一个配置类,注册 BCryptPasswordEncoder

    @Configuration
    public class SecurityConf {
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }
    

6. 认证流程(图)


3. 自定义登录界面


1. 界面

依赖:

        <!--thymeleaf-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

resources/templates/login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>登录</title>
</head>
<body>
<div class="main">
  <div class="title">
    <span>密码登录</span>
  </div>

  <div class="title-msg">
    <span>请输入登录账户和密码</span>
  </div>

  <form class="login-form" method="post" novalidate th:action="@{/myLogin}">
    <!--输入框-->
    <div class="input-content">
      <!--autoFocus-->
      <div>
        <input type="text"
               autocomplete="off"
               placeholder="用户名"
               name="name"
               required/>
      </div>

      <div style="margin-top: 16px">
        <input type="password"
               autocomplete="off"
               placeholder="登录密码"
               name="password"
               required
               maxlength="32"/>
      </div>
    </div>

    <!--登入按钮-->
    <div style="text-align: center">
      <button type="submit" class="enter-btn" >登录</button>
    </div>
  </form>

</div>
</body>
<style>
  body{
    background: #426258;
  }
  *{
    padding: 0;
    margin: 0;
  }

  .main {
    padding-left: 25px;
    padding-right: 25px;
    padding-top: 15px;
    width: 350px;
    height: 350px;
    background: #FFFFFF;
    /*以下css用于让登录表单垂直居中在界面,可删除*/
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -175px auto 0 -175px;
  }

  .title {
    width: 100%;
    height: 40px;
    line-height: 40px;
  }

  .title span {
    font-size: 18px;
    color: #353f42;
  }

  .title-msg {
    width: 100%;
    height: 64px;
    line-height: 64px;
  }

  .title:hover{
    cursor: default	;
  }

  .title-msg:hover{
    cursor: default	;
  }

  .title-msg span {
    font-size: 12px;
    color: #707472;
  }

  .input-content {
    width: 100%;
    height: 120px;
  }

  .input-content input {
    width: 330px;
    height: 40px;
    border: 1px solid #dad9d6;
    background: #ffffff;
    padding-left: 10px;
    padding-right: 10px;
  }

  .enter-btn {
    width: 350px;
    height: 40px;
    color: #fff;
    background: #0bc5de;
    line-height: 40px;
    text-align: center;
    border: 0px;
  }

  .enter-btn:hover {
    cursor:pointer;
    background: #1db5c9;
  }
</style>

</html>

注意: security 处理登录请求的方式一定是 POST

resources/templates/main.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>主页</title>
</head>
<body>
<div class="con">
    <h1><span style="color: #67865d">登陆成功</span></h1>
    <br>
    <div class="component">
    </div>
    <br>
</div>
</body>
<style>
    body {
        background-color: #c1d3d0;
    }
    .con {
        text-align: center;
    }
    .component div {
        font-size: 32px;
    }
    .component span {
        color: #FFFFFF;
        font-size: 32px;
    }
</style>

2. security配置类

extends WebSecurityConfigurerAdapter 并重写 configure(HttpSecurity http)


1. 配置默认登录界面

@Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {
    /**
     * @Description 注册BCryptPasswordEncoder到容器
     * @return BCryptPasswordEncoder
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * @Description 重写父类型中的配置逻辑
     * @param http 基于http协议的security配置对象,包含所有security相关配置逻辑
     * @throws Exception 配置出错会抛出异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置请求相关内容
        http
                .formLogin()
                .loginPage("/myLogin") // 登录页面地址,默认 /login, 这里的地址要由 controller 接口地址决定
                .usernameParameter("name") // 登录页面用户名字段
                .passwordParameter("password"); // 登录页面用户密码字段

        // 关闭csrf
        http.csrf().disable();
    }
}

然后启动:

  • 可发现,并不会拦截请求
  • 一旦自定义配置,所有的默认认证流程会全部清空

2. 配置拦截与放行

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置请求相关内容
        http
                .formLogin()
                .loginPage("/myLogin") // 登录页面地址,默认 /login, 这里的地址要由 controller 接口地址决定
                .usernameParameter("name") // 登录页面用户名字段
                .passwordParameter("password") // 登录页面用户密码字段
                .and()
                .authorizeRequests()
                .antMatchers("/myLogin")
                .permitAll() // /myLogin 不需要认证,可直接访问
                .anyRequest()
                .authenticated(); // 其他请求都需要认证

        // 关闭csrf
        http.csrf().disable();
    }
  • formLogin 配置登录表单
  • loginPage 配置登录界面
  • usernameParameter 登录页面用户字段名
  • passwordParameter 登录页面用户密码字段名
  • and and
  • authorizeRequests 配置拦截和放行的url
  • antMatchers 匹配多个路径,用 , 隔开
  • permitAll 前面匹配的路径全部放行
  • anyRequest 其他路径
  • authenticated 匹配的路径需要认证

注意:

  • 拦截一定要写在放行之后
  • 如果不配置 usernameParameterpasswordParameter,那么请求参数名必须是 usernamepassword,不然 security 无法识别

3. 登录成功后的处理方案

security 默认访问成功后跳转到 “/” ,现在修改配置,使其跳转到 main


方案一: 使用 successForwardUrl,请求转发 —— POST 请求。

    @RequestMapping("main")
    public String main() {
        return "main";
    }
.successForwardUrl("/main") // 登录成功跳转页面

方案二: 使用 successForwardUrl,响应重定向(重新请求) —— GET 请求。

.defaultSuccessUrl("http://127.0.0.1:8088/main") // 响应重定向(GET)

注意1: 因为 successForwardUrl 是重新发起请求,所以需要传入一个绝对地址才能成功定向。

注意2: 最好传入一个参数 alwaysUse (代表一定使用这个重定向地址),防止出现错误。

.defaultSuccessUrl("http://127.0.0.1:8088/main", true) 

方案三: 使用 successHandler,自定义认证成功后的请求处理逻辑(转发、重定向都可自定义)。

successForwardUrldefaultSuccessUrl 内部就是用的 successHandler 的实现类,进行控制成功后交给哪个方法处理。

自定义请求成功处理器: implements AuthenticationSuccessHandler

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SuccessHandler implements AuthenticationSuccessHandler {
    private String url;
    public boolean isRedirect;
    
    /**
     * @Description
     * @param request 请求对象  请求转发——request.getRequestDispatcher(url).forward(request, response);
     * @param response 响应对象 重定向——response.sendRedirect(url);
     * @param authentication 认证成功后的对象,包含用户名和权限信息(防止信息泄露,所以没带密码)
    */
    @Override
    public void onAuthenticationSuccess( HttpServletRequest request,
            HttpServletResponse response,
            Authentication authentication) throws IOException, ServletException {
        if (isRedirect) {
            response.sendRedirect(url);
        } else {
            request.getRequestDispatcher(url).forward(request, response);
        }
    }
}
.successHandler(new SuccessHandler("/main", false)) // 使用自定义请求处理控制逻辑

4. 登陆失败后的处理方案

登陆失败后,默认跳转到 /login?error,现在自定义一个登录失败的处理。


方案一: 使用 failureForwardUrl,登录失败请求转发 —— POST

    @RequestMapping("fail")
    public String fail(Model model) {
        model.addAttribute("fail");
        return "login";
    }
.failureForwardUrl("/fail") // 登录失败转发
<span th:if="${fail} == 'true'" style="color: red">登陆失败</span>

方案二: 使用 failureForwardUrl,响应重定向。

.failureUrl("/fail") // 响应重定向
...
.antMatchers("/myLogin", "/fail")
.permitAll() // /myLogin,/fail 不需要认证,可直接访问

重定向是重新发请求, 所以需要在权限配置中放行 /fail


方案三: 使用 failureForwardUrl,自定义处理器。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class FailureHandler implements AuthenticationFailureHandler {
    private String url;
    private boolean isRedirect;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception) throws IOException, ServletException {
        if (isRedirect) {
            response.sendRedirect(url);
        } else {
            request.getRequestDispatcher(url).forward(request, response);
        }
    }
}
.failureHandler(new FailureHandler("/fail", true)) //

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

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

相关文章

【POJ No. 3764】 最长xor 路径 The xor-longest Path

【POJ No. 3764】 最长xor 路径 The xor-longest Path 北大OJ 题目地址 【题意】 在边权树中&#xff0c;路径p的xor长度被定义为路径p上边权的 &#xff0c;⊕是xor运算符&#xff0c;表示异或。若一个路径有最大的xor长度&#xff0c;则该路径是xor最长的路径。给定n 个节点…

如何正确使用Airtest报告插件?报告小tips上线

1. 前言 在使用Airtest做自动化测试时&#xff0c;默认生成的报告&#xff0c;其实是airtest的专属报告。 它对于poco语句&#xff08;控件测试场景&#xff09;、airtest-selenium语句&#xff08;web测试场景&#xff09;的支持不够完善&#xff0c;因此我们需要用 插件的形…

Shell脚本学习指南(六)——输入/输出、文件与命令执行

文章目录前言标准输入、标准输出与标准错误输出使用read读取行关于重定向额外的重定向运算符文件描述符处理printf的完整介绍波浪号展开与通配符波浪号展开使用通配符命令替换为head命令使用sed创建邮件列表简易数学&#xff1a;expr引用执行顺序与evaleval语句subShell与代码块…

CAS:210236-90-1,Fluorescein Tyramide,荧光素酪胺绿色荧光试剂

一&#xff1a;产品描述 1、名称 英文&#xff1a;Fluorescein Tyramide 中文&#xff1a;荧光素酪胺 2、CAS编号&#xff1a;210236-90-1 3、分子式&#xff1a;C29H21NO7 4、分子量&#xff1a;495.49 6、储存&#xff1a; -20℃可长期保存&#xff0c;注意避光并置于…

个人微信号API接口,微信机器人

自定义的微信机器人&#xff0c;需求是可以自己批量添加好友、批量打标签等进行好友管理&#xff0c;社群管理需要自动聊天&#xff0c;自动回复&#xff0c;发朋友圈&#xff0c;转发语音&#xff0c;以及定时群发等&#xff0c;还可以提取聊天内容&#xff0c;进行数据汇总&a…

美妆商场系统/在线购物系统/美妆销售系统

摘 要 本毕业设计的内容是设计并且实现一个基于JSP技术的美妆商场系统。它是在Windows下&#xff0c;以MYSQL为数据库开发平台&#xff0c;Tomcat网络信息服务作为应用服务器。美妆商场系统的功能已基本实现&#xff0c;主要包括个人中心、用户管理、商品中心管理、商品类型管…

故障分析 | MySQL 使用 load data 导入数据错误的一个场景

作者&#xff1a;刘晨 网名 bisal &#xff0c;具有十年以上的应用运维工作经验&#xff0c;目前主要从事数据库应用研发能力提升和技术管理相关的工作&#xff0c;Oracle ACE &#xff0c;腾讯云TVP&#xff0c;拥有 Oracle OCM & OCP 、EXIN DevOps Master 、SCJP 等国际…

HashSet、HashMap、LinkedHashMap、HashTable、ConcurrentHashMap源码阅读笔记

目录一、HashSet二、HashMap三、LinkedHashMap四、HashTable五、ConcurrentHashMap一、HashSet 首先&#xff0c;让我们先从最简单的开始&#xff0c;总的来说&#xff0c;hashSet可以说是建立在hashMap上的变种应用。 通过阅读hashSet的源码我们可以得出以下结论&#xff1a;…

1556_AURIX_TC275_复位系统控制单元

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这是上一部分没有看完的CCU的核心寄存器的存储映射信息&#xff0c;只是一个汇总&#xff0c;没有需要着重处理的分析点。 复位控制单元涉及到的几个大功能&#xff1a;基本复位、外部复位…

订单服务-----遇到的问题及解决方案

订单服务的问题及解决方案 问题1&#xff1a;Feign远程调用时丢失请求头 出现这个Feign远程调用时丢失请求头的问题是因为Feign在远程调用的时候会创建一个新的请求&#xff0c;但是这个新的请求里啥都没有&#xff0c;没有cookie值&#xff0c;而这个cookie值里有成功登录后的…

投稿MDPI旗下期刊的一些心得和记录

投稿历程&#xff1a; 09.02 提交初稿 under review 09.05 分配助理编辑&#xff0c;论文送审 10.13 第一轮审稿完毕&#xff0c;大修&#xff0c;两个审稿人 Pending Major Revisions 10.16 语言问题需要润色&#xff0c;使用MDPI润色机构 10.19 重新提交修改稿 Resubmitt…

【Axure教程】中继器版穿梭表格

表格是我们系统中常用的组件&#xff0c;穿梭表格就是使用直观方式在两个表格中移动数据&#xff0c;实现数据的流动。今天作者就教大家如何在Axure里用中继器制作出表格穿梭的效果&#xff1a; 1、选中效果&#xff1a;鼠标点击表格中所在的行&#xff0c;可以选中该行数据 …

【Vue 快速入门系列】组件的基本使用

文章目录一、组件的概念二、非单文件组件三、单文件组件1.main.js2.App.vue3.school.vue4.student.vue5.index.html四、内容补充及原理剖析1.组件命名注意点2.组件嵌套内置关系3.重要的内置关系一、组件的概念 组件&#xff08;Component&#xff09;是 Vue.js 最强大的功能之…

Java日期与时间

时间与日期DateSimpleDateFormatCalendarJDK8新增日期类概述LocalDate、LocalTime、LocalDateTimeInstant时间戳DateTimeFormatterDuration/PeriodChronoUnitDate Date类概述 Date类的对象在Java中代表的是当前所在系统的此刻日期时间。 Date的构造器 Date的常用方法 Date类…

2022最新 MySQL事务面试题合集

小熊学Java网站&#xff1a;https://javaxiaobear.gitee.io/&#xff0c;每周持续更新干货&#xff0c;建议收藏&#xff01; 61、什么是数据库事务&#xff1f;事务的特性是什么&#xff1f; 事务&#xff1a; 是数据库操作的最小工作单元&#xff0c;是作为单个逻辑工作单元执…

微服务框架 SpringCloud微服务架构 25 黑马旅游案例 25.2 条件过滤

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构25 黑马旅游案例25.2 条件过滤25.2.1 直接开干25 黑马旅游案例 25.2 条件…

怎么把磁盘合并成一个?两个硬盘分区合并,如何硬盘分区合并

怎么把磁盘合并成一个&#xff1f;有时候电脑分区有点多&#xff0c;想要给硬盘的分区合并不知道如何操作&#xff0c;如何在不影响系统正常使用的情况下合并分区呢&#xff1f;本篇文章将详细解答这个问题。 之前有个客户提出这样一个问题&#xff0c;那就是在安装完windows10…

【CSS3】text-shadow/text-overflow,边框图片,透明度,小米轮播图子菜单另一种实现

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录text-shadowtext-overflow边框图片border-image-sourceborder-image-sliceborder-image-widthbor…

数据智仓功能介绍(一)

数据智仓英文名称为Smart Data Warehouse&#xff0c;可简写为SDW。数据智仓是JVS整体企业数字化解决方案的核心能力&#xff0c;与JVS的低代码开发套件平级。数据仓库的目的是构建面向分析的集成化数据环境&#xff0c;为企业提供基于数据的决策支持&#xff08;Decision Supp…

世界杯论文

♥️作者&#xff1a;小刘在C站 ♥️每天分享云计算网络运维课堂笔记&#xff0c;疫情之下&#xff0c;你我素未谋面&#xff0c;但你一定要平平安安&#xff0c;一 起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;愿…