在企業級應用中,短信通知功能常透過短信貓(GSM Modem)搭配 SMSLib 函式庫實現。本文提供一個完整、可直接參考的 Java 類別範例,涵蓋閘道初始化、單例模式管理、發送中文簡訊、送達狀態回呼、接收簡訊處理與來電通知等核心功能。
建構子中從 sms.properties 讀取 COM 埠名稱與鮑率,預設為 COM10、9600。若配置檔存在則覆蓋預設值,隨後建立 SerialModemGateway 實例,指定廠牌與型號(範例為 Wavecom 17254)。
getInstant() 方法採用雙重檢查鎖定(此處簡化版),確保整個應用僅有一個 SmsModem 實例,避免重複開啟序列埠造成資源衝突。
Service,啟用序列埠輪詢、設定 SIM PIN、註冊三種回呼通知(發送、接收、來電)。msg.setEncoding(MessageEncodings.ENCUCS2),確保中文內容正確編碼為 UCS-2,避免亂碼。readMessage() 示範如何一次性讀取所有類別訊息(MessageClasses.ALL),並格式化輸出「序號、發件人、內容、時間」,同時記錄 Modem 狀態資訊。
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import org.apache.log4j.Logger;
import org.smslib.ICallNotification;
import org.smslib.IInboundMessageNotification;
import org.smslib.IOutboundMessageNotification;
import org.smslib.InboundMessage;
import org.smslib.InboundMessage.MessageClasses;
import org.smslib.Library;
import org.smslib.Message.MessageEncodings;
import org.smslib.Message.MessageTypes;
import org.smslib.OutboundMessage;
import org.smslib.Service;
import org.smslib.modem.SerialModemGateway;
public class SmsModem {
private SerialModemGateway gateway = null;
private ResourceBundle rb = null;
private static SmsModem smsModem = null;
private final OutboundNotification outboundNotification = new OutboundNotification();
private static final Logger LOG = Logger.getLogger(SmsModem.class);
private Service srv;
private final InboundNotification inboundNotification = new InboundNotification();
private final CallNotification callNotification = new CallNotification();
public SmsModem() {
try {
rb = ResourceBundle.getBundle("sms");
String portName = "COM10";
int baudRate = 9600;
LOG.info("default portName:" + portName);
LOG.info("default baudRate:" + baudRate);
if (rb != null) {
LOG.info("RB is not null");
if (rb.getString("smsport") != null && !"".equals(rb.getString("smsport"))) {
portName = rb.getString("smsport");
LOG.info("portName:" + portName);
}
if (rb.getString("smsbaud") != null && !"".equals(rb.getString("smsbaud"))) {
baudRate = Integer.valueOf(rb.getString("smsbaud"));
LOG.info("baudRate:" + baudRate);
}
}
gateway = new SerialModemGateway("modem." + portName, portName, baudRate, "wavecom", "17254");
} catch (Exception e) {
LOG.error("閘道初始化失敗:" + e.getMessage());
e.printStackTrace();
}
}
public static synchronized SmsModem getInstant() {
if (smsModem == null) {
smsModem = new SmsModem();
}
return smsModem;
}
public SerialModemGateway getGateway() {
return gateway;
}
public void sendMessage(String phone, String content) throws Exception {
doIt(phone, content);
}
public void doIt(String phone, String content) throws Exception {
OutboundMessage msg;
LOG.info("發送範例:從序列 GSM Modem 發送簡訊。");
LOG.info(Library.getLibraryDescription());
LOG.info("版本: " + Library.getLibraryVersion());
if (srv == null) {
srv = new Service();
srv.S.SERIAL_POLLING = true;
gateway.setInbound(true);
gateway.setOutbound(true);
gateway.setSimPin("0000");
gateway.setOutboundNotification(outboundNotification);
gateway.setInboundNotification(inboundNotification);
gateway.setCallNotification(callNotification);
srv.addGateway(gateway);
srv.startService();
}
if (gateway != null) {
LOG.info("Modem 資訊:");
LOG.info(" 製造商: " + gateway.getManufacturer());
LOG.info(" 型號: " + gateway.getModel());
LOG.info(" 序號: " + gateway.getSerialNo());
LOG.info(" SIM IMSI: " + gateway.getImsi());
LOG.info(" 訊號強度: " + gateway.getSignalLevel() + "%");
LOG.info(" 電池電量: " + gateway.getBatteryLevel() + "%");
}
msg = new OutboundMessage(phone, content);
msg.setEncoding(MessageEncodings.ENCUCS2); // 中文必須設定 UCS-2 編碼
srv.sendMessage(msg);
}
public class OutboundNotification implements IOutboundMessageNotification {
public void process(String gatewayId, OutboundMessage msg) {
LOG.info("發送回呼來自閘道: " + gatewayId);
LOG.info(msg);
}
}
public String readMessage() {
StringBuffer sb = new StringBuffer("");
List msgList;
try {
System.out.println("讀取範例:從序列 GSM Modem 讀取簡訊。");
System.out.println(Library.getLibraryDescription());
System.out.println("版本: " + Library.getLibraryVersion());
if (srv == null) {
srv = new Service();
srv.S.SERIAL_POLLING = true;
gateway.setInbound(true);
gateway.setOutbound(true);
gateway.setSimPin("0000");
gateway.setOutboundNotification(outboundNotification);
gateway.setInboundNotification(inboundNotification);
gateway.setCallNotification(callNotification);
srv.addGateway(gateway);
srv.startService();
}
LOG.info("讀取 Modem 資訊:");
LOG.info(" 製造商: " + gateway.getManufacturer());
LOG.info(" 型號: " + gateway.getModel());
LOG.info(" 序號: " + gateway.getSerialNo());
LOG.info(" SIM IMSI: " + gateway.getImsi());
LOG.info(" 訊號強度: " + gateway.getSignalLevel() + "%");
LOG.info(" 電池電量: " + gateway.getBatteryLevel() + "%");
msgList = new ArrayList();
this.srv.readMessages(msgList, MessageClasses.ALL);
int num = 1;
for (InboundMessage msg : msgList) {
sb.append("第" + num + "條;發件人:" + msg.getOriginator() + "; 內容:" + msg.getText() + "\n");
LOG.info("第" + num + "條;發件人:" + msg.getOriginator() + "; 內容:" + msg.getText() + "\n");
num++;
LOG.info(msg);
}
} catch (Exception e) {
sb.append(e.getMessage());
e.printStackTrace();
} finally {
// this.srv.stopService(); // 若需持續監聽請勿關閉
}
return sb.toString();
}
public class InboundNotification implements IInboundMessageNotification {
public void process(String gatewayId, MessageTypes msgType, InboundMessage msg) {
if (msgType == MessageTypes.INBOUND)
System.out.println(">> 新收到簡訊來自閘道: " + gatewayId);
else if (msgType == MessageTypes.STATUSREPORT)
System.out.println(">> 新收到狀態報告來自閘道: " + gatewayId);
System.out.println(msg);
try {
// srv.deleteMessage(msg); // 如需自動刪除請取消註解
} catch (Exception e) {
System.out.println("刪除訊息失敗...");
e.printStackTrace();
}
}
}
public class CallNotification implements ICallNotification {
public void process(String gatewayId, String callerId) {
System.out.println(">> 新來電偵測來自閘道: " + gatewayId + " : " + callerId);
}
}
}
sms.properties,內容範例:smsport=COM3smsbaud=115200org.smslib:smslib:3.5.4 相依套件。Service 啟動與關閉納入 Spring 生命週期管理(@PostConstruct / @PreDestroy)。CommPortIdentifier 找不到埠:確認 javax.comm 或 rxtx 原生庫與 JVM 位元數一致(32/64-bit)。OutboundMessage 上呼叫 setEncoding(ENCUCS2)。