当前位置:主页 > 软件编程 > JAVA代码 >

Java实现发送短信验证码+redis限制发送的次数功能

时间:2022-12-03 12:09:56 | 栏目:JAVA代码 | 点击:

java实现短信验证码发送,由于我们使用第三方平台进行验证码的发送,所以首先,我们要在一个平台进行注册。这样的平台有很多,有的平台在新建账号的时候会附带赠几条免费短信。这里我仅做测试使用(具体哪个平台见参考三,很简单,注册账号就行,记得添加短信签名)。

另外,在实际项目中,如果有人恶意攻击,不停的发送短信验证码,就会造成很大的损失。故对发送次数做一定的限制就非常必要,这里我们限制一个手机号一天可以发多少短信和短信平台无关。

这里采用的是存redis来实现这一个功能。就是每次调用发送验证码这个接口都会判断手机号码是否在redis中存为key了。如果没有则创建一个key为手机号码value是1.因为redis中不支持数字所以将其变为了string类型。如果redis中已经有这个key了则将此key的值取出来加1再存进redis中。

代码实现:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lmc</groupId>
    <artifactId>springboot-sendsms</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-sendsms</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </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-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

RespBean.java

package com.lmc.bean;
 
public class RespBean {
    private Integer status;
    private String msg;
    private Object obj;
 
    public static RespBean build() {
        return new RespBean();
    }
 
    public static RespBean ok(String msg) {
        return new RespBean(200, msg, null);
    }
 
    public static RespBean ok(String msg, Object obj) {
        return new RespBean(200, msg, obj);
    }
 
    public static RespBean error(String msg) {
        return new RespBean(500, msg, null);
    }
 
    public static RespBean error(String msg, Object obj) {
        return new RespBean(500, msg, obj);
    }
 
    private RespBean() {
    }
 
    private RespBean(Integer status, String msg, Object obj) {
        this.status = status;
        this.msg = msg;
        this.obj = obj;
    }
 
    public Integer getStatus() {
        return status;
    }
 
    public RespBean setStatus(Integer status) {
        this.status = status;
        return this;
    }
 
    public String getMsg() {
        return msg;
    }
 
    public RespBean setMsg(String msg) {
        this.msg = msg;
        return this;
    }
 
    public Object getObj() {
        return obj;
    }
 
    public RespBean setObj(Object obj) {
        this.obj = obj;
        return this;
    }
}

RedisConfig.java

package com.lmc.config;
 
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
        StringRedisTemplate template = new StringRedisTemplate();
}

SMSController.java

package com.lmc.controller;
 
import com.lmc.bean.RespBean;
import com.lmc.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * @description:
 * @Author: lmc
 * @date: 2021/12/26 10:21
 */
