首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 對接聚合數據短信驗證碼 API 完整實現

     編輯:Java 相關知識

業務場景

接入第三方短信平台(以聚合數據 Juhe.cn 為例),實現短信驗證碼發送、模板管理、屏蔽詞檢測等功能,適用於用戶註冊、登錄驗證、支付確認等場景。

核心接口說明

功能接口地址關鍵參數
發送短信https://v.juhe.cn/sms/sendmobile, tpl_id, tpl_value, key
屏蔽詞檢測https://v.juhe.cn/sms/blackword, key

工具類完整實現

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import net.sf.json.JSONObject;

public class JuheSmsClient {

    private static final String CHARSET = "UTF-8";
    private static final int CONNECT_TIMEOUT = 30000;
    private static final int READ_TIMEOUT = 30000;
    private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
    
    // 配置中心讀取,勿硬編碼
    private static final String APP_KEY = System.getenv("JUHE_SMS_APPKEY");
    private static final String SEND_URL = "https://v.juhe.cn/sms/send";
    private static final String BLACK_URL = "https://v.juhe.cn/sms/black";

    /** 發送模板短信 */
    public static SmsResult sendTemplateSms(String mobile, String tplId, Map tplValues) {
        if (StringUtils.isBlank(APP_KEY)) {
            return SmsResult.fail("APP_KEY 未配置");
        }
        
        Map params = new HashMap<>();
        params.put("mobile", mobile);
        params.put("tpl_id", tplId);
        params.put("key", APP_KEY);
        params.put("dtype", "json");
        
        // 模板變量值需 URL 編碼
        if (tplValues != null && !tplValues.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry entry : tplValues.entrySet()) {
                sb.append("#").append(entry.getKey()).append("#=")
                  .append(encode(entry.getValue())).append("&");
            }
            params.put("tpl_value", sb.substring(0, sb.length() - 1));
        }

        try {
            String response = doPost(SEND_URL, params);
            return parseResponse(response);
        } catch (Exception e) {
            return SmsResult.fail("請求異常: " + e.getMessage());
        }
    }

    /** 屏蔽詞檢測 */
    public static SmsResult checkBlackWord(String content) {
        Map params = new HashMap<>();
        params.put("word", content);
        params.put("key", APP_KEY);
        
        try {
            String response = doGet(BLACK_URL, params);
            return parseResponse(response);
        } catch (Exception e) {
            return SmsResult.fail("檢測異常: " + e.getMessage());
        }
    }

    // ==================== 內部方法 ====================
    
    private static String doGet(String url, Map params) throws IOException {
        String query = buildQuery(params);
        HttpURLConnection conn = createConnection(url + "?" + query, "GET");
        return readResponse(conn);
    }

    private static String doPost(String url, Map params) throws IOException {
        HttpURLConnection conn = createConnection(url, "POST");
        conn.setDoOutput(true);
        try (OutputStream os = conn.getOutputStream()) {
            os.write(buildQuery(params).getBytes(StandardCharsets.UTF_8));
        }
        return readResponse(conn);
    }

    private static HttpURLConnection createConnection(String urlStr, String method) throws IOException {
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(method);
        conn.setRequestProperty("User-Agent", USER_AGENT);
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setConnectTimeout(CONNECT_TIMEOUT);
        conn.setReadTimeout(READ_TIMEOUT);
        conn.setUseCaches(false);
        return conn;
    }

    private static String buildQuery(Map params) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry entry : params.entrySet()) {
            if (sb.length() > 0) sb.append('&');
            sb.append(entry.getKey()).append('=').append(encode(String.valueOf(entry.getValue())));
        }
        return sb.toString();
    }

    private static String encode(String value) {
        try {
            return URLEncoder.encode(value, CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private static String readResponse(HttpURLConnection conn) throws IOException {
        try (InputStream is = conn.getResponseCode() == 200 ? conn.getInputStream() : conn.getErrorStream();
             BufferedReader br = new BufferedReader(new InputStreamReader(is, CHARSET))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) sb.append(line);
            return sb.toString();
        } finally {
            conn.disconnect();
        }
    }

    private static SmsResult parseResponse(String jsonStr) {
        JSONObject json = JSONObject.fromObject(jsonStr);
        int errorCode = json.getInt("error_code");
        if (errorCode == 0) {
            return SmsResult.success(json.get("result"));
        }
        return SmsResult.fail(errorCode + ": " + json.getString("reason"));
    }

    // ==================== 結果封裝 ====================
    
    @Data
    @AllArgsConstructor
    public static class SmsResult {
        private boolean success;
        private Object data;
        private String message;
        
        public static SmsResult success(Object data) {
            return new SmsResult(true, data, null);
        }
        public static SmsResult fail(String message) {
            return new SmsResult(false, null, message);
        }
    }
}

業務層調用示例

@Service
public class SmsService {

    private static final String VERIFY_TPL_ID = "123456"; // 平台申請的模板ID
    
    /** 發送註冊驗證碼 */
    public void sendRegisterCode(String mobile, String code) {
        // 1. 限流檢查(Redis 計數器,每分鐘 1 條,每天 10 條)
        if (!rateLimiter.tryAcquire("sms:register:" + mobile)) {
            throw new BusinessException("發送過於頻繁,請稍後再試");
        }
        
        // 2. 生成 6 位數字驗證碼,存入 Redis(5 分鐘過期)
        String verifyCode = String.format("%06d", new Random().nextInt(1000000));
        redisTemplate.opsForValue().set("verify:code:" + mobile, verifyCode, 5, TimeUnit.MINUTES);
        
        // 3. 調用第三方發送
        Map tplValues = Map.of("code", verifyCode, "time", "5");
        JuheSmsClient.SmsResult result = JuheSmsClient.sendTemplateSms(mobile, VERIFY_TPL_ID, tplValues);
        
        if (!result.isSuccess()) {
            // 發送失敗,刪除驗證碼,拋出異常
            redisTemplate.delete("verify:code:" + mobile);
            throw new BusinessException("短信發送失敗: " + result.getMessage());
        }
    }
    
    /** 驗證碼校驗 */
    public boolean verifyCode(String mobile, String inputCode) {
        String cached = redisTemplate.opsForValue().get("verify:code:" + mobile);
        return StringUtils.isNotBlank(cached) && cached.equals(inputCode);
    }
}

生產環境必備增強

  1. 簽名驗證:部分平台要求請求帶簽名(MD5/SHA256),需按文檔實現
  2. 異步發送:使用 @Async 或消息隊列(RocketMQ/Kafka)解耦,提升響應速度
  3. 多通道降級:配置主備短信供應商,失敗自動切換
  4. 監控告警:發送成功率、延遲、餘額不足等指標接入 Prometheus + Grafana
  5. 模板管理:建立模板審核流程,變更需走發布流程

總結

封裝穩健的 JuheSmsClient,配合業務層限流、緩存、異步、降級機制,即可構建企業級高可用短信服務。核心原則:參數編碼正確、超時控制合理、異常信息完整、結果封裝統一。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved