短信验证码—Java实现

这篇具有很好参考价值的文章主要介绍了短信验证码—Java实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在业务需求中我们经常会用到短信验证码,比如手机号登录、绑定手机号、忘记密码、敏感操作等,都可以通过短信验证码来保证操作的安全性,于是就记录下了一次开发的过程。

一.架构设计

短信验证码—Java实现

  • 发送短信是一个比较慢的过程,因为需要用到第三方服务(腾讯云短信服务),因此我们使用RabbitMq来做异步处理,前端点击获取验证码后,后端做完校验限流后直接返回发送成功。

  • 发送短信的服务是需要收费的,而且我们也不允许用户恶意刷接口,所以需要有一个接口限流方案,可考虑漏桶算法、令牌桶算法,这里采用令牌桶算法

二.编码实现

① 环境搭建

  • Springboot 2.7.0
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

② 令牌桶算法

这里使用Redis实现令牌桶算法,令牌桶算法具体细节可参考其他博客,这里不赘述,大致就是在 一个时间段 内,存在一定数量的令牌,我们需要拿到令牌才可以继续操作。

所以实现思路大致就是:

  • Redis 中记录上次拿取令牌的时间,以及令牌数,每个手机号对应一个桶
  • 每次拿令牌时,校验令牌是否足够。
/**
 * @author YukeSeko
 */
@Component
public class RedisTokenBucket {

    @Resource
    private RedisTemplate<String,String> redisTemplate;

    /**
     *  过期时间,400秒后过期
     */
    private final long EXPIRE_TIME = 400;

    /**
     * 令牌桶算法,一分钟以内,每个手机号只能发送一次
     * @param phoneNum
     * @return
     */
    public boolean tryAcquire(String phoneNum) {
        // 每个手机号码一分钟内只能发送一条短信
        int permitsPerMinute = 1;
        // 令牌桶容量
        int maxPermits = 1;
        // 获取当前时间戳
        long now = System.currentTimeMillis();
        String key = RedisConstant.SMS_BUCKET_PREFIX + phoneNum;
        // 计算令牌桶内令牌数
        int tokens = Integer.parseInt(redisTemplate.opsForValue().get(key + "_tokens") == null ? "0" : redisTemplate.opsForValue().get(key + "_tokens"));
        // 计算令牌桶上次填充的时间戳
        long lastRefillTime = Long.parseLong(redisTemplate.opsForValue().get(key + "_last_refill_time") == null ? "0" : redisTemplate.opsForValue().get(key + "_last_refill_time"));
        // 计算当前时间与上次填充时间的时间差
        long timeSinceLast = now - lastRefillTime;
        // 计算需要填充的令牌数
        int refill = (int) (timeSinceLast / 1000 * permitsPerMinute / 60);
        // 更新令牌桶内令牌数
        tokens = Math.min(refill + tokens, maxPermits);
        // 更新上次填充时间戳
        redisTemplate.opsForValue().set(key + "_last_refill_time", String.valueOf(now),EXPIRE_TIME, TimeUnit.SECONDS);
        // 如果令牌数大于等于1,则获取令牌
        if (tokens >= 1) {
            tokens--;
            redisTemplate.opsForValue().set(key + "_tokens", String.valueOf(tokens),EXPIRE_TIME, TimeUnit.SECONDS);
            // 如果获取到令牌,则返回true
            return true;
        }
        // 如果没有获取到令牌,则返回false
        return false;
    }
}

③ 业务代码

0.Pojo

/**
 * 短信服务传输对象
 * @author niuma
 * @create 2023-04-28 21:16
 */
@Data
@AllArgsConstructor
public class SmsDTO implements Serializable {

    private static final long serialVersionUID = 8504215015474691352L;
    String phoneNum;

    String code;
}

1.Controller

    /**
     * 发送短信验证码
     * @param phoneNum
     * @return
     */
    @GetMapping("/smsCaptcha")
    public BaseResponse<String> smsCaptcha(@RequestParam String phoneNum){
        userService.sendSmsCaptcha(phoneNum);
        // 异步发送验证码,这里直接返回成功即可
        return ResultUtils.success("获取短信验证码成功!");
    }

