sms短信网关对接
因为工作的需求,需要对接短信网关,业务上就是一个注册用户时,需要发送手机验证码;可能别的公司都是使用第三方接口,但是大点的公司,为了安全,他们都有自己的短信消息中心(SMSC)
1.业务需求
- 1.对接短信网关,发送消息,下行发送(MT) 使用openSMPP开源的开发包
- 2.验证码使用redis存储(主要原因是,分布式存储,支持过期时间)
2. 下面是简单的代码实例(包括发送短信和redis存储验证码)
pom中添加依赖
<dependency> <groupId>org.opensmpp</groupId> <artifactId>opensmpp-core</artifactId> <version>3.0.2</version> </dependency>
public class SmsController { @Autowiredprivate RedisTemplate redisTemplate; public boolean sendVerifyCode() { String destination = "8613594312686"; String host = "192.168.XX"; String port = "12345"; String userName = "test"; String password = "test"; //存储redis中的key String key = getDestinationNumKey(destination); //redis中已存在验证码,则使用redis中存储的验证码(在15分钟的过期时间内发送同一条验证码),否则生成6位数字验证码 Object valueByKey = getValueByKey(key); String verificationCode = Objects.nonNull(valueByKey) ? valueByKey.toString() : String.valueOf((int) (Math.random() * 900000 + 100000)); //租户(不传,默认default) boolean sendSuccess = sendMessage(host, port, userName, password, destination, verificationCode); //发送成功后,验证码存入redis中,并设置过期时间(默认15分钟) if (sendSuccess) { putValueToRedisWithTimeout(key, verificationCode, 15, TimeUnit.MINUTES); } return sendSuccess; } private String getDestinationNumKey(String destination) { return String.format("%s:%s", "DESTINATION", destination); } public boolean sendMessage(String host, String port, String userName, String password, String phonenumber, String verifyCode) { log.info("start to send sms notification, reciever,host {},port {}, userName {} password {} destinations is {} verifyCode {}", host, port, userName, password, phonenumber, verifyCode); try { TCPIPConnection connection = new TCPIPConnection(host, Integer.parseInt(port)); Session session = new Session(connection); BindRequest request = new BindTransmitter(); request.setSystemId(userName); request.setPassword(password); //SMPP protocol version request.setInterfaceVersion((byte) 0x34); request.setSystemType("SMPP"); BindResponse bind = session.bind(request); log.info("bind response debugString {},response command status {}", bind.debugString(), bind.getCommandStatus()); String content = "[Registration]" + verifyCode + " is your verification code. Valid in 15 minutes. Please do not share this code with anyone else."; SubmitSM submitSM = constructRequest(phonenumber, content); //bund faild 会导致TCPIPConnection关闭从而导致outputStream关闭从而导致no SubmitSMResp response = session.submit(submitSM); log.info("send message result {},command status is {}", response.debugString(), response.getCommandStatus()); } catch (Exception e) { log.error("invoke sms session exception", e); } } private SubmitSM constructRequest(String phoneNumber, String content) throws WrongLengthOfStringException, UnsupportedEncodingException { String recipientPhoneNumber = phoneNumber; SubmitSM request = new SubmitSM(); request.setSourceAddr(createAddress("test")); request.setDestAddr(createAddress(recipientPhoneNumber)); request.setShortMessage(content, Data.ENC_UTF8); request.setReplaceIfPresentFlag((byte) 0); request.setEsmClass((byte) 0); request.setProtocolId((byte) 0); request.setPriorityFlag((byte) 0); request.setRegisteredDelivery((byte) 1);// we want delivery reports request.setDataCoding((byte) 0); request.setSmDefaultMsgId((byte) 0); return request; } private Address createAddress(String address) throws WrongLengthOfStringException { Address addressInst = new Address(); // national ton addressInst.setTon((byte) 5); // numeric plan indicator addressInst.setNpi((byte) 0); addressInst.setAddress(address, Data.SM_ADDR_LEN); return addressInst; } /** * Redis中存储Value(value可为string,map,list,set等),并设置过期时间 * * @param key * @param value * @param timeout * @param unit */ public void putValueToRedisWithTimeout(Object key, Object value, final long timeout, final TimeUnit unit) { try { ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set(key, value, timeout, unit); } catch (Exception e) { } } /** * 根据key获取Value值 * * @param key */ public Object getValueByKey(Object key) { Object value = null; try { ValueOperations valueOperations = redisTemplate.opsForValue(); value = valueOperations.get(key); } catch (Exception e) { } return value; } /** * 根据key获取Value值 * * @param key */ public Object deleteKey(Object key) { Object value = null; try { if (redisTemplate.hasKey(key)) { redisTemplate.delete(key); } } catch (Exception e) { } return value; } }
3. 校验验证码
此处代码就不贴了,直接根据手机号去redis中查询,若有值进行比较是否对错,若无值,直接抛出验证码无效即可
4. 对接网关的api
请参考:https://www.world-text.com/docs/interfaces/SMPP/ 和 https://www.smssolutions.net/tutorials/smpp/smpperrorcodes/
5.短信网关模拟器
对接在测试环境时怎么测试是个问题,从网上找到一个模拟SMPP的,感觉挺好用,此处分享地址:SMPP模拟器
其他的参考:
https://support.nowsms.com/discus/messages/1/SMPP_v3_4_Issue1_2-24857.pdf
https://www.activexperts.com/sms-component/smpp-specifications/smpp-pdu-definition/
https://www.openmarket.com/docs/Content/apis/v4smpp/bind.htm
https://zhuanlan.zhihu.com/p/58237629
作者:guanbin —— 纵码万里千山
出处:https://www.cnblogs.com/guanbin-529/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。