1.对接第三方短信接口需要模拟一个http请求
用http-client这个包来操作。
在pom.xml文件中加入http-client的包依赖
org.apache.httpcomponents
httpclient
2.新建一个httputil类来模拟get/post请求
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class HttpUtil {
private static final String ENCODING = "UTF-8";
public static String post(String url, Map paramsMap) {
CloseableHttpClient client = HttpClients.createDefault();
String responseText = "";
CloseableHttpResponse response = null;
try {
HttpPost method = new HttpPost(url);
if (paramsMap != null) {
List paramList = new ArrayList();
for (Map.Entry param : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(param.getKey(), param.getValue());
paramList.add(pair);
}
method.setEntity(new UrlEncodedFormEntity(paramList, ENCODING));
}
response = client.execute(method);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseText = EntityUtils.toString(entity);
}
} catch (Exception e) {
log.error("http request failed",e);
} finally {
try {
response.close();
} catch (Exception e) {
log.error("",e);
}
}
return responseText;
}
public static String get(String url, Map paramsMap) {
CloseableHttpClient client = HttpClients.createDefault();
String responseText = "";
CloseableHttpResponse response = null;
try {
String getUrl = url+"?";
if (paramsMap != null) {
for (Map.Entry param : paramsMap.entrySet()) {
getUrl += param.getKey() + "=" + URLEncoder.encode(param.getValue(), ENCODING)+"&";
}
}
HttpGet method = new HttpGet(getUrl);
response = client.execute(method);
HttpEntity entity = response.getEntity();
if (entity != null) {
responseText = EntityUtils.toString(entity);
}
} catch (Exception e) {
log.error("http request failed",e);
} finally {
try {
response.close();
} catch (Exception e) {
log.error("",e);
}
}
return responseText;
}
}
Service接口:
public interface UserService {
void sendVercode(String mobile, String ip) throws ustomException;
}
Service实现类:
@Service
@Slf4j
public class UserServiceImpl implements UserService {
private static final String VERIFYCODE_PREFIX = "verify.code.";
private static final String SMS_QUEUE ="sms.queue"
@Autowired
private CommonCacheUtil cacheUtil;
@Autowired
private SmsProcessor smsProcessor;
@Override
public void sendVercode(String mobile, String ip) throws CustomException {
String verCode = RandomNumberCode.verCode();
int result = cacheUtil.cacheForVerificationCode(VERIFYCODE_PREFIX+mobile,verCode,"reg",60,ip);
if (result == 1) {
log.info("当前验证码未过期,请稍后重试");
throw new CustomException("当前验证码未过期,请稍后重试");
} else if (result == 2) {
log.info("超过当日验证码次数上线");
throw new CustomException("超过当日验证码次数上限");
} else if (result == 3) {
log.info("超过当日验证码次数上限 {}", ip);
throw new CustomException(ip + "超过当日验证码次数上限");
}
log.info("Sending verify code {} for phone {}", verCode, mobile);
//校验通过 发送短信 发消息到队列
Destination destination = new ActiveMQQueue(SMS_QUEUE);
Map smsParam = new HashMap<>();
smsParam.put("mobile",mobile);
smsParam.put("tplId", Constants.MDSMS_VERCODE_TPLID);
smsParam.put("vercode",verCode);
String message = JSON.toJSONString(smsParam);
smsProcessor.sendSmsToQueue(destination,message);
} }
RandomNumberCode方法生成四位随机验证码
public class RandomNumberCode {
public static String verCode(){
Random random =new Random();
return StringUtils.substring(String.valueOf(random.nextInt()*-10), 2, 6);
}
public static String randomNo(){
Random random =new Random();
return String.valueOf(Math.abs(random.nextInt()*-10));
}
}
将验证码信息存到redis缓存中
springboot整合redis
添加配置文件在application.yml中
redis:
host: 127.0.0.1
port: 6379
auth:
max-idle: 5
max-total: 10
max-wait-millis: 3000
新建一个类来获取变量值:
@Data //该注释自动帮你完成get/set方法,在pom.xml文件引入org.projectlombok包,加速开发
/*注入*/
@Component
public class Parameters {
/*****redis config start*******/
@Value("${redis.host}")
private String redisHost;
@Value("${redis.port}")
private int redisPort;
@Value("${redis.auth}")
private String redisAuth;
@Value("${redis.max-idle}")
private int redisMaxTotal;
@Value("${redis.max-total}")
private int redisMaxIdle;
@Value("${redis.max-wait-millis}")
private int redisMaxWaitMillis;
/*****redis config end*******/
}
创建JedisPoolWrapper类
@Component
@Slf4j
public class JedisPoolWrapper {
private JedisPool jedisPool = null;
@Autowired
private Parameters parameters;
@PostConstruct
public void init() throws CustomException {
try{
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(parameters.getRedisMaxIdle());
config.setMaxTotal(parameters.getRedisMaxTotal());
config.setMaxWaitMillis(parameters.getRedisMaxWaitMillis());
jedisPool = new JedisPool(config,parameters.getRedisHost(),parameters.getRedisPort(),2000);
}catch (Exception e){
log.error("Fail to initialize redis pool",e);
throw new CustomException("初始化redis失败");
}
}
public JedisPool getJedisPool(){
return jedisPool;
}
}
创建一个CommonCacheUtil工具类
@Component
@Slf4j
public class CommonCacheUtil {
private static final String TOKEN_PREFIX = "token.";
private static final String USER_PREFIX = "user.";
@Autowired
private JedisPoolWrapper jedisPoolWrapper;
/**
* 缓存 key value 永久
* @param key
* @param value
*/
public void cache(String key, String value) {
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);//选择redis第0片区
Jedis.set(key, value);
}
}
} catch (Exception e) {
log.error("Fail to cache value", e);
}
}
/**
* 获取缓存key
* @param key
* @return
*/
public String getCacheValue(String key) {
String value = null;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis Jedis = pool.getResource()) {
Jedis.select(0);
value = Jedis.get(key);
}
}
} catch (Exception e) {
log.error("Fail to get cached value", e);
}
return value;
}
/**
* 设置key value 以及过期时间
* @param key
* @param value
* @param expiry
* @return
*/
public long cacheNxExpire(String key, String value, int expiry) {
long result = 0;
try {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
result = jedis.setnx(key, value);
jedis.expire(key, expiry);
}
}
} catch (Exception e) {
log.error("Fail to cacheNx value", e);
}
return result;
}
/**
* 删除缓存key
* @param key
*/
public void delKey(String key) {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
jedis.del(key);
} catch (Exception e) {
log.error("Fail to remove key from redis", e);
}
}catch (Exception e) {
log.error("Fail to remove key from redis", e);
}
}
}
/**
* 缓存手机验证码专用 限制了发送次数
* @return 1 当前验证码未过期 2 手机号超过当日验证码次数上限 3 ip超过当日验证码次数上线
*/
public int cacheForVerificationCode(String key, String verCode, String type, int second, String ip) throws Exception {
JedisPool pool = jedisPoolWrapper.getJedisPool();
if (pool != null) {
try (Jedis jedis = pool.getResource()) {
jedis.select(0);
try {
String ipKey = "ip."+ip;
if(ip==null){
return 3;
} else {
String ipSendCount = jedis.get(ipKey);
try {
if (ipSendCount != null && Integer.parseInt(ipSendCount) >= 10) {
return 3;
}
} catch (NumberFormatException e) {
log.error("Fail to process ip send count", e);
return 3;
}
long succ = jedis.setnx(key, verCode);
if (succ == 0) {
return 1;
}
String sendCount = jedis.get(key + "." + type);
try {
if (sendCount != null && Integer.parseInt(sendCount) >= 10) {
jedis.del(key);
return 2;
}
} catch (NumberFormatException e) {
log.error("Fail to process send count", e);
jedis.del(key);
return 2;
}
try {
jedis.expire(key, second);
long val = jedis.incr(key + "." + type);
if (val == 1) {
jedis.expire(key + "." + type, 86400);
}
jedis.incr(ipKey);
if (val == 1) {
jedis.expire(ipKey, 86400);
}
} catch (Exception e) {
log.error("Fail to cache data into redis", e);
}
}
} catch (Exception e) {
log.error("Fail to set vercode to redis", e);
throw e;
}
} catch (Exception e) {
log.error("Fail to cache for expiry", e);
throw new Exception("Fail to cache for expiry");
}
}
return 0;
}
}
新建一个短信接口
public interface SmsSender {
void sendSms(String phone,String tplId,String params);
}
实现类,依据官方demo作修改
@Service("verCodeService")
@Slf4j
public class MiaoDiSmsSender implements SmsSender{
private static String operation = "/industrySMS/sendSMS";
@Override
public void sendSms(String phone,String tplId,String params){
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String timestamp = sdf.format(new Date());
String sig = MD5Util.getMD5(Constants.MDSMS_ACCOUNT_SID +Constants.MDSMS_AUTH_TOKEN +timestamp);
String url = Constants.MDSMS_REST_URL +operation;
Map param = new HashMap<>();
param.put("accountSid",Constants.MDSMS_ACCOUNT_SID);
param.put("to",phone);
param.put("templateid",tplId);
param.put("param",params);
param.put("timestamp",timestamp);
param.put("sig",sig);
param.put("respDataType","json");
String result = HttpUtil.post(url,param);
JSONObject jsonObject = JSON.parseObject(result);
if(!jsonObject.getString("respCode").equals("00000")){
log.error("fail to send sms to "+phone+":"+params+":"+result);
}
} catch (Exception e) {
log.error("fail to send sms to "+phone+":"+params);
}
}
}
整合ActiveMQ,在pom.xml文件中加入包依赖
org.springframework.boot
spring-boot-starter-activemq
新建一个消息生产者SmsProcessor类
@Component("smsProcessor")
public class SmsProcessor {
@Autowired
private JmsMessagingTemplate jmsTemplate;
@Autowired
@Qualifier("verCodeService")
private SmsSender smsSender;
public void sendSmsToQueue(Destination destination, final String message){
jmsTemplate.convertAndSend(destination, message);
}
@JmsListener(destination="sms.queue")
public void doSendSmsMessage(String text){
JSONObject jsonObject = JSON.parseObject(text);
smsSender.sendSms(jsonObject.getString("mobile"),jsonObject.getString("tplId"),jsonObject.getString("vercode"));
}
}
Controller代码:
@RequestMapping("/sendVercode")
public ApiResult sendVercode(@RequestBody User user, HttpServletRequest request){
ApiResult resp = new ApiResult();
try {
userService.sendVercode(user.getMobile(),getIpFromRequest(request));
}catch(CustomException e){
resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
resp.setMessage(e.getMessage());
}
catch(CustomException e){
log.error("Fail to login",e);
resp.setCode(Constants.RESP_STATUS_INTERNAL_ERROR);
resp.setMessage("内部错误");
}
return resp;
}
User类
@Data
public class User {
private Long id;
private String nickname;
private String mobile;
private String headImg;
private Byte verifyFlag;
private Byte enableFlag;
}
统一返回数据ApiResult类
public class ApiResult {
private int code = Constants.RESP_STATUS_OK;
private String message;
private T data;
}
自定义异常类:CustomException
public class CustomException extends Exception {
public CustomException(String message){
super(message);
}
}
常量类:Constants
public class Constants {
/**自定义状态码 start**/
public static final int RESP_STATUS_OK = 200;
public static final int RESP_STATUS_NOAUTH = 401;
public static final int RESP_STATUS_INTERNAL_ERROR = 500;
public static final int RESP_STATUS_BADREQUEST = 400;
/**秒滴SMS start**/
public static final String MDSMS_ACCOUNT_SID = "填写自己的SID";
public static final String MDSMS_AUTH_TOKEN = "填写自己TOKEN";
public static final String MDSMS_REST_URL = "https://api.miaodiyun.com/20150822";
public static final String MDSMS_VERCODE_TPLID = "模板编号";
/**秒滴SMS end**/
public static final String REQUEST_TOKEN_KEY = "user-token";
public static final String REQUEST_VERSION_KEY = "version"
}
考虑短信接口的安全性
1.IP 根据IP来判定 超过十次 不发送验证码手机号 同一个手机号 不能超过多少次
通过调用redis工具类中的cacheForVerificationCode方法
2.获取真正的ip+本机ip测试(ngnix负载均衡,请求分发)
protected String getIpFromRequest(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}