用戶入職時,系統自動生成符合複雜度要求的隨機初始密碼(8-16 位,含大小寫字母、數字、特殊符號),通過 **雲通訊 (Yuntongxun) REST API** 發送模板短信至用戶手機。
public class PasswordGenerator {
private static final String LOWER = "abcdefghijklmnopqrstuvwxyz";
private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String DIGITS = "0123456789";
private static final String SPECIAL = "!@#$%";
private static final String ALL = LOWER + UPPER + DIGITS + SPECIAL;
private static final SecureRandom RANDOM = new SecureRandom(); // 密碼學安全隨機數
/**
* 生成 8-16 位密碼,強制包含:至少 2 小寫、2 大寫、2 數字、2 特殊符號
*/
public static String generateSecurePassword(int length) {
if (length < 8) length = 8;
if (length > 16) length = 16;
StringBuilder pwd = new StringBuilder(length);
// 1. 強制放入各類字符(保證複雜度)
appendRandom(pwd, LOWER, 2);
appendRandom(pwd, UPPER, 2);
appendRandom(pwd, DIGITS, 2);
appendRandom(pwd, SPECIAL, 2);
// 2. 剩餘長度從全字符集隨機填充
int remaining = length - pwd.length();
for (int i = 0; i < remaining; i++) {
pwd.append(ALL.charAt(RANDOM.nextInt(ALL.length())));
}
// 3. 打亂順序(避免固定模式)
char[] arr = pwd.toString().toCharArray();
for (int i = arr.length - 1; i > 0; i--) {
int j = RANDOM.nextInt(i + 1);
char tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
}
return new String(arr);
}
private static void appendRandom(StringBuilder sb, String pool, int count) {
for (int i = 0; i < count; i++) {
sb.append(pool.charAt(RANDOM.nextInt(pool.length())));
}
}
}
<dependency>
<groupId>com.cloopen</groupId>
<artifactId>rest-sdk</artifactId>
<version>1.0.0</version> <!-- 請以官方最新為準 -->
</dependency>
public class SmsSender {
// 配置常量(請替換為官方控制台獲取的真實值)
private static final String SERVER_IP = "app.cloopen.com";
private static final String SERVER_PORT = "8883";
private static final String ACCOUNT_SID = "your_account_sid";
private static final String AUTH_TOKEN = "your_auth_token";
private static final String APP_ID = "your_app_id";
private static final String TEMPLATE_ID = "1"; // 模板 ID,內容如:【公司】您的初始密碼為 {1},請妥善保管。
private final CCPRestSDK sdk = new CCPRestSDK();
public SmsSender() {
sdk.init(SERVER_IP, SERVER_PORT);
sdk.setAccount(ACCOUNT_SID, AUTH_TOKEN);
sdk.setAppId(APP_ID);
}
/**
* 發送密碼短信
* @param mobile 手機號(多號碼英文逗號分隔)
* @param password 初始密碼
* @return true 發送成功
*/
public boolean sendPasswordSms(String mobile, String password) {
HashMap result = sdk.sendTemplateSMS(
mobile,
TEMPLATE_ID,
new String[]{password} // 模板變量 {1}
);
if ("000000".equals(result.get("statusCode"))) {
// 成功:result.get("data") 包含 sid, date_created 等
return true;
} else {
// 失敗:記錄錯誤碼與信息
log.error("短信發送失敗: code={}, msg={}",
result.get("statusCode"), result.get("statusMsg"));
return false;
}
}
}
@Service
public class EntryService {
@Autowired private SmsSender smsSender;
public void processEntry(User user) {
// 1. 生成密碼
String rawPassword = PasswordGenerator.generateSecurePassword(12);
// 2. 加密存儲(BCrypt)
String hashed = BCrypt.hashpw(rawPassword, BCrypt.gensalt(12));
user.setPasswordHash(hashed);
userRepository.save(user);
// 3. 發送短信(異步非阻塞)
CompletableFuture.runAsync(() -> {
boolean ok = smsSender.sendPasswordSms(user.getMobile(), rawPassword);
if (!ok) {
// 重試機制、告警通知
alertService.notifySmsFailure(user.getId(), user.getMobile());
}
});
}
}
| 項目 | 說明 |
|---|---|
| 模板審核 | 短信模板需在雲通訊控制台預先審核通過,變量用 {1}、{2}... 佔位 |
| 頻率限制 | 同一號碼/簽名/模板有發送頻率限制(如 1 分鐘 1 條、1 小時 10 條),需業務層防刷 |
| 密碼安全 | 明文密碼僅在內存短暫存在,**日誌嚴禁打印密碼**,發送後立即清除變量引用 |
| 異步發送 | 短信發送耗時 100-500ms,必須異步處理,避免阻塞主線程 |
| 重試策略 | 指數退避 + 最大重試 3 次,失敗進入死信隊列人工處理 |
| 國際化 | 海外號碼需使用國際通道,模板內容需符合當地法規 |
本文展示了從**安全隨機密碼生成**到**雲通訊 REST API 發送模板短信**的完整鏈路。核心要點:使用 SecureRandom 保證熵源、強制字符類型分布、打亂順序;SDK 初始化復用、異步發送、錯誤碼處理、日誌脫敏。生產系統建議接入雲廠商原生 SDK 或自建統一通知平台,實現多渠道、可觀測、可治理的通知能力。