spring security 自定义图形验证码(web/前后端分离)

news2024/9/23 13:29:27

一、准备工作

1.1 导入pom 所需依赖

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <!-- <version>2.7.18</version>-->
    </parent>


 <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

        <!-- thymeleaf 相关依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.16</version>
        </dependency>
    </dependencies>


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

1.2 常量类


    /**
     *  图形验证码
     */
    public static final String SESSION_IMAGE = "session-verifyimage";


    /**
     *  登录的url
     */
    public static final String LOGIN_URL = "/user/login";

二、web端自定义图像验证码

2.1  配置security 配置文件

package com.fasion.config;

import com.fasion.security.LoginImageVerifyFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.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;

/**
 * @Author: LQ
 * @Date 2024/8/26 20:49
 * @Description: security 配置
 */
@Configuration
@Slf4j
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    /**
     * 自定义数据源,从内存中,后期自己写一个mybatis 从数据库查询
     * @throws Exception
     */
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(User.withUsername("test").password("{noop}12345").authorities("admin").build());
        return userDetailsManager;
    }

    /**
     *  自定义authenticationManager 管理器,将自定义的数据源加到其中
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService());
    }


    /**
     *  用自己的认证管理器
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    /**
     *  登录   自定义过滤器
     * @return
     */
    @Bean
    public LoginImageVerifyFilter loginImageVerifyFilter() throws Exception {
        LoginImageVerifyFilter verifyFilter = new LoginImageVerifyFilter();
        verifyFilter.setFilterProcessesUrl("/login.do");// 认证地址
        verifyFilter.setUsernameParameter("loginId");
        verifyFilter.setPasswordParameter("loginPwd");
        verifyFilter.setVerifyImageParams("imageCode");// 图像验证码的参数

        // 认证成功处理逻辑
        verifyFilter.setAuthenticationSuccessHandler((req,resp,auth) -> {
            resp.sendRedirect("/main.html");
        });
        // 认证失败处理逻辑
        verifyFilter.setAuthenticationFailureHandler((req,resp,ex) -> {
            log.info("ex信息:{}",ex.getMessage());
            req.getSession().setAttribute("errMsg",ex.getMessage());
            resp.sendRedirect("/");// 跳到首页
        });

        // 自定义自己的管理器
        verifyFilter.setAuthenticationManager(authenticationManagerBean());

        return verifyFilter;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/").permitAll()   //放行登录首页
                .mvcMatchers("/kap.jpg").permitAll() // 放行图像验证码
                //.mvcMatchers("/static/**").permitAll() // 静态目录放行
                .anyRequest()
                .authenticated()
                .and()
                .formLogin() //表单设置
                .and()
                .csrf().disable();// 关闭csrf 防护

        // 自定义过滤器替换默认的
        http.addFilterAt(loginImageVerifyFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

2.2  web端配置

package com.fasion.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @Author: LQ
 * @Date 2024/8/26 20:55
 * @Description:传统web开发
 */
@Configuration
public class WebConfiguration implements WebMvcConfigurer {


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
        registry.addViewController("/main.html").setViewName("main");
    }

//    @Override
//    public void addResourceHandlers(ResourceHandlerRegistry registry) {
//        registry.addResourceHandler("/static/").addResourceLocations("/static/**");
//    }
}

2.3 图片验证码工具生成类

该类是利用hutool 包提供的工具类生成图片验证码,具体请参考文档   概述 | Hutool

,由浏览器直接写出图片,该地方如果是集群环境可以将图形验证码的code存到redis中,登录时候再取出来验证;

package com.fasion.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
import com.fasion.constants.ComConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;

/**
 *  图像验证码
 */
@Controller
@Slf4j
public class ComController {

    /**
     *  获取图像验证码
     * @param response
     */
    @RequestMapping("kap.jpg")
    public void getVerifyImage(HttpSession session,HttpServletResponse response) {
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        //定义图形验证码的长、宽、验证码位数、干扰线数量
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(120, 40,4,19);
        lineCaptcha.setGenerator(randomGenerator);
        lineCaptcha.createCode();
        //设置背景颜色
        lineCaptcha.setBackground(new Color(249, 251, 220));
        //生成四位验证码
        String code = lineCaptcha.getCode();
        log.info("图形验证码生成成功:{}",code);
        session.setAttribute(ComConstants.SESSION_IMAGE,code);

        response.setContentType("image/jpeg");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        try {
            lineCaptcha.write(response.getOutputStream());
        } catch (IOException e) {
            log.error("图像验证码获取失败:",e);
        }
    }


}

2.4 验证码过滤器

package com.fasion.security;

import com.fasion.constants.ComConstants;
import com.fasion.exception.CustomerException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.ObjectUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @Author: LQ
 * @Date 2024/8/26 20:58
 * @Description: 登录验证,图形验证码
 */
@Slf4j
public class LoginImageVerifyFilter extends UsernamePasswordAuthenticationFilter {

    private String verifyImageParams = "captcha";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        // 获取图像验证码
        String imageCode = request.getParameter(getVerifyImageParams());
        String realCode = (String) request.getSession().getAttribute(ComConstants.SESSION_IMAGE);
        log.info("传过来的图像验证码为:{},session中实际的是:{}",imageCode,realCode);

        if (!ObjectUtils.isEmpty(imageCode) && !ObjectUtils.isEmpty(realCode) &&
                imageCode.equalsIgnoreCase(realCode)) {
            // 调用父类的认证方法
            return super.attemptAuthentication(request,response);
        }

        throw new CustomerException("图像验证码不正确!!!");
    }

    public String getVerifyImageParams() {
        return verifyImageParams;
    }

    public void setVerifyImageParams(String verifyImageParams) {
        this.verifyImageParams = verifyImageParams;
    }
}

