欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)短信驗(yàn)證碼詳細(xì)過程

 更新時(shí)間:2023年09月14日 10:41:53   作者:dogbin丶  
這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)短信驗(yàn)證碼的相關(guān)資料, 在業(yè)務(wù)需求中我們經(jīng)常會(huì)用到短信驗(yàn)證碼,比如手機(jī)號(hào)登錄、綁定手機(jī)號(hào)、忘記密碼、敏感操作等,需要的朋友可以參考下

前言

在業(yè)務(wù)需求中我們經(jīng)常會(huì)用到短信驗(yàn)證碼,比如手機(jī)號(hào)登錄、綁定手機(jī)號(hào)、忘記密碼、敏感操作等,都可以通過短信驗(yàn)證碼來保證操作的安全性,于是就記錄下了一次開發(fā)的過程。

一.架構(gòu)設(shè)計(jì)

  • 發(fā)送短信是一個(gè)比較慢的過程,因?yàn)樾枰玫降谌椒?wù)(騰訊云短信服務(wù)),因此我們使用RabbitMq來做異步處理,前端點(diǎn)擊獲取驗(yàn)證碼后,后端做完校驗(yàn)限流后直接返回發(fā)送成功。

  • 發(fā)送短信的服務(wù)是需要收費(fèi)的,而且我們也不允許用戶惡意刷接口,所以需要有一個(gè)接口限流方案,可考慮漏桶算法、令牌桶算法,這里采用令牌桶算法。

二.編碼實(shí)現(xiàn)

① 環(huán)境搭建

  • 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實(shí)現(xiàn)令牌桶算法,令牌桶算法具體細(xì)節(jié)可參考其他博客,這里不贅述,大致就是在 一個(gè)時(shí)間段 內(nèi),存在一定數(shù)量的令牌,我們需要拿到令牌才可以繼續(xù)操作。

所以實(shí)現(xiàn)思路大致就是:

  • Redis 中記錄上次拿取令牌的時(shí)間,以及令牌數(shù),每個(gè)手機(jī)號(hào)對(duì)應(yīng)一個(gè)桶
  • 每次拿令牌時(shí),校驗(yàn)令牌是否足夠。
/**
 * @author YukeSeko
 */
@Component
public class RedisTokenBucket {
    @Resource
    private RedisTemplate<String,String> redisTemplate;
    /**
     *  過期時(shí)間,400秒后過期
     */
    private final long EXPIRE_TIME = 400;
    /**
     * 令牌桶算法,一分鐘以內(nèi),每個(gè)手機(jī)號(hào)只能發(fā)送一次
     * @param phoneNum
     * @return
     */
    public boolean tryAcquire(String phoneNum) {
        // 每個(gè)手機(jī)號(hào)碼一分鐘內(nèi)只能發(fā)送一條短信
        int permitsPerMinute = 1;
        // 令牌桶容量
        int maxPermits = 1;
        // 獲取當(dāng)前時(shí)間戳
        long now = System.currentTimeMillis();
        String key = RedisConstant.SMS_BUCKET_PREFIX + phoneNum;
        // 計(jì)算令牌桶內(nèi)令牌數(shù)
        int tokens = Integer.parseInt(redisTemplate.opsForValue().get(key + "_tokens") == null ? "0" : redisTemplate.opsForValue().get(key + "_tokens"));
        // 計(jì)算令牌桶上次填充的時(shí)間戳
        long lastRefillTime = Long.parseLong(redisTemplate.opsForValue().get(key + "_last_refill_time") == null ? "0" : redisTemplate.opsForValue().get(key + "_last_refill_time"));
        // 計(jì)算當(dāng)前時(shí)間與上次填充時(shí)間的時(shí)間差
        long timeSinceLast = now - lastRefillTime;
        // 計(jì)算需要填充的令牌數(shù)
        int refill = (int) (timeSinceLast / 1000 * permitsPerMinute / 60);
        // 更新令牌桶內(nèi)令牌數(shù)
        tokens = Math.min(refill + tokens, maxPermits);
        // 更新上次填充時(shí)間戳
        redisTemplate.opsForValue().set(key + "_last_refill_time", String.valueOf(now),EXPIRE_TIME, TimeUnit.SECONDS);
        // 如果令牌數(shù)大于等于1,則獲取令牌
        if (tokens >= 1) {
            tokens--;
            redisTemplate.opsForValue().set(key + "_tokens", String.valueOf(tokens),EXPIRE_TIME, TimeUnit.SECONDS);
            // 如果獲取到令牌,則返回true
            return true;
        }
        // 如果沒有獲取到令牌,則返回false
        return false;
    }
}