2.Service

  • 手机号格式校验可参考其他人代码。
    public Boolean sendSmsCaptcha(String phoneNum) {

        if (StringUtils.isEmpty(phoneNum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号不能为空");
        }
        AuthPhoneNumberUtil authPhoneNumberUtil = new AuthPhoneNumberUtil();

        // 手机号码格式校验
        boolean checkPhoneNum = authPhoneNumberUtil.isPhoneNum(phoneNum);
        if (!checkPhoneNum) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "手机号格式错误");
        }

        //生成随机验证码
        int code = (int) ((Math.random() * 9 + 1) * 10000);
        SmsDTO smsDTO = new SmsDTO(phoneNum,String.valueOf(code));

        return smsUtils.sendSms(smsDTO);
    }

3.发送短信工具类

  • 提供两个方法
    • sendSms:先从令牌桶中获取令牌,获取失败不允许发短信,获取成功后,将验证码信息存入Redis,使用RabbitMq异步发送短信
    • verifyCode:根据手机号校验验证码,使用Redis
/**
 * @author niuma
 * @create 2023-04-28 22:18
 */
@Component
@Slf4j
public class SmsUtils {
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private RedisTokenBucket redisTokenBucket;
    @Resource
    private RabbitMqUtils rabbitMqUtils;

    public boolean sendSms(SmsDTO smsDTO) {
        // 从令牌桶中取得令牌,未取得不允许发送短信
        boolean acquire = redisTokenBucket.tryAcquire(smsDTO.getPhoneNum());
        if (!acquire) {
            log.info("phoneNum:{},send SMS frequent", smsDTO.getPhoneNum());
            return false;
        }
        log.info("发送短信:{}",smsDTO);
        String phoneNum = smsDTO.getPhoneNum();
        String code = smsDTO.getCode();

        // 将手机号对应的验证码存入Redis,方便后续检验
        redisTemplate.opsForValue().set(RedisConstant.SMS_CODE_PREFIX + phoneNum, String.valueOf(code), 5, TimeUnit.MINUTES);

        // 利用消息队列,异步发送短信
        rabbitMqUtils.sendSmsAsync(smsDTO);
        return true;
    }

    public boolean verifyCode(String phoneNum, String code) {
        String key = RedisConstant.SMS_CODE_PREFIX + phoneNum;
        String checkCode = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(code) && code.equals(checkCode)) {
            redisTemplate.delete(key);
            return true;
        }
        return false;

    }
}

4.RabbitMq初始化

创建交换机和消息队列

/**
 * RabbitMQ配置
 * @author niumazlb
 */
@Slf4j
@Configuration
public class RabbitMqConfig {

    /**
     * 普通队列
     * @return
     */
    @Bean
    public Queue smsQueue(){
        Map<String, Object> arguments = new HashMap<>();
        //声明死信队列和交换机消息,过期时间:1分钟
        arguments.put("x-dead-letter-exchange", SMS_EXCHANGE_NAME);
        arguments.put("x-dead-letter-routing-key", SMS_DELAY_EXCHANGE_ROUTING_KEY);
        arguments.put("x-message-ttl", 60000);
        return new Queue(SMS_QUEUE_NAME,true,false,false ,arguments);
    }

    /**
     * 死信队列:消息重试三次后放入死信队列
     * @return
     */
    @Bean
    public Queue deadLetter(){
        return new Queue(SMS_DELAY_QUEUE_NAME, true, false, false);
    }

    /**
     * 主题交换机
     * @return
     */
    @Bean
    public Exchange smsExchange() {
        return new TopicExchange(SMS_EXCHANGE_NAME, true, false);
    }


    /**
     * 交换机和普通队列绑定
     * @return
     */
    @Bean
    public Binding smsBinding(){
        return new Binding(SMS_QUEUE_NAME, Binding.DestinationType.QUEUE,SMS_EXCHANGE_NAME,SMS_EXCHANGE_ROUTING_KEY,null);
    }

    /**
     * 交换机和死信队列绑定
     * @return
     */
    @Bean
    public Binding smsDelayBinding(){
        return new Binding(SMS_DELAY_QUEUE_NAME, Binding.DestinationType.QUEUE,SMS_EXCHANGE_NAME,SMS_DELAY_EXCHANGE_ROUTING_KEY,null);
    }


}

