四、实现 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 连接。
创建一个简单的 MDM 服务器(2),布布扣,bubuko.com