Demo
这里我们以一个完整的demo程序做示例,通过真实代码介绍如何基于ACService开发用户自己的服务程序。demo场景不一定完全符合常识,其目的是为了尽量简单而全面的演示服务框架提供的功能。
场景介绍
本示例场景有一智能灯,可以通过手机端向云端发消息远程控制灯的开/关,云端服务把这些APP发来的控制行为记录到ablecloud提供的云存储中。同时,智能灯也可能由用户通过机械开关起停,智能灯将这类开/关事件也主动汇报到服务端,我们写的服务程序将主动汇报开/关数据也存到云存储中。所有存储在云存储中的数据,可提供给APP查询,比如可用于统计用户的作息习惯等。
实现步骤
先简单分析下需求,然后梳理出实现步骤。
- 要开发服务程序,继承自
ACService
实现我们自己的服务框架必不可少,因此我们要实现服务的主体框架DemoService
; - 服务要接收来自APP对灯的远程控制命令,也会接收来自APP的数据查询请求,因此必须为
handleMsg
提供具体的实现handler; - 服务要向智能灯发送控制命令,因此我们需要和灯以及APP端定义具体的控制消息格式
LightMsg
; - 在步骤3定义好了消息格式后,我们还需要根据
ACDeviceMsgMarshaller
实现具体的消息序列化/反序列化器LightMsgMarshaller
; - 实现了
LightMsgMarshaller
后,重载ACService
的init
接口,将序列化器设置到ac
框架中; - 服务要接收智能灯汇报的开/关消息,因此必须为
handleDeviceMsg
提供具体的实现handler;
具体实现
DemoService
DemoService
为自定义服务主要逻辑处理类,通过handleMsg
处理APP端发来的消息,通过handleDeviceMsg
处理设备上报上来的消息。
当前为了简单,在handleMsg
中实现了两个具体的处理函数handleControlLight
和handleQueryData
。在handleDeviceMsg
中只实现了一个具体的处理函数handleLightReport
。生产环境真正的服务用户可以根据业务任意扩展handler。
package com.ablecloud.demo;
import com.ablecloud.common.ACServiceException;
import com.ablecloud.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DemoService extends ACService {
private static final Logger logger = LoggerFactory.getLogger(DemoService.class);
private static final String DATA_CLASS_NAME = "light-action-data";
private static final long FROM_APP = 0;
private static final long FROM_SWITCH = 1;
/**
* 重载init函数。
* 因为我们的服务要处理和设备交互的消息,因此在该函数中将序列化/反序列化器设置到ac框架中。
*
* @throws Exception
*/
public void init() throws Exception {
ac.setDeviceMsgMarshaller(new LightMsgMarshaller());
}
/**
* 处理来自APP或其它service发来消息的入口函数
*
* @param req 请求消息
* @param resp 响应消息
* @throws Exception
*/
public void handleMsg(ACMsg req, ACMsg resp) throws Exception {
String name = req.getName();
if (name != null) {
switch (name) {
case "controlLight":
handleControlLight(req, resp);
break;
case "queryData":
handleQueryData(req, resp);
break;
default:
logger.warn("got an invalid request, method[" + name + "] is not implemented.");
}
} else {
logger.warn("got an invalid request, method name is empty.");
}
}
/**
* 处理来自设备上报消息的入口函数
*
* @param context 上下文信息,在设备联动情况下,可能会跨子域访问
* @param deviceId 设备的逻辑id
* @param req 设备上报消息
* @throws Exception
*/
public void handleDeviceMsg(ACContext context, long deviceId, ACDeviceMsg req) throws Exception {
Integer msgCode = req.getCode();
switch (msgCode) {
case LightMsg.CODE:
handleLightReport(context, subDomain, deviceId, req);
break;
default:
logger.warn("got an unknown report, opcode[" + msgCode + "]");
}
}
//////////////////////////////////////
// 具体的私有handler
/**
* 处理来自APP端的智能灯控制命令,再将命令发往具体的设备
*
* 实际上,厂商在实现后端服务的时候,通常情况下自定义服务不用处理APP端发来的设备控制请求也
* 能实现远程控制。因为ablecloud在云端提供了设备管理服务,APP通过APP端的sendToDevice
* 接口可以将控制命令远程发往ablecloud的设备管理服务,设备管理服务再将控制命令发给设备。
*
* 本示例在开发者自定义的这个服务中实现对灯的控制,一方面是为了展示后端服务的灵活性,可以作
* 各种事情,包括对设备的控制,比如后端服务在多设备联动的时候,可能会主动往设备发控制命令。
* 另外一方面,为了将控制数据存入ablecloud提供的云存储服务中以供查询之用。
*
* @param req 请求消息
* @param resp 响应消息
* @throws Exception
*/
private void handleControlLight(ACMsg req, ACMsg resp) throws Exception {
Long lightId = req.get("deviceId");
String action = req.get("action");
byte deviceAction = LightMsg.OFF;
if (action.equalsIgnoreCase("on")) {
deviceAction = LightMsg.ON;
}
ACDeviceMsg deviceReqMsg = new ACDeviceMsg(LightMsg.CODE, new LightMsg(deviceAction));
ACDeviceMsg deviceRespMsg;
try {
// 通过ac框架的sendToDevice接口,向灯发送控制命令
deviceRespMsg = ac.sendToDevice(req.getContext().getSubDomainName(), lightId,
deviceReqMsg, req.getContext());
// do some check of deviceRespMsg.getCode()
resp.put("code", deviceRespMsg.getCode());
ACGroup group = ac.deviceMgr(req.getContext()).getGroup(lightId);
if (group == null) {
logger.error("get group info of device[" + lightId + "] failed");
throw new ACServiceException(1, "get group info error");
}
long groupId = group.getId();
long timestamp = System.currentTimeMillis();
// 通过ac框架,将APP对智能灯的控制数据存入云端存储
ac.store(DATA_CLASS_NAME, req.getContext()).create("groupId", groupId, "time", timestamp)
.put("action", (long)deviceAction)
.put("type", FROM_APP)
.execute();
resp.setAck();
logger.info("handle control light ok, action[" + action + "].");
} catch (ACServiceException e) {
resp.setErr(e.getErrorCode(), e.getErrorMsg());
logger.error("send to device[" + lightId + "] error:", e);
}
}
/**
* 处理智能灯汇报的消息,在该函数中,服务还将收到的汇报数据写入ablecloud提供的云端存储中。
*
* @param context 汇报设备的上下文数据,包括主域/子域等。
* @param deviceId 汇报设备的逻辑id
* @param req 汇报的消息
* @throws Exception
*/
private void handleLightReport(ACContext context, long deviceId, ACDeviceMsg req) throws Exception {
try {
ACGroup group = ac.deviceMgr(ac.newContext()).getGroup(deviceId);
if (group == null) {
logger.error("get group info of device[" + deviceId + "] failed");
throw new ACServiceException(1, "get group info error");
}
long groupId = group.getId();
LightMsg lightMsg = (LightMsg)req.getContent();
long onOff = lightMsg.getLedOnOff();
long timestamp = System.currentTimeMillis();
// 通过ac框架,将智能灯汇报的数据存入云端存储
ac.store(DATA_CLASS_NAME, context).create("groupId", groupId, "time", timestamp)
.put("action", onOff)
.put("type", FROM_SWITCH)
.execute();
} catch (ACServiceException e) {
logger.error("handle light report error:", e);
}
}
/**
* 处理APP端发来的数据查询,并将查询到的智能等开/关记录数据返回
*
* @param req 请求消息
* @param resp 响应消息
* @throws Exception
*/
private void handleQueryData(ACMsg req, ACMsg resp) throws Exception {
Long groupId = req.get("groupId");
Long startTime = req.get("startTime");
Long endTime = req.get("endTime");
try {
List<ACObject> zos = ac.store(DATA_CLASS_NAME, req.getContext())
.scan("groupId", groupId)
.start("time", startTime)
.end("time", endTime)
.execute();
resp.put("actionData", zos);
resp.setAck();
} catch (ACServiceException e) {
resp.setErr(e.getErrorCode(), e.getErrorMsg());
logger.error("handle query data error:", e);
}
}
}
LightMsg
LightMsg
是控制灯开/关的消息(命令),需要设备/APP/服务三方共同确定。如果服务需要和其它类型的智能设备交互,则再定义其它的message即可。
package com.ablecloud.demo;
class LightMsg {
public static final int CODE = 68;
public static final int REPORT_CODE = 203;
public static final byte ON = 1;
public static final byte OFF = 0;
private byte ledOnOff; // 1表示开灯,0表示关灯
private byte[] pad;
public LightMsg(byte ledOnOff) {
this.ledOnOff = ledOnOff;
pad = new byte[3];
}
public void setLedOnOff(byte ledOnOff) {
this.ledOnOff = ledOnOff;
}
public void setPad(byte[] pad) {
this.pad = pad;
}
byte getLedOnOff() {
return ledOnOff;
}
byte[] getPad() {
return pad;
}
}
LightMsgMarshaller
该序列化/反序列化器用于序列化智能灯控制消息,如果一个服务会和多类智能设备交互,则在你自定义的marshaller中可以对多中设备消息进行序列化/反序列化操作。
package com.ablecloud.demo;
import com.ablecloud.service.ACDeviceMsg;
import com.ablecloud.service.ACDeviceMsgMarshaller;
import java.nio.ByteBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LightMsgMarshaller implements ACDeviceMsgMarshaller {
private static final Logger logger = LoggerFactory.getLogger(LightMsgMarshaller.class);
/**
* 将具体的ACDeviceMsg序列化成字节数组,用于控制设备时通过网络传输给设备
*
* @param msg 设备消息
* @return 序列化后的字节数组
* @throws Exception
*/
public byte[] marshal(ACDeviceMsg msg) throws Exception {
int msgCode = msg.getCode();
ByteBuffer bb;
switch (msgCode) {
case LightMsg.CODE:
case LightMsg.REPORT_CODE:
LightMsg lightMsg = (LightMsg)msg.getContent();
bb = ByteBuffer.allocate(4);
bb.put(lightMsg.getLedOnOff());
bb.put(lightMsg.getPad());
return bb.array();
default:
logger.warn("got an unknown msgCode[" + msgCode + "]");
throw new IllegalArgumentException("got an unknown msgCode[" + msgCode + "]");
}
}
/**
* 将通过网络收到的字节数组数据,反序列化成具体的消息,以便从消息中提取各个字段。
*
* @param msgCode 消息码,ablcloud也称为操作码opCode
* @param payload 设备消息序列化后的字节数组
* @return 设备消息
* @throws Exception
*/
public ACDeviceMsg unmarshal(int msgCode, byte[] payload) throws Exception {
if (payload == null || payload.length == 0) {
logger.warn("input payload is empty.");
throw new IllegalArgumentException("empty payload");
}
ByteBuffer bb;
switch (msgCode) {
case LightMsg.CODE:
case LightMsg.REPORT_CODE:
bb = ByteBuffer.wrap(payload);
byte ledOnOff = bb.get();
byte[] pad = new byte[3];
bb.get(pad);
return new ACDeviceMsg(msgCode, new LightMsg(ledOnOff));
default:
logger.warn("got an unknown msgCode[" + msgCode + "]");
throw new IllegalArgumentException("got an unknown msgCode[" + msgCode + "]");
}
}
}
简简单单的百十来行代码,一个完整的服务程序便开发完了。在终端运行mvn package
即可编译成jar包。本章节重点介绍服务的开发,即便如此,也只是很简单的进行了说明。你可以开发更多好玩的逻辑,比如多设备联动,当某些设备上报的数据达到你设置的规则时,触发另外的设备做出响应。
对于如何进行测试咱们开发的服务,我们下一章节详细介绍。