说说验证码功能的实现

前言

大家好,我是 god23bin,今天说说验证码功能的实现,相信大家都经常接触到验证码的,毕竟平时上网也能遇到各种验证码,需要我们输入验证码进行验证我们是人类,而不是机器人。

验证码有多种类型,比如图片验证码、短信验证码和邮件验证码等等,虽说多种类型,图片也好,短信也好,邮件也好,都是承载验证码的载体,最主要的核心就是一个验证码的生成、存储和校验。

本篇文章就从这几个方面出发说说验证码,废话不多说,下面开始正文。

实现思路

验证码验证的功能,其实现思路还是挺简单的,不论是图片验证码、短信验证码还是邮件验证码,无非就以下几点:

  1. 验证码本质就是一堆字符的组合(数字也好,英文字母也好),后端生成验证码,并存储到某个位置(比如存储到 Redis,并设置验证码的过期时间)。
  2. 返回验证码给前端页面、发送短信验证码给用户或者发送邮件验证码给用户。验证码可以是以文字显示或者图片显示。
  3. 用户输入看到的验证码,并提交验证(验证也可以忽略大小写,当然具体看需求)。
  4. 后端将用户输入的验证码拿过来进行校验,对比用户输入的验证码是否和后端生成的一致,一致就验证成功,否则验证失败。

验证码的生成

首先,需要知道的就是验证码的生成,这就涉及到生成验证码的算法,可以自己纯手写,也可以使用人家提供的工具,这里我就介绍下面 4 种生成验证码的方式。

1. 纯原生手写生成文本验证码

需求:随机产生一个 n 位的验证码,每位可能是数字、大写字母、小写字母。

实现:本质就是随机生成字符串,字符串可包含数字、大写字母、小写字母。

准备一个包含数字、大写字母、小写字母的字符串,借助 Random 类,循环 n 次随机获取字符串的下标,就能拼接出一个随机字符组成的字符串了。

package cn.god23bin.demo.util;

import java.util.Random;

public class MyCaptchaUtil {

	/**
     * 生成 n 位验证码
     * @param n 位数
     * @return n 位验证码
     **/
    public static String generateCode(int n) {
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        StringBuilder sb = new StringBuilder();
        Random random = new Random();
        for (int i = 0; i < n; i++) {
            int index = random.nextInt(chars.length());
            sb.append(chars.charAt(index));
        }
        return sb.toString();
    }
    
}

2. 纯原生手写生成图片验证码

实现:使用 Java 的 awt 和 swing 库来生成图片验证码。下面使用 BufferedImage 类创建一个指定大小的图片,然后随机生成 n 个字符,将其画在图片上,将生成的字符和图片验证码放到哈希表返回。后续我们就可以拿到验证码的文本值,并且可以将图片验证码输出到指定的输出流中。

package cn.god23bin.demo.util;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

public class MyCaptchaUtil {

	/**
     * 生成 n 位的图片验证码
     * @param n 位数
     * @return 哈希表,code 获取文本验证码,img 获取 BufferedImage 图片对象
     **/
    public static Map<String, Object> generateCodeImage(int n) {
        int width = 100, height = 50;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.setColor(Color.LIGHT_GRAY);
        g.fillRect(0, 0, width, height);
        String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            int index = random.nextInt(chars.length());
            char c = chars.charAt(index);
            sb.append(c);
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g.setFont(new Font("Arial", Font.BOLD, 25));
            g.drawString(Character.toString(c), 20 + i * 15, 25);
        }
        Map<String, Object> res = new HashMap<>();
        res.put("code", sb.toString());
        res.put("img", image);
        return res;
    }
    
}

我们可以写一个获取验证码的接口,以二进制流输出返回给前端,前端可以直接使用 img 标签来显示我们返回的图片,只需在 src 属性赋值我们的获取验证码接口。

@RequestMapping("/captcha")
@RestController
public class CaptchaController {