2.5 自定义异常类

package com.fasion.exception;

import org.springframework.security.core.AuthenticationException;

/**
 * @Author: LQ
 * @Date 2024/8/26 21:07
 * @Description: 自定义异常
 */
public class CustomerException extends AuthenticationException {


    public CustomerException(String msg) {
        super(msg);
    }
}

2.6 前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录页</title>
    <!-- 引入样式 -->
    <style type="text/css">
        #app{width: 600px;margin: 28px auto 10px }
        img{cursor: pointer;}
    </style>
</head>
<body>
   <div id="app">
       <form th:action="@{/login.do}" method="post" >
           <div>
               <label>用户名:</label>
               <input type="text" name="loginId">
           </div>
           <div>
               <label>密码:</label>
               <input type="text" name="loginPwd" >
           </div>
           <div>
               <label>图像验证码:</label>
               <input type="text" name="imageCode">
               <img src="/kap.jpg">
           </div>
           <div>
               <label>错误信息:<span th:text="${session.errMsg}"></span></label>
           </div>
           <div>
              <button type="submit" name="登录">登录</button>
           </div>
       </form>
   </div>

</body>
</html>

2.6.1 前端效果

2.6.2 登录失败展示效果

2.6.3 登录成功


三、前后端分离自定义验证码(json数据格式)

