首页 > 代码库 > 创建一个简单的 MDM 服务器(2)

创建一个简单的 MDM 服务器(2)

四、实现 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 连接。