供稿人:刘少林
现在的越来越多的项目都有发送短信的需求,FWK 中内置有短信发送的功能模块,开发中需要根据短信提供商的接口或短信网关接口,实现相应的短信服务即可。
首先,在应用中新建一个新的 ShortMessageService 服务类,并继承 bropen.framework.plugins.message.ShortMessageService,模板如下:
import bropen.framework.plugins.message.ShortMessage
import bropen.framework.plugins.message.MessageStatus
/**
* XXX 短信服务
*/
class ShortMessageService extends bropen.framework.plugins.message.ShortMessageService {
static aliasOverridingOrder = bropen.framework.plugins.message.ShortMessageService.aliasOverridingOrder + 1
static bootStrapInit() {
// 一些初始化配置可以放到这里
}
@java.lang.Override
protected MessageStatus send(ShortMessage sms) {
// 调用短信接口,发送短信,并且返回状态
...
return MessageStatus.SENT
}
@java.lang.Override
protected ShortMessage bind(Map msg) {
// 将一个 Map 转换为 ShortMessage 对象
}
}
上面的模板中,bootStrapInit 方法用于初始化一些系统参数,如系统参数:
- bropen.framework.plugins.message.sms.enabled:是否启用短信,默认为否
- bropen.framework.plugins.message.sms.http.url:第三方短信服务的地址
- bropen.framework.plugins.message.sms.attempt.max:短信发送失败的最大重试次数,默认为 5
- bropen.framework.plugins.message.sms.attempt.interval:短信发送失败的重试间隔,默认为 5 分钟
此外,还可以根据第三方短信服务的接口,自定义其他系统参数,如下例所示:
static bootStrapInit() {
if ( !settingService.exists("bropen.framework.plugins.message.sms.http.sn") ) {
// 短信服务地址
settingService.createOrUpdate("bropen.framework.plugins.message.sms.http.url",
"http://www.jianzhou.sh.cn/JianzhouSMSWSServer/services/BusinessService", "string").save()
// 短信服务帐号
settingService.createOrUpdate("bropen.framework.plugins.message.sms.http.sn",
"sdk_foobar", "string").save()
// 短信服务密码
settingService.createOrUpdate("bropen.framework.plugins.message.sms.http.pwd",
"1234567890", "passwd").save()
// 短信服务开关
settingService.createOrUpdate("bropen.framework.plugins.message.sms.enabled",
"false", "boolean").save()
// 短信服务帐号
settingService.createOrUpdate("bropen.framework.plugins.message.sms.http.sig",
"【Foobar】", "string").save()
}
}
接口方法 send 是最重要的部分,该方法中,需要调用实际的第三方接口,将短信发送出去,如下例所示:
/**
* 接口方法:通过短信网关发送短信,并返回状态。如果短信发送被禁用,则应直接返回 null。
*/
@java.lang.Override
protected MessageStatus send(ShortMessage sms) {
if ( !isEnabled() ) return null // 如果没有启用短信服务,则直接返回null
String url = settingService.get("bropen.framework.plugins.message.sms.http.url") // 服务地址
String account = settingService.get("bropen.framework.plugins.message.sms.http.sn") // 企业账户
String pwd = settingService.get("bropen.framework.plugins.message.sms.http.pwd") // 密码
String sig = settingService.get("bropen.framework.plugins.message.sms.http.sig") ?: "" // 短信后缀
// 收件人
List<String> phones = []
for ( String to in sms.to ) {
if ( to.indexOf("<") )
phones += to.replaceAll("^[^<]*<", "").replaceAll(">.*\$", "")
}
// 拼消息实体:接收人、短信内容
String phone = phones.join(";")
String message = sms.text + (sms.signature ? ("\n"+ sms.signature) : "")
message += sig
// 发送 (开发和测试环境不发短信)
String resp = null
if ( grails.util.Environment.current == grails.util.Environment.PRODUCTION ) {
Client c = new Client(url,account, pwd)
resp = c.sendBatchMessage(phone, message)
log.debug("sms.id=" + sms.id + ", resp=" + resp)
resp = resp.replaceAll(/,.+/, "")
} else {
resp = '999999999'
log.debug("sms.id=" + sms.id + ", resp=" + resp + ", phone=" + phone + ", message=" + message)
}
// 检查返回值(服务反馈的状态码),并设置 sms 对象的 statusCode、statusMessage,以便审计和查找发送失败原因
sms.statusCode = resp
switch ( sms.statusCode ) {
case "-1" : sms.statusMessage="余额不足"; break
case "-2" : sms.statusMessage="帐号或密码错误"; break
...
case "-19" : sms.statusMessage="必须为POST提交"; break
case "-20" : sms.statusMessage="超速提交(一般为每秒一次提交)"; break
default: sms.statusMessage = sms.statusCode
}
// 返回状态
return sms.statusCode.toLong() > 0 ? MessageStatus.SENT : MessageStatus.ERROR
}
/**
* 从第三方接口的 demo 代码中拷贝出来的短信客户端
*/
public static class Client {
....
}
上面的例子中,注意:
- 第一行判断是否启用了短信,否则直接返回 null
- 调用短信客户端类发送完成后,可以设置 sms 对象的 statusCode、statusMessage 属性,以便审计和查找发送失败原因
此外,上面的例子中有一个内部类 Client,一般可以从短信服务商提供的 java 例子中找到,简单修改即可,如:
public static class Client {
// webservice服务器定义
private String serviceURL = null; // 入口地址:http://www.jianzhou.sh.cn/JianzhouSMSWSServer/http/sendBatchMessage
private String account = null; // 账户
private String pwd = null; // 密码
/*
* 构造函数
*/
public Client(String url, String account, String pwd) throws UnsupportedEncodingException {
this.serviceURL = url;
this.account = account;
this.pwd = pwd;
}
/*
* 方法名称:sendBatchMessage
* 功 能:发送个性短信
* 参 数:mobile,content(手机号,内容)
* 返 回 值:唯一标识,如果不填写rrid将返回系统生成的
*/
public String sendBatchMessage(String mobile, String content) {
....
}
}
接口方法 bind 用来将 Map 对象转换成一个 ShortMessage 对象,默认情况下,Map 中包含下列数据(其中 from、to、text 不能为空):
- from:发送人名称/员工或用户对象
- to:逗号或分号分隔的手机号、或手机号列表、员工列表
- text:消息正文
- signature:签名
- sentDateSche:计划发送时间,空则立即发送
如有其他需求,可以覆盖 bind 方法实现自己的转换逻辑。
最后,接口开发完成后,可以调用 ShortMessageService 的 sendMessage 方法发送短信,如:
ctx.shortMessageService.flush = true // 如果在 console 中执行,必须加上这句;否则不需要
// 发送一条新的短信
ctx.shortMessageService.sendMessage( [from:"abc", to:"13312345678", text:"test text"] )
// 重试一条老的短信
def sms = bropen.framework.plugins.message.ShortMessage.get(xxxx)
ctx.shortMessageService.sendMessage( sms )
如上例所示,sendMessage 方法有两个版本,一个版本的参数是 Map、一个版本的参数是 ShortMessage 对象,通常情况下我们使用 Map 来传递参数(新建一条短信并发送),而 Map 中的数据规范参见上文中 bind 方法的默认情况。
此外,启用短信配置、并实现短信接口后,用管理员登录到后台,可以查看短信发送情况,并进行重发、或直接发送短信。