3.1  security 配置

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    /**
     *  自定义数据源
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
        userDetailsManager.createUser(User.withUsername("test").password("{noop}1234").authorities("admin").build());
        return userDetailsManager;
    }

    /**
     *  配置数据源
     *
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(userDetailsService());
    }


    /**
     *  显示指定自己的 AuthenticationManager
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    @Bean
    public LoginVerifyCaptchaFilter loginVerifyImgFilter() throws Exception {
        LoginVerifyCaptchaFilter filter = new LoginVerifyCaptchaFilter();
        filter.setImageParams("verifyImg");// 图形验证码请求参数
        filter.setUsernameParameter("loginId");
        filter.setPasswordParameter("pwd");
        filter.setFilterProcessesUrl("/login.do");
        // 成功的响应
        filter.setAuthenticationSuccessHandler((req,resp,auth) -> {
            Map<String,Object> resMap = new HashMap<>();
            resMap.put("code","0000");
            resMap.put("msg","登录成功!");
            resMap.put("data",auth);
            WebUtils.writeJson(resp,resMap);
        });
        //失败的处理
        filter.setAuthenticationFailureHandler((req,resp,ex) -> {
            Map<String,Object> resMap = new HashMap<>();
            resMap.put("code","5001");
            resMap.put("msg",ex.getMessage());
            WebUtils.writeJson(resp,resMap);
        });

        // 指定自己的authenticationmanager
        filter.setAuthenticationManager(authenticationManagerBean());

        return filter;
    }




    /**
     *  springsecurity 配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .mvcMatchers("/comm/kaptcha.jpg").permitAll()// 该路径放行
                .mvcMatchers("/").permitAll()// 入口页放行
                .anyRequest().authenticated()// 所有请求都需要认证
                .and()
                .formLogin()// 表单配置
                .loginPage("/")
                .and()
                .csrf().disable();//关闭csrf 防护


        // 定义登录图形过滤器,替换掉UsernamePasswordAuthenticationFilter
        http.addFilterAt(loginVerifyImgFilter(), UsernamePasswordAuthenticationFilter.class);

    }

3.2 图片验证码base生成

@RestController
@Slf4j
public class CommController {


    /**
     * 获取图形验证码
     * @param session
     * @param response
     * @return
     */
    @RequestMapping("/comm/kaptcha.jpg")
    public Map<String,String> image(HttpSession session, HttpServletResponse response) {
        // 自定义纯数字的验证码(随机4位数字,可重复)
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(92, 40,4,10);
        lineCaptcha.setGenerator(randomGenerator);
        // 重新生成code
        lineCaptcha.createCode();
        // 获取
        String captchaCode = lineCaptcha.getCode();
        log.info("获取到验证码为:{}",captchaCode);
        session.setAttribute(ComConst.SESSION_CAPTCHA,captchaCode);

        // 转为base64
        String imageBase64 = lineCaptcha.getImageBase64();
        HashMap<String, String> resMap = MapUtil.newHashMap();
        resMap.put("code","0000");
        resMap.put("data",imageBase64);
        return resMap;
    }

}

3.2.1  postman 效果

一般由后台将图片转为base64后,前端再通过传过来的base64 评价 image/ 到 img标签的src 就可以显示出来;需要加上前缀:data:image/jpeg;base64, 后面再把返回的data中的结果拼接到后面

3.3 验证码过滤器(核心类)

该过滤器需要加到配置security 配置里面,用来替换到默认的 UsernamePasswordAuthenticationFilter 过来器,所以之前配置的

formLogin.loginPage("/") 
.loginProcessingUrl("/doLogin")    //form表单提交地址(POST)
//.defaultSuccessUrl("/main",true)    //登陆成功后跳转的页面,也可以通过Handler实现高度自定义
.successForwardUrl("/main")
这些配置实际都会失效
public class LoginVerifyCaptchaFilter extends UsernamePasswordAuthenticationFilter {


    private String imageParams = "verifyImg";


    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        Map<String,String> userMap = null;
        try {
            // 用户信息
            userMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 获取图形验证码
        String reqImgCode = userMap.get(getImageParams());
        String username = userMap.get(getUsernameParameter());
        String password = userMap.get(getPasswordParameter());
        // 获取session 的验证码
        String realCode = (String)request.getSession().getAttribute(ComConst.SESSION_CAPTCHA);

        // 图形验证码通过
        if (!ObjectUtils.isEmpty(reqImgCode) && !ObjectUtils.isEmpty(realCode) && realCode.equalsIgnoreCase(reqImgCode)) {
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
        throw new CustomerException("图形验证码错误!");
      //  return super.attemptAuthentication(request,response);
    }

    public String getImageParams() {
        return imageParams;
    }

    public void setImageParams(String imageParams) {
        this.imageParams = imageParams;
    }
}

3.4 新建一个测试类 

@RestController
public class HelloController {