5.Mq短信消息生产者

  • 通过实现ConfirmCallback、ReturnsCallback接口,提高消息的可靠性
  • sendSmsAsync:将消息的各种信息设置进Redis(重试次数、状态、数据),将消息投递进Mq,这里传入自己设置的messageId,方便监听器中能够在Redis中找到这条消息。
/**
 * 向mq发送消息,并进行保证消息可靠性处理
 *
 * @author niuma
 * @create 2023-04-29 15:09
 */
@Component
@Slf4j
public class RabbitMqUtils implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private RabbitTemplate rabbitTemplate;


    private String finalId = null;

    private SmsDTO smsDTO = null;

    /**
     * 向mq中投递发送短信消息
     *
     * @param smsDTO
     * @throws Exception
     */
    public void sendSmsAsync(SmsDTO smsDTO) {
        String messageId = null;
        try {
            // 将 headers 添加到 MessageProperties 中,并发送消息
            messageId = UUID.randomUUID().toString();
            HashMap<String, Object> messageArgs = new HashMap<>();
            messageArgs.put("retryCount", 0);
            //消息状态:0-未投递、1-已投递
            messageArgs.put("status", 0);
            messageArgs.put("smsTo", smsDTO);
            //将重试次数和短信发送状态存入redis中去,并设置过期时间
            redisTemplate.opsForHash().putAll(RedisConstant.SMS_MESSAGE_PREFIX + messageId, messageArgs);
            redisTemplate.expire(RedisConstant.SMS_MESSAGE_PREFIX + messageId, 10, TimeUnit.MINUTES);

            String finalMessageId = messageId;
            finalId = messageId;
            this.smsDTO = smsDTO;
            // 将消息投递到MQ,并设置消息的一些参数
            rabbitTemplate.convertAndSend(RabbitMqConstant.SMS_EXCHANGE_NAME, RabbitMqConstant.SMS_EXCHANGE_ROUTING_KEY, smsDTO, message -> {
                MessageProperties messageProperties = message.getMessageProperties();
                //生成全局唯一id
                messageProperties.setMessageId(finalMessageId);
                messageProperties.setContentEncoding("utf-8");
                return message;
            });

        } catch (Exception e) {
            //出现异常,删除该短信id对应的redis,并将该失败消息存入到“死信”redis中去,然后使用定时任务去扫描该key,并重新发送到mq中去
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + messageId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, messageId, smsDTO);
            throw new RuntimeException(e);
        }
    }

    /**
     * 发布者确认的回调
     *
     * @param correlationData 回调的相关数据。
     * @param b               ack为真,nack为假
     * @param s               一个可选的原因,用于nack,如果可用,否则为空。
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        // 消息发送成功,将redis中消息的状态(status)修改为1
        if (b) {
            redisTemplate.opsForHash().put(RedisConstant.SMS_MESSAGE_PREFIX + finalId, "status", 1);
        } else {
            // 发送失败,放入redis失败集合中,并删除集合数据
            log.error("短信消息投送失败:{}-->{}", correlationData, s);
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + finalId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, finalId, this.smsDTO);
        }
    }

    /**
     * 发生异常时的消息返回提醒
     *
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("发生异常,返回消息回调:{}", returnedMessage);
        // 发送失败,放入redis失败集合中,并删除集合数据
        redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + finalId);
        redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, finalId, this.smsDTO);
    }

    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnsCallback(this);
    }
}

6.Mq消息监听器

  • 根据messageId从Redis中找到对应的消息(为了判断重试次数,规定重试3次为失败,加入死信队列)
  • 调用第三方云服务商提供的短信服务发送短信,通过返回值来判断是否发送成功
  • 手动确认消息
/**
 * @author niuma
 * @create 2023-04-29 15:35
 */
@Component
@Slf4j
public class SendSmsListener {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private SendSmsUtils sendSmsUtils;