③ 業(yè)務(wù)代碼

0.Pojo

/**
 * 短信服務(wù)傳輸對(duì)象
 * @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

    /**
     * 發(fā)送短信驗(yàn)證碼
     * @param phoneNum
     * @return
     */
    @GetMapping("/smsCaptcha")
    public BaseResponse<String> smsCaptcha(@RequestParam String phoneNum){
        userService.sendSmsCaptcha(phoneNum);
        // 異步發(fā)送驗(yàn)證碼,這里直接返回成功即可
        return ResultUtils.success("獲取短信驗(yàn)證碼成功!");
    }

2.Service

  • 手機(jī)號(hào)格式校驗(yàn)可參考其他人代碼。
    public Boolean sendSmsCaptcha(String phoneNum) {
        if (StringUtils.isEmpty(phoneNum)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "手機(jī)號(hào)不能為空");
        }
        AuthPhoneNumberUtil authPhoneNumberUtil = new AuthPhoneNumberUtil();
        // 手機(jī)號(hào)碼格式校驗(yàn)
        boolean checkPhoneNum = authPhoneNumberUtil.isPhoneNum(phoneNum);
        if (!checkPhoneNum) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "手機(jī)號(hào)格式錯(cuò)誤");
        }
        //生成隨機(jī)驗(yàn)證碼
        int code = (int) ((Math.random() * 9 + 1) * 10000);
        SmsDTO smsDTO = new SmsDTO(phoneNum,String.valueOf(code));
        return smsUtils.sendSms(smsDTO);
    }