    @RequestMapping("hello")
    public String hello() {
        return "hello web security ";
    }

}

3.5 验证结果

我们看到 hello 接口是受到保护的,没有认证是访问不了的

3.5.1  访问hello接口

这个时候登录成功后再将登录接口返回的cookie 信息放到hello接口中请求

3.6 增加异常处理

该地方是用来处理用户未登录,接口提示需要用户有认证信息,这个时候我们没有登录访问受限接口 hello 就会提示,请认证后再来请求接口,新增一个工具类,用于将写出json数据

/**
 * 写出json 数据
 *
 * @param response
 * @throws Exception
 */
public static void writeJson(HttpServletResponse response, Object object) {
    response.setContentType("application/json;charset=UTF-8");
    response.setCharacterEncoding("UTF-8");
    response.setHeader("Cache-Control", "no-cache");
    PrintWriter pw = null;
    try {
        pw = response.getWriter();
        pw.print(JSONUtil.toJsonStr(object));
        pw.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (pw != null) {
            pw.close();
        }
    }
}
.authenticationEntryPoint(((request, response, authException) -> {
                    // 判断是否有登录
                    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    if (authentication == null) {
                        WebUtils.writeJson(response,"请认证后再来请求接口");
                    } else {
                        WebUtils.writeJson(response,authException.getLocalizedMessage());
                    }
                }))

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

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

相关文章

代码随想录算法训练营第13天 |二叉树的学习

目录 二叉树 理论基础 二叉树的分类 1. 满二叉树 (Full Binary Tree) 2. 完全二叉树 (Complete Binary Tree) 3. 平衡二叉树 (Balanced Binary Tree) 5. 二叉搜索树 (Binary Search Tree, BST) 二叉树的存储 1. 链式存储 (Linked Representation) 2. 顺序存储 (Sequent…

废酸处理业务

废酸处理是指将工业生产过程中产生的废酸进行有效处理&#xff0c;以实现其回收利用或安全排放的过程。这一过程对于环境保护和资源节约具有重要意义。以下是对废酸处理的详细介绍&#xff1a; 一、废酸处理的必要性 废酸中含有大量的有害物质&#xff0c;如重金属离子、有机物…

SAP ERP与长城汽车EDI业务集成案例(SAP CPI平台)

一、项目背景 某智能座舱公司是国内领先的智能座舱领域科技公司&#xff0c;致力于成为智能网联行业变革的领导者和推动者&#xff0c;聚焦整车域控制器产品、智能网联软件产品和运营服务产品&#xff1b; 已建成首条先进的数智化域控制器生产线&#xff0c;为客户提供最优…

零基础学PLC的指令-沿指令(2)

扫描操作数的信号上升沿&#xff1a; 在触点分配的 "IN" 位上检测到正跳变&#xff08;0->1&#xff09;时&#xff0c;该触点的状态为 TRUE。该触点逻辑状态随后与能流输入状态组合以设置能流输出状态。P 触点可以放置在程序段中除分支结尾外的任何位置。 扫描…

【VUE入门级温故知新】一文向您详细介绍~组件注册(选项式API)

大家好&#xff0c;我是DX3906 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘大前端领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; 前面和大家分享了《如何从零开始创建一个 Vue 应用》 《VUE模板语法(超详细讲解)》 《一文向您详细介绍~Vu…

FTP服务器(服务名vsftpd,端口tcp/20和tcp/21)

目录 前言 配置文件 FTP服务器的配置 FTP服务的下载 配置防火墙 编辑配置文件 常用字段&#xff1a; 常用字段&#xff08;匿名用户&#xff09;&#xff1a; 常用字段&#xff08;系统用户&#xff09;&#xff1a; 指定系统用户访问&#xff1a; 编辑名单/etc/vsf…

AI预测体彩排3采取888=3策略+和值012路或胆码测试8月27日升级新模型预测第64弹

经过60多期的测试&#xff0c;当然有很多彩友也一直在观察我每天发的预测结果&#xff0c;得到了一个非常有价值的信息&#xff0c;那就是9码定位的命中率非常高&#xff0c;已到达90%的命中率&#xff0c;这给喜欢打私菜的朋友提供了极高价值的预测结果~当然了&#xff0c;大部…

【GD32 MCU 移植教程】从 GD32F303 移植到 GD32F503

1. 前言 GD32E503 系列是 GD 推出的 Cortex_M33 系列产品&#xff0c;该系列资源上与 GD32F303 兼容度非常 高&#xff0c;本应用笔记旨在帮助您快速将应用程序从 GD32F303 系列微控制器移植到 GD32E503 系列微 控制器。 2. 引脚兼容性 GD32F303 与 GD32E503…

C++研发笔记1——github注册文档

1、第一步&#xff1a;登录网站 GitHub: Let’s build from here GitHub 最新跳转页面如下&#xff1a; 2、选择“sign up”进行注册&#xff0c;并填写设置账户信息 3、创建账户成功之后需要进行再次登录 4、根据实际情况填写个人状态信息 登录完成后页面网站&#xff1a; 5…

大规模预训练语言模型的参数高效微调

人工智能咨询培训老师叶梓 转载标明出处 大规模预训练语言模型&#xff08;PLMs&#xff09;在特定下游任务上的微调和存储成本极高&#xff0c;这限制了它们在实际应用中的可行性。为了解决这一问题&#xff0c;来自清华大学和北京人工智能研究院的研究团队探索了一种优化模型…

[MRCTF2020]pyFlag(详解附送多个python脚本)

Hex&#xff1a; FF D9 5B 53 65 63 72 65 74 20 46 69 6C 65 20 50 61 72 74 20 31 3A 5D ASCII&#xff1a; [Secret File Part 1:] 发现Setsuna.jpg尾部有多余的一部分有左侧窗口pk头&a…

手把手教你GPT-SoVITS V2版本模型教程,内附整合包

首先需要声明的一点就是V1的模型能用在V2上面&#xff0c;但是V2的模型不能用在V1上&#xff0c;并且V1模型在V2上效果不佳&#xff01; 整合包下载地址&#xff1a; GPT-SoVITS V2整合包下载 https://klrvc.com/ GPT-SoVITS V2模型下载网 这次V2更新了以下功能 UVR5&#x…

超声波清洗机哪些品牌好用?小型超声波清洗机推荐

在日常生活中&#xff0c;诸如眼镜、项链和耳环之类的常用小物件&#xff0c;频繁的接触使得它们表面易吸附尘埃&#xff0c;尤其是缝隙里的污垢往往难以手动清除。此时&#xff0c;超声波清洗机成为了理想的清洁助手&#xff0c;它能深入细微之处&#xff0c;带来彻底的清洁体…

【设计模式-策略】

定义 策略模式是一种行为型设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以互相替换&#xff0c;且算法的变化不会影响到使用算法的客户。通过使用策略模式&#xff0c;算法可以在运行时根据需要动态地进行更换&#xff0c;从…

JAVA毕业设计164—基于Java+Springboot+vue3的汽车租赁管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的汽车租赁管理系统(源代码数据库)164 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、会员、管理员三种角色 1、用户&#xff1a…

破防了!软考小白们的春天,低起点也能赢在起跑线

软考通过率是否真的很低&#xff0c;可以通过官方数据来了解。 一、软考通过率是多少&#xff1f; 首先要说明的是&#xff0c;软考办并没有公布全国考试的通过率。但根据官方公布的报名人数和合格人数可以做一个预估。 浙江软考办官方公布&#xff0c;浙江2022年下半年软考…

c#透明悬浮球实现 从零开始用C#写一个桌面应用程序(三)

目标&#xff1a;透明悬浮球 记录日期&#xff1a;20240308 要求基础&#xff1a;C#语言基础部分事件与委托&#xff0c;c#桌面程序基础操作 注&#xff1a;可见前文 http://t.csdnimg.cn/9uWK8 今天开始做一个悬浮球软件。本以为最难的是让悬浮球的具体功能&#xff0c…

养猫知识!猫罐头好还是猫粮好?宠物医生都在用的猫罐头

有位姐妹刚养猫大概已经快一年了&#xff0c;一直给猫喂的都是干粮&#xff0c;猫咪毛发枯燥&#xff0c;长肉慢。带到医院检查后&#xff0c;我发现猫咪营养不良&#xff0c;吸收能力差&#xff0c;有点软便&#xff0c;我建议她给猫咪喂主食罐。结果猫咪爱吃&#xff0c;而且…

openGuass——对象管理

目录 一、表空间 二、数据库 三、模式:Schema 四、database schema table之间的关系 五、表 六、分区表 七、索引 八、视图 九、序列 十、同义词 十一、约束 一、表空间 自带了两个表空间&#xff1a;pg_default和pg_global。查看命令&#xff1a;\db 默认表空间pg…

AI时代,什么是QPS数据?

自 OpenAI 公司于 2022 年 11 月 30 日发布 ChatGPT 以来&#xff0c;经过 23 年一整年的发展之后&#xff0c;大语言模型的概念已逐渐普及&#xff0c;出现了各种基于大语言模型的周边产品&#xff0c;可以说已经玩的相当花哨了。 在这个AI发展的过程中&#xff0c;不少本地化…