    /**
     * 监听发送短信普通队列
     * @param smsDTO
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = SMS_QUEUE_NAME)
    public void sendSmsListener(SmsDTO smsDTO, Message message, Channel channel) throws IOException {
        String messageId = message.getMessageProperties().getMessageId();
        int retryCount = (int) redisTemplate.opsForHash().get(RedisConstant.SMS_MESSAGE_PREFIX + messageId, "retryCount");
        if (retryCount > 3) {
            //重试次数大于3,直接放到死信队列
            log.error("短信消息重试超过3次:{}",  messageId);
            //basicReject方法拒绝deliveryTag对应的消息,第二个参数是否requeue,true则重新入队列,否则丢弃或者进入死信队列。
            //该方法reject后,该消费者还是会消费到该条被reject的消息。
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + messageId);
            return;
        }
        try {
            String phoneNum = smsDTO.getPhoneNum();
            String code = smsDTO.getCode();
            if(StringUtils.isAnyBlank(phoneNum,code)){
                throw new RuntimeException("sendSmsListener参数为空");
            }

            // 发送消息
            SendSmsResponse sendSmsResponse = sendSmsUtils.sendSmsResponse(phoneNum, code);
            SendStatus[] sendStatusSet = sendSmsResponse.getSendStatusSet();
            SendStatus sendStatus = sendStatusSet[0];
            if(!"Ok".equals(sendStatus.getCode()) ||!"send success".equals(sendStatus.getMessage())){
                throw new RuntimeException("发送验证码失败");
            }

            //手动确认消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            log.info("短信发送成功:{}",smsDTO);
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + messageId);
        } catch (Exception e) {
            redisTemplate.opsForHash().put(RedisConstant.SMS_MESSAGE_PREFIX+messageId,"retryCount",retryCount+1);
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }

    /**
     * 监听到发送短信死信队列
     * @param sms
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(queues = SMS_DELAY_QUEUE_NAME)
    public void smsDelayQueueListener(SmsDTO sms, Message message, Channel channel) throws IOException {
        try{
            log.error("监听到死信队列消息==>{}",sms);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }
}

7.腾讯云短信服务

@Component
public class TencentClient {

    @Value("${tencent.secretId}")
    private String secretId;

    @Value("${tencent.secretKey}")
    private String secretKey;

    /**
     * Tencent应用客户端
     * @return
     */
    @Bean
    public SmsClient client(){
        Credential cred = new Credential(secretId, secretKey);
        SmsClient smsClient = new SmsClient(cred, "ap-guangzhou");
        return smsClient;
    }
}

@Component
public class SendSmsUtils {

    @Resource
    private TencentClient tencentClient;

    @Value("${tencent.sdkAppId}")
    private String sdkAppId;
    @Value("${tencent.signName}")
    private String signName;
    @Value("${tencent.templateId}")
    private String templateId;

    /**
     * 发送短信工具
     * @param phone
     * @return
     * @throws TencentCloudSDKException
     */
    public SendSmsResponse sendSmsResponse (String phone,String code) throws TencentCloudSDKException {
        SendSmsRequest req = new SendSmsRequest();

        /* 短信应用ID */
        // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看
        req.setSmsSdkAppId(sdkAppId);

        /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */
        // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看
        req.setSignName(signName);

        /* 模板 ID: 必须填写已审核通过的模板 ID */
        // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
        req.setTemplateId(templateId);

        /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */
        String[] templateParamSet = {code};
        req.setTemplateParamSet(templateParamSet);

        /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号]
         * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号 */
        String[] phoneNumberSet = new String[]{"+86" + phone};
        req.setPhoneNumberSet(phoneNumberSet);

        /* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回
        String sessionContext = "";
        req.setSessionContext(sessionContext);
        */

        /* 通过 client 对象调用 SendSms 方法发起请求。注意请求方法名与请求对象是对应的
         * 返回的 res 是一个 SendSmsResponse 类的实例,与请求对象对应 */
        SmsClient client = tencentClient.client();
        return client.SendSms(req);
    }
}

配置文件文章来源地址https://www.toymoban.com/news/detail-486427.html

tencent:
  secretId: #你的secretId
  secretKey: #你的secretKey
  sdkAppId: #你的sdkAppId
  signName: #你的signName
  templateId: #你的templateId

三. 心得

  1. 消息队列的一个用法
  2. ConfirmCallback、ReturnsCallback接口的使用
  3. 腾讯云短信服务的使用
  4. 令牌桶算法的实践

