四、实现 server URL
接下来以推送最简单的锁屏命令为例,演示如何实现从服务器推送完整的 MDM 消息给注册设备。
首先实现一个简单的Jsp 页面。页面中是一个 html 表单,在<select>标签中我们会列出所有注册设备的 UUID(当然列出设备名称会更好),当你选择一个UUID,点击submit 按钮,服务器会向这个设备推送锁屏命令。
servlet lock 来负责处理这个表单:
Stringudid=request.getParameter("select1");
TokenUpdatetu=TokenUpdate.load(udid);
StringpemPath=request.getSession().getServletContext().getRealPath("/")+"/mdm_push.p12";
// Utils.sendMdmPush(pemPath,tu);
Utils.pushLock(pemPath,tu);
它首先会通过 UUID 查找 TokenUpdate表以获取设备的token、pushMagic 和 unlock token。然后调用 Utils.pushLock方法发送锁屏命令。
pushLock 方法与正常的 APNS 发送么有什么不同,也是借用了 JavaAPNS 框架进行的:
public static int pushLock(String p12Path,TokenUpdate tu) {
int pushState = 0 ;
try {
ApnsService service =
APNS.newService()
.withCert(p12Path,MDMPASS)
.withProductionDestination()
.build();
String mdmPayload = APNS.newPayload().customField("mdm",tu.PushMagic).build();
System.out.println(mdmPayload);
String deviceToken=tu.Token;
deviceToken=parseToken(deviceToken);
System.out.println("device token="+deviceToken);
service.push(deviceToken, mdmPayload);
pushState = 1;
System.out.println("推送锁屏信息已发送!");
Command cmd=new Command();
cmd.command="DeviceLock";
cmd.deviceid=tu.UDID;
cmd.status=0;
cmd.enqueue();
} catch (Exception e) {
System.out.println("出错了:"+e.getMessage());
pushState = 0;
}
return pushState;
}
值得注意的是,它没有使用 PayloadBuilder 来构建默认的消息 payload,而是直接 new 了一个 payload,然后在其中添加了自定义字段"mdm",值为push magic 。
java apns 在推送时需要知道设备的 device token,这个 device token 稍有点奇怪,它不是二进制形式(即 byte 数组),而必须是这些二进制转换成16进制后的字符串形式(string)。parseToken方法就是用来干这个的。它先将数据库中的 token(Base64 encode 后的字符串)反编码(Base64 decode),得到原始 token 的byte 数组,然后逐字节转换为16进制的字符串形式。
服务器并不能立即收到设备的响应,因为这个 APNS 消息是发给苹果推送服务器的。所以在这个方法中,将这条命令同时记录在 commandqueue 表中,方便等下(终端设备)来查找已经发过的指令。commandqueue表结构类似如下:
CREATE TABLE `commandqueue` (
`id` int(11) NOT NULLAUTO_INCREMENT,
`command` varchar(45) DEFAULTNULL,
`deviceid` varchar(45)DEFAULT NULL,
`status` int(11) DEFAULTNULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1;
如果推送证书和注册设备所安装的描述文档经过了苹果的校验,接下来的苹果会通知设备来访问 MDM 服务器,即我们在配置描述文档时指定的那个服务器 URL:
因此接下来要实现servlet server。server类主要处理两类消息:Status:Idle消息和 Acknowledge 消息:
protected void doPut(HttpServletRequest request, HttpServletResponseresponse) throws ServletException, IOException {
StringplistStr=Utils.is2string(request.getInputStream());
System.out.println("*********ReceivedMessage:***********\n"+plistStr);
try{
Map<String,Object>plist=Plist.fromXml(plistStr);
PrintWriter out = response.getWriter();
if(plist!=null){
StringmsgType=MessageType.typeOfMessage(plist);
if(msgType.equals("Status:Idle")){
Stringdeviceid=plist.get("UDID").toString();
Command cmd = Command.dequeue(deviceid);
if(cmd.command!=null &&"DeviceLock".equals(cmd.command)){
String commandString =CommandPlist.deviceLock(cmd);
Command.updateStatus(cmd.id, 1);
System.out.println("——————-Commandwill send.—————");
response.setHeader("content-type","application/xml;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
String filename = "DeviceLock.plist";
response.setHeader("Content-Disposition","attachment; filename=" + filename);
System.out.printf("%s\n",commandString);
out.write(commandString);
out.flush();
out.close();
}
}elseif(msgType.equals("Acknowledged")){
String cmdID = (String)plist.get("CommandUUID");
Integer integer=Integer.parseInt(cmdID);
Command.updateStatus(integer, 2);
System.out.println("%s\n——————-Commandexecution completed.—————\n");
out.println();
out.close();
}
}
}catch(Exceptione){
e.printStackTrace();
}
}
当苹果的 APNS 服务器通知设备访问 MDM 服务器时,设备首先会发送一个 Status:Idle 消息(如果设备空闲),表示 MDM 服务器现在可以把指令取给它执行。
在 server.java 里,首先处理的就是这个消息。它首先从 commandqueue 表中取出一个“曾经”发送给这个设备的有效命令(我们通过status 字段识别,2 是已经执行完的命令,1 是还在执行的命令,0 是已经推送但未执行的命令),然后组装成 Plist 文件响应给它。
在组装 Plist 文件时,在 commandUUID 中填入该命令在 commandqueue 表中的主键(id 字段),这样当设备执行完命令,我们可以依据id 值回写执行状态。
设备执行完命令,会再次向 server 发送 Acknowledged 消息。对于这个消息,server.java 需要根据 commandUUID 回写命令执行状态,然后简单回复一个空响应(即http 200),并关闭 http 连接。