3.發(fā)送短信工具類

  • 提供兩個(gè)方法
    • sendSms:先從令牌桶中獲取令牌,獲取失敗不允許發(fā)短信,獲取成功后,將驗(yàn)證碼信息存入Redis,使用RabbitMq異步發(fā)送短信
    • verifyCode:根據(jù)手機(jī)號(hào)校驗(yàn)驗(yàn)證碼,使用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) {
        // 從令牌桶中取得令牌,未取得不允許發(fā)送短信
        boolean acquire = redisTokenBucket.tryAcquire(smsDTO.getPhoneNum());
        if (!acquire) {
            log.info("phoneNum:{},send SMS frequent", smsDTO.getPhoneNum());
            return false;
        }
        log.info("發(fā)送短信:{}",smsDTO);
        String phoneNum = smsDTO.getPhoneNum();
        String code = smsDTO.getCode();
        // 將手機(jī)號(hào)對(duì)應(yīng)的驗(yàn)證碼存入Redis,方便后續(xù)檢驗(yàn)
        redisTemplate.opsForValue().set(RedisConstant.SMS_CODE_PREFIX + phoneNum, String.valueOf(code), 5, TimeUnit.MINUTES);
        // 利用消息隊(duì)列,異步發(fā)送短信
        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初始化

創(chuàng)建交換機(jī)和消息隊(duì)列

/**
 * RabbitMQ配置
 * @author niumazlb
 */
@Slf4j
@Configuration
public class RabbitMqConfig {
    /**
     * 普通隊(duì)列
     * @return
     */
    @Bean
    public Queue smsQueue(){
        Map<String, Object> arguments = new HashMap<>();
        //聲明死信隊(duì)列和交換機(jī)消息,過期時(shí)間: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);
    }
    /**
     * 死信隊(duì)列:消息重試三次后放入死信隊(duì)列
     * @return
     */
    @Bean
    public Queue deadLetter(){
        return new Queue(SMS_DELAY_QUEUE_NAME, true, false, false);
    }
    /**
     * 主題交換機(jī)
     * @return
     */
    @Bean
    public Exchange smsExchange() {
        return new TopicExchange(SMS_EXCHANGE_NAME, true, false);
    }
    /**
     * 交換機(jī)和普通隊(duì)列綁定
     * @return
     */
    @Bean
    public Binding smsBinding(){
        return new Binding(SMS_QUEUE_NAME, Binding.DestinationType.QUEUE,SMS_EXCHANGE_NAME,SMS_EXCHANGE_ROUTING_KEY,null);
    }
    /**
     * 交換機(jī)和死信隊(duì)列綁定
     * @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短信消息生產(chǎn)者

  • 通過實(shí)現(xiàn)ConfirmCallback、ReturnsCallback接口,提高消息的可靠性
  • sendSmsAsync:將消息的各種信息設(shè)置進(jìn)Redis(重試次數(shù)、狀態(tài)、數(shù)據(jù)),將消息投遞進(jìn)Mq,這里傳入自己設(shè)置的messageId,方便監(jiān)聽器中能夠在Redis中找到這條消息。
/**
 * 向mq發(fā)送消息,并進(jìn)行保證消息可靠性處理
 *
 * @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中投遞發(fā)送短信消息
     *
     * @param smsDTO
     * @throws Exception
     */
    public void sendSmsAsync(SmsDTO smsDTO) {
        String messageId = null;
        try {
            // 將 headers 添加到 MessageProperties 中,并發(fā)送消息
            messageId = UUID.randomUUID().toString();
            HashMap<String, Object> messageArgs = new HashMap<>();
            messageArgs.put("retryCount", 0);
            //消息狀態(tài):0-未投遞、1-已投遞
            messageArgs.put("status", 0);
            messageArgs.put("smsTo", smsDTO);
            //將重試次數(shù)和短信發(fā)送狀態(tài)存入redis中去,并設(shè)置過期時(shí)間
            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,并設(shè)置消息的一些參數(shù)
            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) {
            //出現(xiàn)異常,刪除該短信id對(duì)應(yīng)的redis,并將該失敗消息存入到“死信”redis中去,然后使用定時(shí)任務(wù)去掃描該key,并重新發(fā)送到mq中去
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + messageId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, messageId, smsDTO);
            throw new RuntimeException(e);
        }
    }
    /**
     * 發(fā)布者確認(rèn)的回調(diào)
     *
     * @param correlationData 回調(diào)的相關(guān)數(shù)據(jù)。
     * @param b               ack為真,nack為假
     * @param s               一個(gè)可選的原因,用于nack,如果可用,否則為空。
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean b, String s) {
        // 消息發(fā)送成功,將redis中消息的狀態(tài)(status)修改為1
        if (b) {
            redisTemplate.opsForHash().put(RedisConstant.SMS_MESSAGE_PREFIX + finalId, "status", 1);
        } else {
            // 發(fā)送失敗,放入redis失敗集合中,并刪除集合數(shù)據(jù)
            log.error("短信消息投送失?。簕}-->{}", correlationData, s);
            redisTemplate.delete(RedisConstant.SMS_MESSAGE_PREFIX + finalId);
            redisTemplate.opsForHash().put(RedisConstant.MQ_PRODUCER, finalId, this.smsDTO);
        }
    }
    /**
     * 發(fā)生異常時(shí)的消息返回提醒
     *
     * @param returnedMessage
     */
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.error("發(fā)生異常,返回消息回調(diào):{}", returnedMessage);
        // 發(fā)送失敗,放入redis失敗集合中,并刪除集合數(shù)據(jù)
        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消息監(jiān)聽器

  • 根據(jù)messageId從Redis中找到對(duì)應(yīng)的消息(為了判斷重試次數(shù),規(guī)定重試3次為失敗,加入死信隊(duì)列)
  • 調(diào)用第三方云服務(wù)商提供的短信服務(wù)發(fā)送短信,通過返回值來判斷是否發(fā)送成功
  • 手動(dòng)確認(rèn)消息
/**
 * @author niuma
 * @create 2023-04-29 15:35
 */
@Component
@Slf4j
public class SendSmsListener {
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    @Resource
    private SendSmsUtils sendSmsUtils;
    /**
     * 監(jiān)聽發(fā)送短信普通隊(duì)列
     * @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) {
            //重試次數(shù)大于3,直接放到死信隊(duì)列
            log.error("短信消息重試超過3次:{}",  messageId);
            //basicReject方法拒絕deliveryTag對(duì)應(yīng)的消息,第二個(gè)參數(shù)是否requeue,true則重新入隊(duì)列,否則丟棄或者進(jìn)入死信隊(duì)列。
            //該方法reject后,該消費(fèi)者還是會(huì)消費(fèi)到該條被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參數(shù)為空");
            }
            // 發(fā)送消息
            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("發(fā)送驗(yàn)證碼失敗");
            }
            //手動(dòng)確認(rèn)消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
            log.info("短信發(fā)送成功:{}",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);
        }
    }
    /**
     * 監(jiān)聽到發(fā)送短信死信隊(duì)列
     * @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("監(jiān)聽到死信隊(duì)列消息==>{}",sms);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
        }catch (Exception e){
            channel.basicReject(message.getMessageProperties().getDeliveryTag(),true);
        }
    }
}

7.騰訊云短信服務(wù)

@Component
public class TencentClient {
    @Value("${tencent.secretId}")
    private String secretId;
    @Value("${tencent.secretKey}")
    private String secretKey;
    /**
     * Tencent應(yīng)用客戶端
     * @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;
    /**
     * 發(fā)送短信工具
     * @param phone
     * @return
     * @throws TencentCloudSDKException
     */
    public SendSmsResponse sendSmsResponse (String phone,String code) throws TencentCloudSDKException {
        SendSmsRequest req = new SendSmsRequest();
        /* 短信應(yīng)用ID */
        // 應(yīng)用 ID 可前往 [短信控制臺(tái)](https://console.cloud.tencent.com/smsv2/app-manage) 查看
        req.setSmsSdkAppId(sdkAppId);
        /* 短信簽名內(nèi)容: 使用 UTF-8 編碼,必須填寫已審核通過的簽名 */
        // 簽名信息可前往 [國(guó)內(nèi)短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [國(guó)際/港澳臺(tái)短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的簽名管理查看
        req.setSignName(signName);
        /* 模板 ID: 必須填寫已審核通過的模板 ID */
        // 模板 ID 可前往 [國(guó)內(nèi)短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [國(guó)際/港澳臺(tái)短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看
        req.setTemplateId(templateId);
        /* 模板參數(shù): 模板參數(shù)的個(gè)數(shù)需要與 TemplateId 對(duì)應(yīng)模板的變量個(gè)數(shù)保持一致,若無模板參數(shù),則設(shè)置為空 */
        String[] templateParamSet = [code];
        req.setTemplateParamSet(templateParamSet);
        /* 下發(fā)手機(jī)號(hào)碼,采用 E.164 標(biāo)準(zhǔn),+[國(guó)家或地區(qū)碼][手機(jī)號(hào)]
         * 示例如:+8613711112222, 其中前面有一個(gè)+號(hào) ,86為國(guó)家碼,13711112222為手機(jī)號(hào),最多不要超過200個(gè)手機(jī)號(hào) */
        String[] phoneNumberSet = new String[]{"+86" + phone};
        req.setPhoneNumberSet(phoneNumberSet);
        /* 用戶的 session 內(nèi)容(無需要可忽略): 可以攜帶用戶側(cè) ID 等上下文信息,server 會(huì)原樣返回
        String sessionContext = "";
        req.setSessionContext(sessionContext);
        */
        /* 通過 client 對(duì)象調(diào)用 SendSms 方法發(fā)起請(qǐng)求。注意請(qǐng)求方法名與請(qǐng)求對(duì)象是對(duì)應(yīng)的
         * 返回的 res 是一個(gè) SendSmsResponse 類的實(shí)例,與請(qǐng)求對(duì)象對(duì)應(yīng) */
        SmsClient client = tencentClient.client();
        return client.SendSms(req);
    }
}

配置文件

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

三. 心得

  • 消息隊(duì)列的一個(gè)用法
  • ConfirmCallback、ReturnsCallback接口的使用
  • 騰訊云短信服務(wù)的使用
  • 令牌桶算法的實(shí)踐

 總結(jié)

到此這篇關(guān)于Java實(shí)現(xiàn)短信驗(yàn)證碼的文章就介紹到這了,更多相關(guān)Java短信驗(yàn)證碼內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • idea?maven依賴引入失效無法正常導(dǎo)入依賴問題的解決方法

    idea?maven依賴引入失效無法正常導(dǎo)入依賴問題的解決方法

    有時(shí)候idea導(dǎo)入一個(gè)新項(xiàng)目,或者pom文件修改(新增)了依賴,pom文件和代碼會(huì)報(bào)紅,提示依賴包不存在,下面這篇文章主要給大家介紹了關(guān)于idea?maven依賴引入失效無法正常導(dǎo)入依賴問題的解決方法,需要的朋友可以參考下
    2023-04-04
  • java中接口(interface)及使用方法示例

    java中接口(interface)及使用方法示例

    這篇文章主要介紹了java中接口(interface)及使用方法示例,涉及接口定義的簡(jiǎn)單介紹以及Java語言代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-11-11
  • 基于Java實(shí)現(xiàn)五子棋小游戲(附源碼)

    基于Java實(shí)現(xiàn)五子棋小游戲(附源碼)

    這篇文章主要為大家介紹了如何通過Java實(shí)現(xiàn)簡(jiǎn)單的五子棋游戲,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java游戲開發(fā)有一定幫助,需要的可以參考一下
    2022-11-11
  • Java Builder Pattern建造者模式詳解及實(shí)例

    Java Builder Pattern建造者模式詳解及實(shí)例

    這篇文章主要介紹了Java Builder Pattern建造者模式詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • OpenCV實(shí)現(xiàn)普通閾值

    OpenCV實(shí)現(xiàn)普通閾值

    這篇文章主要為大家詳細(xì)介紹了OpenCV實(shí)現(xiàn)普通閾值,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • Java利用三目運(yùn)算符比較三個(gè)數(shù)字的大小

    Java利用三目運(yùn)算符比較三個(gè)數(shù)字的大小

    今天小編就為大家分享一篇關(guān)于Java利用三目運(yùn)算符比較三個(gè)數(shù)字的大小,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-12-12
  • Java怎樣創(chuàng)建集合才能避免造成內(nèi)存泄漏你了解嗎

    Java怎樣創(chuàng)建集合才能避免造成內(nèi)存泄漏你了解嗎

    內(nèi)存泄漏是指無用對(duì)象持續(xù)占有內(nèi)存或無用對(duì)象的內(nèi)存得不到及時(shí)釋放,從而造成內(nèi)存空間的浪費(fèi)稱為內(nèi)存泄漏。長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用就很可能發(fā)生內(nèi)存泄漏,盡管短生命周期對(duì)象已經(jīng)不再需要,但是因?yàn)殚L(zhǎng)生命周期持有它的引用而導(dǎo)致不能被回收
    2021-09-09
  • Java8?Stream之groupingBy分組使用解讀

    Java8?Stream之groupingBy分組使用解讀

    這篇文章主要介紹了Java8?Stream之groupingBy分組使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Java獲取resources下文件路徑的幾種方法及遇到的問題

    Java獲取resources下文件路徑的幾種方法及遇到的問題

    這篇文章主要給大家介紹了關(guān)于Java獲取resources下文件路徑的幾種方法及遇到的問題,在Java開發(fā)中經(jīng)常需要讀取項(xiàng)目中resources目錄下的文件或獲取資源路徑,需要的朋友可以參考下
    2023-12-12
  • Java并發(fā)編程必備之Synchronized關(guān)鍵字深入解析

    Java并發(fā)編程必備之Synchronized關(guān)鍵字深入解析

    本文我們深入探索了Java中的Synchronized關(guān)鍵字,包括其互斥性和可重入性的特性,文章詳細(xì)介紹了Synchronized的三種使用方式:修飾代碼塊、修飾普通方法和修飾靜態(tài)方法,感興趣的朋友一起看看吧
    2025-04-04

最新評(píng)論