@RestController
public class SMSController {
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @RequestMapping("/send")
    public RespBean sendSMS() {
        String Uid = "xxxxxxxx";
        String Key = "xxxxxxxxxxxxxxx";
        String smsMob = "xxxxxxxxx";
        String sendSMSCount = "sendSMSCount:" + smsMob;
        if ("2".equals(stringRedisTemplate.opsForValue().get(sendSMSCount))) {
            return RespBean.error("今天已达到发送短信验证码上限,请明天再试");
        }
        //短信内容
        String smsText = "欢迎使用xx系统,验证码:8888";
        Map maps = new HashMap();
        maps.put("Uid", Uid);
        maps.put("Key", Key);
        maps.put("smsMob", smsMob);
        maps.put("smsText", smsText);
        String result = HttpClientUtils.sendHttpPost("http://utf8.sms.webchinese.cn", maps);
        int i = Integer.parseInt(result);
        if (i > 0) {
            if (stringRedisTemplate.opsForValue().get(sendSMSCount) == null) {
                stringRedisTemplate.opsForValue().set(sendSMSCount, "1", getEndTime(), TimeUnit.MILLISECONDS);
            } else {
                String value = stringRedisTemplate.opsForValue().get(sendSMSCount);
                int times = Integer.parseInt(value) + 1;
                String timesStr = String.valueOf(times);
                stringRedisTemplate.opsForValue().set(sendSMSCount, timesStr, getEndTime(), TimeUnit.MILLISECONDS);
            }
            return RespBean.ok("发送成功");
        return RespBean.ok("发送失败");
    }
    
    //获取当前时间到今天结束时间所剩余的毫秒数:
    public static long getEndTime() {
        //获取当前时间的毫秒数
        long time = new java.util.Date().getTime();
        //获取到今天结束的毫秒数
        Calendar todayEnd = Calendar.getInstance();
        todayEnd.set(Calendar.HOUR_OF_DAY, 23); // Calendar.HOUR 12小时制。HOUR_OF_DAY 24小时制
        todayEnd.set(Calendar.MINUTE, 59);
        todayEnd.set(Calendar.SECOND, 59);
        todayEnd.set(Calendar.MILLISECOND, 999);
        long endTime = todayEnd.getTimeInMillis();
        //这里endTime-time获取的是到23:59:59:999的毫秒数。再加1才是到24点整的毫秒数
        return endTime-time+1;
}

HttpClientUtils.java(HttpClient工具类,可以复用)

package com.lmc.utils;
 
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.util.PublicSuffixMatcher;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HttpClientUtils {
	private static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
	// 链接相关参数
	private static int socketTimeout = 15000;
	private static int connectTimeout = 15000;
	private static int connectionRequestTimeout = 15000;
	private static RequestConfig requestConfig = null;
	// 连接池相关参数
	private static int connMgrMaxTotal = 100;
	private static int connMgrMaxPerRoute = 50;
	private static PoolingHttpClientConnectionManager connMgr = null;
	
	static {
		requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).setConnectionRequestTimeout(connectionRequestTimeout).build();
		connMgr = new PoolingHttpClientConnectionManager();
		connMgr.setDefaultMaxPerRoute(connMgrMaxPerRoute);
		connMgr.setMaxTotal(connMgrMaxTotal);
	}
	private static String doHttp(HttpRequestBase httpRequestBase) {
		CloseableHttpClient httpClient = null;
		CloseableHttpResponse response = null;
		String responseContent = null;
		
		try {
			// 创建默认的httpClient实例.
			String scheme = httpRequestBase.getURI().getScheme();
			if (scheme.equalsIgnoreCase("https")) {
				PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(new URL(httpRequestBase.getURI().toString()));
				DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
				httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).setConnectionManager(connMgr).build();
				//httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).build();
			} else if (scheme.equalsIgnoreCase("http")) {
				httpClient = HttpClients.custom().setConnectionManager(connMgr).build();
				//httpClient = HttpClients.createDefault();
			} else {
				throw new IllegalArgumentException("url的scheme错误,必须是http或者https! ");
			}
			httpRequestBase.setConfig(requestConfig);
			// 执行请求
			response = httpClient.execute(httpRequestBase);
			// 如果这里有必要获取的是其他资料都可以在这里进行逻辑处理
			responseContent = EntityUtils.toString(response.getEntity(), "UTF-8");
			return responseContent;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				// 关闭连接,释放资源
				if (response != null) {
					// EntityUtils.consume(response.getEntity());
					response.close();
				}
				// 这里不能关闭httpClient,这个会关链接池
				//if (httpClient != null) {
				//  httpClient.close();
				//}
				
			} catch (IOException e) {
				e.printStackTrace();
		}
		return responseContent;
	/**
	 *  sendHttpGet(url)
	 * @param url
	 * @return
	 */
	public static String sendHttpGet(String url) {
		return doHttp(new HttpGet(url));
	 * sendHttpGet()
	 * @param param key1=value1&key2=value2&key3=value3
	public static String sendHttpGet(String url, String param) {
		// 创建httpGet
		HttpGet httpGet = new HttpGet(url + '?' + param);
		return doHttp(httpGet);
	 * sendHttpPost()
	public static String sendHttpPost(String url, String param) {
		// 创建httpPost
		HttpPost httpPost = new HttpPost(url);
			StringEntity stringEntity = new StringEntity(param, "UTF-8");
			stringEntity.setContentType("application/x-www-form-urlencoded");
			httpPost.setEntity(stringEntity);
		return doHttp(httpPost);
	 * sendHttpGet
	 * @param param 是个map<String, String>
	public static String sendHttpGet(String url, Map<String, String> param) {
		String paramStr = "";
		for (String key : param.keySet()) {
			String tmp = "";
			tmp = "&" + key + "=" + param.get(key);
			paramStr += tmp;
		paramStr = paramStr.substring(1);
		HttpGet httpGet = new HttpGet(url + '?' + paramStr);
        return doHttp(httpGet);
	 * sendHttpPost
	 * @param param 是个map<String,String>
	public static String sendHttpPost(String url, Map<String, String> param) {
		// 创建参数队列 
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        for (String key : param.keySet()) {
            nameValuePairs.add(new BasicNameValuePair(key, param.get(key)));   
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));    
        } catch (Exception e) {
            e.printStackTrace();
        
        return doHttp(httpPost);
	public static String sendHttpPostJson(String url, String json) {
			// StringEntity stringEntity = new StringEntity(param, "UTF-8");
			// stringEntity.setContentType("application/json");
			// stringEntity.setContentEncoding("UTF-8");
			StringEntity stringEntity = new StringEntity(json, ContentType.create("application/json", "UTF-8"));
						
	public static void main(String[] args) {
		String url = "http://api.crpay.com/payapi/gateway";
		String param = "merchant_no=TOF00001&method=unified.trade.pay&version=1.0";
		Map map = new HashMap<String, String>();
		map.put("merchant_no", "TOF00001");
		map.put("method", "unified.trade.pay");
		map.put("version", "1.0");
		// 这个工具是走的链接池,但是在关闭httpClient会关闭连接池的地方已经注销
		//System.out.println(HttpClientUtils.sendHttpPost(url, map));
		//System.out.println(HttpClientUtils.sendHttpPost(url, param));
		//System.out.println(HttpClientUtils.sendHttpGet(url, map));
		System.out.println(HttpClientUtils.sendHttpGet("https://www.baidu.com"));
		System.out.println(HttpClientUtils.sendHttpGet("http://www.baidu.com/s?wd=aaa"));
		Map map2 = new HashMap<String, String>();
		map2.put("wd", "aaa");
		System.out.println(HttpClientUtils.sendHttpGet("http://www.baidu.com/s",map2));
		// doHttp是静态私有方法,不能使用多次,会报Connection pool shut down 
		System.out.println(HttpClientUtils.doHttp(new HttpGet("http://www.baidu.com/s?wd=aaa")));
		System.out.println(HttpClientUtils.doHttp(new HttpGet("https://www.baidu.com/")));
		System.out.println(HttpClientUtils.sendHttpGet("https://www.cnblogs.com/hugo-zhangzhen/p/6858013.html"));
		System.out.println(HttpClientUtils.sendHttpGet("https://www.cnblogs.com/hugo-zhangzhen/p/6739658.html"));
		System.out.println(HttpClientUtils.sendHttpGet("https://www.cnblogs.com/hugo-zhangzhen/p/6737810.html"));
		System.out.println(HttpClientUtils.sendHttpGet("http://blog.csdn.net/xiechengfa/article/details/42016153"));
}

application.properties

# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456

项目结果如下:

 结果展示:

使用postman调用接口,超过2次后,显示如下。

在具体项目中的流程一般如下:

①构造手机验证码,需要生成一个6位的随机数字串;

②找短信平台获取使用接口向短信平台发送手机号和验证码,然后短信平台再把验证码发送到制定手机号上;

③将手机号验证码、操作时间存入Session中,作为后面验证使用;

④接收用户填写的验证码、手机号及其他注册数据;

⑤对比提交的验证码与Session中的验证码是否一致,同时判断提交动作是否在有效期内;

⑥验证码正确且在有效期内,请求通过,处理相应的业务。

参考:

接收短信验证码条数限制(java发送短信验证码限制) - 简书

Java如何实现短信验证码功能? - 知乎

Java 实现手机发送短信验证码 - 胖头陀春天 - 博客园

您可能感兴趣的文章:

相关文章