服务开发框架
有了前面一些基本概念、基础数据结构作铺垫,咱们就可以开始真正熟悉ablecloud的服务开发框架了。实际上大多数开发者在使用ablecloud框架开发服务时,仅需简单的使用那些基础数据,将精力集中在实际的业务逻辑,快速的完成服务程序的开发/测试/发布。
ACService:自定义后端服务
ablecloud定义了抽象基类ACService,开发者只需要继承该类,并实现各个handler即可。定义如下:
public abstract class ACService {
// ac是非常重要的ablecloud云框架服务,ablecloud提供了默认实现ACCloud,
// 开发者可以调用ac的相关接口直接调用ablecloud提供的云服务,后文会有详述
protected AC ac;
// 以下信息可用于服务内部追踪问题等用,比如打印到日志中
protected long developerId; // 开发者id
protected String majorDomain; // 服务的主域名
protected String subDomain; // 服务的子域名
protected int majorVersion; // 服务的主版本号
protected int minorVersion; // 服务的副版本号
protected int patchVersion; // 服务的修订版本号
/**
* 开发者可根据自身需要,重载该方法,在该方法里做一些初始化工作,框架在启动服务时会调用该函数。
* 比如,该服务要处理和设备之间的交互消息,需要自定义设备消息的序列化/反序列化器
* ACDeviceMsgMarshaller,在init函数内将marshaller设置到ac中。
*
* @throws Exception
*/
public void init() throws Exception {}
/**
* 处理APP-->Service,Service-->Service之间的交互消息
* @param req 请求消息体
* @param resp 响应消息体
* @throws Exception
*/
public abstract void handleMsg(ACMsg req, ACMsg resp) throws Exception;
/**
* 处理Device-->Service之间的交互消息
* 如果服务不处理和设备之间的交互消息,则无须重载该方法。
*
* 当前,处理设备汇报的消息不做响应。
*
* @param context 设备的上下文,其中uid字段为系统填充
* @param deviceId 设备的逻辑id
* @param req 请求消息体
* @throws Exception
*/
public abstract void handleDeviceMsg(ACContext context, long deviceId, ACDeviceMsg req) throws Exception;
/**
* 处理JINDDONG-->Service之间的交互消息,收到Stream点数组,进行设备控制
*
* @param context 设备的上下文,其中uid字段为系统填充
* @param physicalDeviceId 设备的物理id
* @param req 请求消息体(Stream数组)
* @param resp 响应消息体
* @throws Exception
*/
public void handleJDSetStatusMsg(ACContext context, String physicalDeviceId, List<ACJDMsg> req, ACMsg resp) throws Exception {}
/**
* 处理JINDDONG-->Service之间的交互消息,获取设备上所有Stream点
*
* @param context 设备的上下文,其中uid字段为系统填充
* @param physicalDeviceId 设备的物理id
* @param resp 响应消息体(Stream数组)
* @throws Exception
*/
public void handleJDGetStatusMsg(ACContext context, String physicalDeviceId, List<ACJDMsg> resp) throws Exception {}
/**
* 内部调用接口,开发者不用关注且不能修改。
* 设置服务相关的信息,并将全局AC框架传给服务
* 服务内部可以使用AC框架提供的各种功能,如
* 帐号管理、设备管理、存储服务等。
* @param ac
* @param config
*/
public final void setEnv(AC ac, ACConfiguration config) {}
/**
* 内部调用接口,开发者不用关注且不能修改。
* @return
*/
public final AC getAc() {}
}
在上述抽象类中,对开发者来说,总共有三个公共接口,其中init提供了默认实现。如果开发者实现的某一服务不需要和设备直接交互,则直接重载handleDeviceMsg为空实现即可。通常情况下,init也无需重载。因此,开发者可以将精力集中在handleMsg接口的实现中,该接口处理客户端请求,并作出响应。下文会对该抽象类进行详细介绍。
注:通常情况下,开发者只需要重点实现handleMsg即可。
ACCronJob:云端定时任务
ablecloud定义了云端定时任务的抽象基类ACCronJob。开发者需要继承该类,并实现其定义的抽象方法ACCronJob::run,即能完成定时任务的开发。ACCronJob的定义如下:
public abstract class ACCronJob {
// ac是非常重要的ablecloud云框架服务,ablecloud提供了默认实现ACCloud,
// 开发者可以调用ac的相关接口直接调用ablecloud提供的云服务,后文会有详述
protected AC ac;
// 以下信息可用于任务内部追踪问题等用,比如打印到日志中等。
protected long developerId; // 开发者id
protected String majorDomain; // 服务的主域名
protected String subDomain; // 服务的子域名
protected int majorVersion; // 服务的主版本号
protected int minorVersion; // 服务的副版本号
protected int patchVersion; // 服务的修订版本号
/**
* 内部调用接口,开发者不用关注且不能修改。
* 设置服务相关的信息,并将全局AC框架传给服务
* 服务内部可以使用AC框架提供的各种功能,如
* 帐号管理、设备管理、存储服务等
* @param ac
* @param config
*/
public final void setEnv(AC ac, ACConfiguration config) {
this.ac = ac;
this.developerId = config.getDeveloperId();
this.majorDomain = config.getServiceMajorDomain();
this.subDomain = config.getServiceSubDomain();
this.majorVersion = config.getServiceMajorVersion();
this.minorVersion = config.getServiceMinorVersion();
this.patchVersion = config.getServicePatchVersion();
}
/**
* 内部调用接口,开发者不用关注且不能修改。
* @return AC对象。
*/
public final AC getAc() {
return ac;
}
/**
* 定时任务的执行函数。
* @return 返回任务的结束后,进程退出时所使用的状态码。
* @throws Exception
*/
public abstract int run() throws Exception;
}
上述抽象类共定义了三个公共方法:ACCronJob::setEnv,ACCronJob::getAC,以及ACCronJob::run。其中,ACCronJob::run是定时任务的执行函数,要求开发者提供具体实现。
AC
在介绍ACService和ACCronJob的时候提到过重要的成员变量ac,ac实际上是ablecloud对抽象服务框架AC的具体实现,其实现过程对开发者透明。通过AC,开发者可以根据需要获取一系列内嵌服务的功能接口。AC的定义如下:
public abstract class AC {
protected ACConfiguration config;
protected ACDeviceMsgMarshaller deviceMsgMarshaller;
/**
* 初始化框架
* @param config 配置信息
*/
public void init(ACConfiguration config) {}
/**
* 构建一个开发者上下文
* @return
*/
public ACContext newContext() {}
/**
* 构建一个用户上下文
* @param userId
* @return
*/
public ACContext newContext(long userId) {}
/**
* 则用于创建数据分类/清空数据等操作。
* 用于测试之用。
*
* @return
*/
public abstract ACStoreForTest storeForTest(ACContext context);
/**
* 用于对数据分类进行具体的操作,如create/find/delete/update/scan等
*
* @param className 要操作的分类名
* @param context 要进行操作的开发者context
* @return
*/
public abstract ACStore store(String className, ACContext context);
/**
* 直接往设备发送命令/消息
* @param subDomain 子域名,比如thermostat
* @param deviceId 设备逻辑id
* @param reqMsg 具体的消息内容
* @param context 如果通过app端发送消息到服务,服务在中继给设备,
* 则服务在发送给设备的时候需要带上app端的用户context。
* 如果是服务自主发送控制命令给设备,则需要传入开发者的context。
* @return 设备返回的消息
* @throws Exception
*/
public abstract ACDeviceMsg sendToDevice(String subDomain, long deviceId,
ACDeviceMsg reqMsg, ACContext context) throws Exception;
/**
* 往某一服务发送命令/消息
* @param subDomain 子域名,比如thermostat
* @param service Name 服务名
* @param version 服务版本
* @param req 具体的消息内容
* @return 服务端相应的消息
* @throws Exception
*/
public abstract ACMsg sendToService(String subDomain, String serviceName, int version, ACMsg req) throws Exception;
/**
* 往JD service发送命令/消息,上报设备上的所有Stream点到JINGDONG Service
*
* @param context 设备的上下文,其中uid字段为系统填充
* @param physicalDeviceId 设备的物理id
* @param req 请求消息体(Stream数组)
* @return 服务端相应的消息
* @throws Exception
*/
public abstract ACMsg sendToJDService(ACContext context, String physicalDeviceId, List<ACJDMsg> req) throws Exception;
/**
* 获取设备管理器。开发者在实现自定义服务时,
* 可以调用ACDeviceMgr提供的各个通用接口
*
* @param context 用户的context
* @return
*/
public abstract ACDeviceMgr deviceMgr(ACContext context);
/**
* 获取用于单元测试的设备管理器,可以创建分组/绑定设备等
*
* @param context 用户的context
* @return
*/
public abstract ACDeviceMgrForTest deviceMgrForTest(ACContext context);
/**
* 获取帐号管理器。开发者组实现自定义服务时,
* 可以调用ACAccountMgr提供的各个通用接口
*
* @param context 开发者的context
* @return
*/
public abstract ACAccountMgr accountMgr(ACContext context);
/**
* 获取用于单元测试的帐号管理器,可以注册用户等
*
* @param context 开发者的context
* @return
*/
public abstract ACAccountMgrForTest accountMgrForTest(ACContext context);
/**
* 获取通知管理器,可以给用户发送通知消息
*
* @param context 开发者的context
* @return
*/
public abstract ACNotificationMgr notificationMgr(ACContext context);
/**
* 为便于测试,开发者可实现一个服务的桩
* 在框架中添加一个服务桩
*
* @param name 服务名
* @param stub 服务桩的实现,实际上也是一个ACService
*/
public abstract void addServiceStub(String name, ACService stub);
/**
* 为便于测试,开发者可实现一个设备的桩
*
* @param subDomain 设备所属子域
* @param stub 设备桩
*/
public abstract void addDeviceStub(String subDomain, ACDeviceStub stub);
/**
* 如果服务要处理和设备之间的交互消息,需要实现设备消息序列化/反序列化器
* 该接口将序列化/反序列化器设置给ac框架
*
* @param marshaller 设备消息序列化/反序列化器
*/
public void setDeviceMsgMarshaller(ACDeviceMsgMarshaller marshaller) {}
/**
* 获取设备消息序列化/反序列化器
* @return
*/
public ACDeviceMsgMarshaller getDeviceMsgMarshaller() {}
/**
* 获取用于单元测试的服务框架ac
* @param config 单元测试环境构造的config
* @return
* @throws Exception
*/
public static final AC getTestAc(ACConfiguration config) throws Exception {}
}
附录
签名算法
public class ACSigner {
private static final Logger logger = LoggerFactory.getLogger(ACSigner.class);
private static final String ENCODING = "UTF-8";
private static final String HASH = "HmacSHA256";
public static String genSignString(long developerId, String majorDomain,
String subDomain, String method,
long timestamp, long timeout, String nonce) {
String stringToSign = String.valueOf(timeout) +
String.valueOf(timestamp) +
nonce +
String.valueOf(developerId) +
method +
majorDomain +
subDomain;
return stringToSign;
}
public static String genSignature(String sk, String stringToSign) {
String signature = "";
try {
String encodedSign = URLEncoder.encode(stringToSign, ENCODING);
try {
Mac mac = Mac.getInstance(HASH);
mac.init(new SecretKeySpec(sk.getBytes(ENCODING), HASH));
byte[] hashData = mac.doFinal(encodedSign.getBytes(ENCODING));
StringBuilder sb = new StringBuilder(hashData.length * 2);
for (byte data : hashData) {
String hex = Integer.toHexString(data & 0xFF);
if (hex.length() == 1) {
// append leading zero
sb.append("0");
}
sb.append(hex);
}
signature = sb.toString().toLowerCase();
} catch (Exception e) {
logger.warn("sha256 exception for[" + sk + "," + stringToSign + "]. e:", e);
}
} catch (UnsupportedEncodingException e) {
logger.warn("encode error, string[" + stringToSign + "] e:" + e);
}
return signature;
}
}