到了这里,关于短信验证码—Java实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【业务功能篇94】微服务-springcloud-springboot-认证服务-注册功能-第三方短信验证API

      结合我们前面介绍的商城的架构我们需要单独的搭建一个认证服务。   首先创建一个SpringBoot项目,然后添加对应的依赖   我们需要把认证服务注册到Nacos中,添加对应的依赖,然后完成对应的配置 放开Nacos注册中心 然后启动测试   然后我们整理登录和注册的相关

    2024年02月09日
    浏览(53)
  • 基于短信宝API零代码实现短信自动化业务

    基于短信宝开放的API能力,实现在特定事件(如天气预警)或定时自动发送短信(本文以定时群发短信为例)。通过Aboter平台如何实现呢? 首先创建一个IPaaS流程,触发条件组件编辑区选择【定时触发】类型。比如早上10点发送短信通知。 拖动左侧【应用连接器 短信邮件 短

    2024年02月14日
    浏览(31)
  • 微信小程序 跳转页面经常会遇到判断是否登录情况。基于此需求,做了一个路由跳转拦截的jump组件

    组件代码非常简单 在根目录创建components目录 在components目录新建jump目录 在jump目录新建四个文件 index.js index.json index.wxml index.wxss 这个样式文件内容空就行

    2024年02月09日
    浏览(53)
  • Java 图片验证码需求分析

    💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! 需求分析 连续因输错密码而登录失败时,记录其连续输错密码的累加次数;若在次数小于5时,用户输入正确的密码并成功登录,则次数被清零 连续5次因输错密码而登录失败后,系统弹框提示【您已连续5次输入错误的

    2024年02月08日
    浏览(26)
  • 短信验证码的实现(阿里云)

    背景:目前在很多项目中都出现使用短信验证码来实现注册、登录、购买、支付、转账等功能,发短信功能几乎已经成为项目中不可或缺的技术之一。 选择一个合适的编程语言,例如Python、Java或PHP等。 寻找能够发送短信的API,例如Twilio、阿里云短信等。 注册并获取API的账号

    2024年02月15日
    浏览(44)
  • 前端Vue自定义发送短信验证码弹框popup 实现剩余秒数计数 重发短信验证码

    前端Vue自定义发送短信验证码弹框popup 实现剩余秒数计数 重发短信验证码, 阅读全文下载完整代码请关注微信公众号: 前端组件开发 效果图如下: 实现代码如下: 使用方法 HTML代码实现部分 组件实现代码

    2024年02月11日
    浏览(38)
  • 基于阿里云服务实现短信验证码功能

    阿里云短信服务是一项基于云计算和大数据技术的企业级短信平台服务。它能够为企业和开发者提供高可用、高性能、高稳定性的短信发送服务,可以快速地将各类业务通知、验证码、营销推广等信息发送给用户。在我们经常登录一些系统或者APP时候,经常会遇到其他登录登

    2024年02月14日
    浏览(48)
  • 如何通过腾讯云短信实现发送验证码并校验验证码以实现登录功能

    验证码相关的10种技术 图像处理技术:生成、识别、验证验证码的图像。 机器学习技术:让计算机自动学习并识别验证码。 文字识别技术:将图像中的文字转换成计算机可读的文本。 模式识别技术:识别验证码中的模式及规律。 图像噪声处理技术:去除图像中的噪声干扰。

    2024年02月10日
    浏览(49)
  • 工作中,我们经常用到哪些SQL语句呢?

    目录 一、DDL部分(create、drop、alter) 1.1 create 语句上 1.2 drop 语句 1.3 alter 语句 二、DML(数据操纵语言)和DQL(数据查询语言) 2.1 insert 语句 2.2 update 语句 2.3 delete 语句 2.4 select 语句 2.5 其他操纵语言 2.5.1 truncate 语句 2.5.2 merge 语句 三、用户角色权限 3.1 用户相关 3.1.1 创建用户

    2024年02月03日
    浏览(43)
  • 前端实现手机短信验证码倒计时效果

    实现效果:实现按钮倒计时10s后可重新发送验证码 一、思路 1、禁用按钮,调用后端接口,获取验证码 2、setTimeOut(() = {},1000)延迟1s执行,time - 1,返回文案,9s 3、迭代处理,调用自身函数,time - 1,返回文案,8s,实现9s 8s 7s 这样倒计时的效果。 4、不能无限迭代减1,判断时

    2024年02月04日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包