    @GetMapping("/code/custom")
    public void getCode(HttpServletResponse response) {
        Map<String, Object> map = MyCaptchaUtil.generateCodeImage(5);
        System.out.println(map.get("code"));
        BufferedImage img = (BufferedImage) map.get("img");

        // 设置响应头,防止缓存
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/png");
        try {
            ImageIO.write(img, "png", response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

3. 使用 Hutool 工具生成图形验证码

引入依赖:可以单独引入验证码模块或者全部模块都引入

<!-- 验证码模块 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-captcha</artifactId>
    <version>5.8.15</version>
</dependency>

<!-- 全部模块都引入 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.15</version>
</dependency>
  • 生成线段干扰的验证码:
// 设置图形验证码的宽和高,同时生成了验证码,可以通过 lineCaptcha.getCode() 获取文本验证码
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
  • 生成圆圈干扰的验证码:
// 设置图形验证码的宽、高、验证码字符数、干扰元素个数
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 100, 4, 20);
  • 生成扭曲干扰的验证码:
// 定义图形验证码的宽、高、验证码字符数、干扰线宽度
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 100, 4, 4);

image-20230606231536066

获取验证码接口:

@RequestMapping("/captcha")
@RestController
public class CaptchaController {

    @GetMapping("/code/hutool")
    public void getCodeByHutool(HttpServletResponse response) {
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(200, 100);
        System.out.println("线段干扰的验证码:" + lineCaptcha.getCode());

        // 设置响应头,防止缓存
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/png");
        try {
            lineCaptcha.write(response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 使用 Kaptcha 生成验证码

Kaptcha 是谷歌的一个生成验证码工具包,我们简单配置其属性就可以实现验证码的验证功能。

引入依赖项:它只有一个版本:2.3.2

<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>

简单看看 kaptcha 属性:

属性描述默认值
kaptcha.border图片边框,合法值:yes , noyes
kaptcha.border.color边框颜色,合法值: r,g,b (and optional alpha) 或者 white,black,blue.black
kaptcha.border.thickness边框厚度,合法值:>01
kaptcha.image.width图片宽200
kaptcha.image.height图片高50
kaptcha.producer.impl图片实现类com.google.code.kaptcha.impl.DefaultKaptcha
kaptcha.textproducer.impl文本实现类com.google.code.kaptcha.text.impl.DefaultTextCreator
kaptcha.textproducer.char.string文本集合,验证码值从此集合中获取abcde2345678gfynmnpwx
kaptcha.textproducer.char.length验证码长度5
kaptcha.textproducer.font.names字体Arial, Courier
kaptcha.textproducer.font.size字体大小40px
kaptcha.textproducer.font.color字体颜色,合法值: r,g,b 或者 white,black,blue.black
kaptcha.textproducer.char.space文字间隔2
kaptcha.noise.impl干扰实现类com.google.code.kaptcha.impl.DefaultNoise
kaptcha.noise.color干扰颜色,合法值: r,g,b 或者 white,black,blue.black
kaptcha.obscurificator.impl图片样式: 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpycom.google.code.kaptcha.impl.WaterRipple
kaptcha.background.impl背景实现类com.google.code.kaptcha.impl.DefaultBackground
kaptcha.background.clear.from背景颜色渐变,开始颜色light grey
kaptcha.background.clear.to背景颜色渐变,结束颜色white
kaptcha.word.impl文字渲染器com.google.code.kaptcha.text.impl.DefaultWordRenderer
kaptcha.session.keysession keyKAPTCHA_SESSION_KEY
kaptcha.session.datesession dateKAPTCHA_SESSION_DATE

简单配置下 Kaptcha:

package cn.god23bin.demo.config;

import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Properties;

@Configuration
public class KaptchaConfig {
    /**
     * 配置生成图片验证码的bean
     * @return
     */
    @Bean(name = "kaptchaProducer")
    public DefaultKaptcha getKaptchaBean() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        properties.setProperty("kaptcha.border", "no");
        properties.setProperty("kaptcha.textproducer.font.color", "black");
        properties.setProperty("kaptcha.textproducer.char.space", "4");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        properties.setProperty("kaptcha.textproducer.char.string", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

也是和 Hutool 一样,很简单就能生成验证码了。如下:

// 生成文字验证码
String text = kaptchaProducer.createText();
// 生成图片验证码
BufferedImage image = kaptchaProducer.createImage(text);

获取验证码接口:

@RequestMapping("/captcha")
@RestController
public class CaptchaController {

    @Autowired
    private Producer kaptchaProducer;

    @GetMapping("/code/kaptcha")
    public void getCodeByKaptcha(HttpServletResponse response) {
        // 生成文字验证码
        String text = kaptchaProducer.createText();
        System.out.println("文字验证码:" + text);
        // 生成图片验证码
        BufferedImage image = kaptchaProducer.createImage(text);

        // 设置响应头,防止缓存
        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/jpeg");
        try {
            ImageIO.write(image, "jpg", response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

验证码的存储与校验

上面的验证码的生成,就仅仅是生成验证码,并没有将验证码存储在后端,所以现在我们需要做的是:将验证码存储起来,便于后续的校验对比。

那么存储到什么地方呢?如果你没接触过 Redis,那么第一次的想法可能就是存储到关系型数据库中,比如 MySQL。想当年,我最开始的想法就是这样哈哈哈。

不过,目前用得最多的就是将验证码存储到 Redis 中,好处就是减少了数据库的压力,加快了验证码的读取效率,还能轻松设置验证码的过期时间。

简单配置 Redis

引入 Redis 依赖项:

我们使用 Spring Data Redis,它提供了 RedisTemplateStringRedisTemplate 模板类,简化了我们使用 Java 进行 Redis 的操作。

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

简单配置下 Redis:

spring:
  redis:
    host: localhost
    port: 6379
    database: 1
    timeout: 5000
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 大多数情况,都是选用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 使用JSON的序列化对象,对数据 key 和 value 进行序列化转换
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        // ObjectMapper 是 Jackson 的一个工作类,作用是将 JSON 转成 Java 对象,即反序列化。或将 Java 对象转成 JSON,即序列化
        ObjectMapper mapper = new ObjectMapper();
        // 设置序列化时的可见性,第一个参数是选择序列化哪些属性,比如时序列化 setter? 还是 filed? 第二个参数是选择哪些修饰符权限的属性来序列化,比如 private 或者 public,这里的 any 是指对所有权限修饰的属性都可见(可序列化)
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(mapper);
        // 设置 RedisTemplate 模板的序列化方式为 jacksonSeial
        template.setDefaultSerializer(jackson2JsonRedisSerializer);
        return template;
    }
    
}

将验证码存储到 Redis

将验证码存储到 Redis 设置 5 分钟的过期时间,Redis 是 Key Value 这种形式存储的,所以需要约定好 Key 的命名规则。

命名的时候,为了区分为每个用户生成的验证码,所以需要一个标识,刚好可以通过当前请求的 HttpSession 中的 SessionID 作为唯一标识,拼接到 Key 的名称中。

当然,也不一定使用 SessionID 作为唯一标识,如果能知道其他的,也可以用其他的作为标识,比如拼接用户的手机号。

实现:

@RequestMapping("/captcha")
@RestController
public class CaptchaController {

    @Autowired
    private Producer kaptchaProducer;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("/code")
    public void getCode(HttpServletRequest request, HttpServletResponse response) {
        // 生成文字验证码
        String text = kaptchaProducer.createText();
        System.out.println("文字验证码:" + text);
        // 生成图片验证码
        BufferedImage image = kaptchaProducer.createImage(text);

        // 存储到 Redis 设置 5 分钟的过期时间
        // 约定好存储的 Key 的命名规则,这里使用 code_sessionId_type_1 表示图形验证码
        // Code_sessionId_Type_1:分为 3 部分,code 表明是验证码,sessionId 表明是给哪个用户的验证码,type_n 表明验证码类型,n 为 1 表示图形验证码,2 表示短信验证码,3 表示邮件验证码
        String key = "code_" + request.getSession().getId() + "_type_1";
        redisTemplate.opsForValue().set(key, text, 5, TimeUnit.SECONDS);

        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/jpeg");
        try {
            ImageIO.write(image, "jpg", response.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

上面代码中有一个额外的设计就是,由于发送的验证码有多种类型(图形验证码、短信验证码、邮件验证码),所以加多了一个 type_n 来标识当前存储的验证码是什么类型的,方便以后出现问题快速定位。

实际上,这里的命名规则,可以根据你的具体需求来定制,又比如说,登录的时候需要验证码、注册的时候也需要验证码、修改用户密码的时候也需要验证码,为了便于出现问题进行定位,也可以继续加多一个标识 when_n,n 为 1 表示注册、n 为 2 表示登录,以此类推。

校验

我们模拟登录的时候进行验证码的校验,使用一个 LoginDTO 对象来接收前端的登录相关的参数。

package cn.god23bin.demo.model.domain.dto;

import lombok.Data;

@Data
public class LoginDTO {
    private String username;
    private String password;
    /**
     * 验证码
     */
    private String code;
}

写一个登录接口,登录的过程中,校验用户输入的验证码。

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/login")
    public Result<String> login(@RequestBody LoginDTO loginDTO, HttpServletRequest request) {
        if (!"root".equals(loginDTO.getUsername()) || !"123456".equals(loginDTO.getPassword())) {
            return Result.fail("登录失败!账号或密码不正确!");
        }
        // 校验用户输入的验证码
        String code = loginDTO.getCode();
        String codeInRedis = (String) redisTemplate.opsForValue().get("code_" + request.getSession().getId() + "_type_1");
        if (!code.equals(codeInRedis)) {
            return Result.fail("验证码不正确!");
        }
        return Result.ok("登录成功!");
    }
}

至此,便完成了验证码功能的实现。

获取验证码的安全设计

验证码功能的实现现在是OK的,但还有一点需要注意,那就是防止验证码被随意调用获取,或者被大量调用。如果不做限制,那么谁都能调用,就非常大的可能会被攻击了。

我们上面实现的验证码功能是图形验证码,是校验用户从图形验证码中看到后输入的数字字母组合跟后端生成的组合是否是一致的。对于图形验证码,到这里就可以了,不用限制(当然想限制也可以)。**但是对于短信验证码,就还不可以。**我们需要额外考虑一些防刷机制,以保障系统的安全性和可靠性(因为发短信是要钱的啊!)。

对于短信来说,一种常见的攻击方式是「短信轰炸」,攻击者通过自动批量提交手机号码、模拟IP等手段,对系统进行大规模的短信请求,从而消耗资源或干扰正常业务。为了应对这种情况,我们需要设计一些防刷机制。

防刷机制

目前我了解到的防刷机制有下面几种,如果你有别的方法,欢迎评论说出来噢!

  1. 图形验证码或者滑动验证:发送短信前先使用图形验证码或者滑动进行验证,验证成功才能调用发送短信验证码的接口。
  2. 时间限制:从用户点击发送短信验证码开始,前端进行一个 60 秒的倒数,在这 60 秒之内,用户无法提交发送信息的请求的,这样就限制了发送短信验证码的接口的调用次数。不过这种方式,如果被攻击者知道了发送短信的接口,那也是会被刷的。
  3. 手机号限制:对使用同一个手机号码进行注册或者其他发送短信验证码的操作的时候,系统可以对这个手机号码进行限制,例如,一天只能发送 5 条短信验证码,超出限制则做出提示(如:系统繁忙,请稍后再试)。然而,这也只能够避免人工手动刷短信而已,对于批量使用不同手机号码来刷短信的机器,同样是会被刷。
  4. IP地址限制:记录请求的IP地址,并对同一 IP 地址的请求进行限制,比如限制某个 IP 地址在一定时间内只能发送特定数量的验证码。同样,也是可以被轰炸的。

至于这些机制的实现,有机会再写写,你感兴趣的话可以自己去操作试试!

总结

本篇文字就说了验证码功能的实现思路和实现,包括验证码的生成、存储、展示和校验。

  • 生成验证码可以手写也可以借助工具。

  • 存储一般是存储在 Redis 中的,当然你想存储在 MySQL 中也不是不可以,就是需要自己去实现诸如过期时间的功能。

  • 展示可以通过文本展示或者图片展示,我们可以返回一个二进制流给前端,前端通过 img 标签的 src 属性去请求我们的接口。

  • 校验就拿到用户输入的验证码,和后端生成的验证码进行比对,相同就验证成功,否则失败。

最后我们也说了验证码的防刷机制,这是需要考虑的,这里的防刷机制对于使用大量不同手机号、不同 IP 地址是没效果的,依旧可以暴刷。所以这部分内容还是有待研究的。也欢迎大家在评论区说出你的看法!

最后的最后

希望各位屏幕前的靓仔靓女们给个三连!你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

咱们下期再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

项目中使用es(一):使用springboot操作elasticsearch

使用springboot操作es 写在前面搭建项目环境和选择合适版本具体的代码实现&#xff08;1&#xff09;继承ProductInfoRepository具体的代码实现&#xff08;2&#xff09;使用ElasticsearchRestTemplate操作问题总结最后放个demo 写在前面 对于elasticsearch的搭建&#xff0c…

5款提高工作效率的无广告软件

今天推荐一些可以大幅度提升办公效率的小软件&#xff0c;安全无毒&#xff0c;下载简单&#xff0c;最重要的是没有广告&#xff01; 1.照片处理——Darktable Darktable是一款用于处理和管理数码照片的工具。它可以让你对RAW格式的照片进行非破坏性的编辑,并提供多种模块和…

设计模式之~观察者模式

观察者模式又叫做发布-订阅&#xff08;Publish/Subscribe&#xff09;模式。 观察者模式observer&#xff1a;定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某个主题对象。这个主题对象在状态发生变化时&#xff0c;会通知所有观察者对象&#xff0c;使他们…

Linux系统下imx6ull QT编程—— C++构造函数、析构函数、this指针(四)

Linux QT编程 文章目录 Linux QT编程一、什么是构造函数&#xff1f;二、什么是析构函数&#xff1f;三、示例四、this指针 一、什么是构造函数&#xff1f; 构造函数在对象实例化时被系统自动调用&#xff0c;仅且调用一次。前面我们学过类&#xff0c;实际上定义类时&#x…

【Spring】— 动态SQL :<if>元素

动态SQL &#xff1a;元素 在MyBatis中&#xff0c;<if>元素是常用的判断语句&#xff0c;主要用于实现某些简单的条件选择。在实际应用中&#xff0c;我们可能会通过多个条件来精确地查询某个数据。 【示例8-1】下面通过一个具体的案例来演示元素的使用。 &#xff0…

基于RPC协议的接口自动化测试可以用Python语言实现

基于RPC协议的接口自动化测试可以用Python语言实现。下面是实现步骤&#xff1a; 1、安装依赖库&#xff0c;如protobuf、grpc。 2、编写.proto文件定义接口参数和返回值。 3、使用protoc编译.proto文件生成Python代码。 4、编写客户端代码调用远程接口进行测试。 具体实现…

数据结构与算法练习(三)二叉树

文章目录 1、树2、二叉树3、满二叉树4、完全二叉树5、二叉树的遍历&#xff08;前序、中序、后序&#xff09;二叉树删除节点或树 6、顺序存储二叉树顺序存储二叉树遍历&#xff08;前序、中序、后序&#xff09; 7、线索化二叉树中序线索二叉树前序线索二叉树后序线索二叉树 1…

悲观锁、乐观锁、自旋锁

悲观锁、乐观锁、自旋锁 &#xff08;1&#xff09;乐观锁 乐观锁是一种乐观的思想&#xff0c;即认为读多写少&#xff0c;遇到并发的可能性低&#xff0c;每次拿数据时都认为别人不会修改&#xff0c;所以不会上锁&#xff0c;但是在更新的时候会判断一下在此期间别人有没有…

开源赋能 普惠未来|中软国际寄语 2023 开放原子全球开源峰会

中软国际作为行业领先的全球化软件与信息技术服务企业及数字化转型服务商&#xff0c;近年来积极布局开源生态&#xff08;OpenHarmony、openEuler&#xff09;、智能云、ERP、AIGC、教育科技、智能车六大赛道&#xff0c;加速业务转型创新。 中软国际为开放原子开源基金会白金…

力扣---二叉树OJ题(多种题型二叉树)

文章目录 前言&#x1f31f;一、剑指 Offer 55 - I. 二叉树的深度&#x1f30f;1.1 链接&#xff1a;&#x1f30f;1.2 代码一&#xff1a;&#x1f30f;1.3 代码二&#xff1a;&#x1f30f;1.4 流程图&#xff1a; &#x1f31f;二、100. 相同的树&#x1f30f;2.1 链接&…

【ChatGPT】ChatGPT快速生成短视频

1.chatGPT剪映 chatGPT生成文本后通过剪映图文成片 这次用了new bing&#xff1a;Chatbot AI 在线网页版 (atmob.cn) 打开剪映-图文成片 把new bing生成的文本粘贴过来&#xff0c;点击生成视频。 生成好了&#xff0c;是这样 剪映自动生成的&#xff0c;最后还是得手工改改&…

Linux4.4网页与安全优化

文章目录 计算机系统5G云计算第一章 LINUX Apache网页与安全优化一、网页压缩1.检查是否安装 mod_deflate 模块2.如果没有安装mod_deflate 模块&#xff0c;重新编译安装 Apache 添加 mod_deflate 模块3.配置 mod_deflate 模块启用4.检查安装情况&#xff0c;启动服务5.测试 mo…

06 Redis分布式锁

常见面试问题 Redis除了拿来做缓存&#xff0c;你还见过基于Redis的什么用法&#xff1f;Redis 做分布式锁的时候有需要注意的问题&#xff1f;如果是 Redis 是单点部署的&#xff0c;会带来什么问题&#xff1f;那你准备怎么解决单点问题呢&#xff1f;集群模式下&#xff0c…

MySQL函数

日期函数 获得年月日&#xff1a; select current_date(); ---------------- | current_date() | ---------------- | 2017-11-19 | ----------------获得时分秒&#xff1a; select current_time(); ---------------- | current_time() | ---------------- | 13:51:21 …

SpringCloud:分布式缓存之Redis哨兵

Redis提供了哨兵&#xff08;Sentinel&#xff09;机制来实现主从集群的自动故障恢复。 1.哨兵原理 1.1.集群结构和作用 哨兵的结构如图&#xff1a; 哨兵的作用如下&#xff1a; 监控&#xff1a;Sentinel会不断检查您的master和slave是否按预期工作自动故障恢复&#xff…

使用 ChatGPT API 构建系统(三):思维链推理

今天我学习了DeepLearning.AI的 Building Systems with the ChatGPT API 的在线课程&#xff0c;我想和大家一起分享一下该门课程的一些主要内容。 下面是我们通过Open API来访问ChatGPT模型的主要代码&#xff1a; import openai#您的openai的api key openai.api_key YOUR-O…

VMware安装Centos7图形化GUI系统全过程

1、打开vmware&#xff0c;点击文件然后新建虚拟机 2、然后自定义直接下一步 3、下一步 4、这里我们稍后安装操作系统&#xff0c;继续下一步 5、随后选择Centos7 64位&#xff0c;继续下一步 6、选择你所需要安装的虚拟机存放的位置&#xff0c;虚拟机名字看自己来设置&#x…

MapReduce序列化【用户流量使用统计】

目录 什么是序列化和反序列化&#xff1f; 序列化 反序列化 为什么要序列化&#xff1f; 序列化的主要应用场景 MapReduce实现序列化 自定义bean对象实现Writable接口 1.实现Writable接口 2.无参构造 3.重写序列化方法 4.重写反序列化方法 5.顺序一致 6.重写toStri…

小狗避障-第14届蓝桥杯省赛Scratch中级组真题第4题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第139讲。 小狗避障&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组编程第4题&#xf…

二、高通相机bringup 流程

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、相机Sensor 点亮相关的文件二、Sensor 驱动文件详解 一、相机Sensor 点亮相关的文件 1.1 Sensor 驱动XML以及CPP文件 Sensor 文件路径&#xff1a;…