内嵌云端服务
顾名思义,内嵌云端服务,是指ablecloud抽象并实现的多种通用后端服务,避免开发者重复开发这些基础设施。开发者可直接使用这些服务,降低应用服务程序的开发代价,提高开发效率。各个云端服务的对象通过上节介绍的服务框架AC的相关接口获取。
账号相关接口
该服务用于管理和某一智能设备相关的用户,比如查看用户的基本信息/状态等。发现异常用户时,服务程序能及时做出相应操作。
获取方式
ACAccountMgr accountMgr = ac.accountMgr(ACContext context);
接口说明
当前提供接口较少,后续会进一步丰富。
public interface ACAccountMgr {
/**
* 根据用户的id,查找用户信息
* @param accountId
* @throws Exception
*/
public ACAccount getAccountInfo(long accountId) throws Exception;
}
绑定相关接口
该服务接口主要用于用户和设备绑定关系管理,可以获取设备的Owner等详细信息,云端给设备发送消息等,定制化自己开发的服务。
获取方式
ACBindMgr bindMgr = ac.bindMgr(ACContext context);
接口说明
public interface ACBindMgr {
/**
* 直接往设备发送命令/消息
*
* @param subDomain 子域名,比如thermostat
* @param deviceId 设备逻辑id
* @param reqMsg 具体的消息内容
* @return 设备返回的消息
* @throws Exception
*/
public abstract ACDeviceMsg sendToDevice(String subDomain, long deviceId, ACDeviceMsg reqMsg) throws Exception;
/**
* 查询设备在线状态
*
* @param deviceId 设备逻辑id
* @return 设备是否在线
* @throws Exception
*/
public boolean isDeviceOnline(long deviceId) throws Exception;
/**
* 根据设备物理Id查询逻辑Id
*
* @param physicalDeviceId 设备物理id
* @return deviceId 设备逻辑id
* @throws Exception
*/
public long getDeviceId(String physicalDeviceId) throws Exception;
/**
* 根据逻辑Id查询设备的详细信息,比如物理ID,设备的名称,owner等
*
* @param deviceId 设备逻辑id
* @return ACUserDevice 设备对象信息
* @throws Exception
*/
public ACUserDevice getUserDevice(long deviceId) throws Exception;
/**
* 列举某一组内所有设备
*
* @return 所有设备信息
* @throws Exception
*/
public List<ACUserDevice> listDevices() throws Exception;
/**
* 列举某一设备的所有用户
*
* @return 所有设备信息
* @throws Exception
*/
public List<ACDeviceUser> listUsers(long deviceId) throws Exception;
}
数据结构说明
被绑定的设备的基础信息
public class ACUserDevice {
private long id; // 设备的逻辑ID
private String physicalId; // 设备的物理ID
private long owner; // 设备的管理员ID
private String name; // 设备的名称
private long subDomainId; // 设备所述产品子域ID
private long gatewayDeviceId; // 如果是子设备,其网关设备的逻辑ID
private long rootId; // 分组设备管理模型
public ACUserDevice(long id, long owner, String name, String physicalId, long subDomainId, long gatewayDeviceId, long rootId) {
this.id = id;
this.physicalId = physicalId;
this.owner = owner;
this.name = name;
this.subDomainId = subDomainId;
this.gatewayDeviceId = gatewayDeviceId;
this.rootId = rootId;
}
public long getId() {
return id;
}
public String getPhysicalId() {
return physicalId;
}
public long getSubDomainId() {
return subDomainId;
}
public long getOwner() {
return owner;
}
public String getName() {
return name;
}
public long getGatewayDeviceId() {
return gatewayDeviceId;
}
public long getRootId() {
return rootId;
}
}
绑定设备的用户的基础信息
public class ACDeviceUser {
public static final long NORMAL = 0;
public static final long OWNER = 1;
private long id; // 用户唯一标识ID
private long deviceId; // 设备唯一标识,逻辑ID
private long userType; // 设备绑定的用户类型:0普通用户,1管理员
private String phone; // 用户的手机号码
private String email; // 用户的Email
public ACDeviceUser(long id, long deviceId, long userType, String phone, String email) {
this.id = id;
this.deviceId = deviceId;
this.userType = userType;
this.phone = phone;
this.email = email;
}
public long getId() {
return id;
}
public long getDeviceId() {
return deviceId;
}
public long getUserType() {
return userType;
}
public String getPhone() {
return phone;
}
public String getEmail() {
return email;
}
}
存储相关接口
该服务为开发者提供了一个通用的key-value存储系统服务。开发者可用此服务存储自定义数据。
获取方式
ACStore store = ac.store(String className, ACContext contexte);
存储模型
ablecloud目前提供基于sql的存储服务,开发者需要预先设定数据集的结构,同时可以选择对数据进行分区或不分区。因此如何定位数据所在分区,需要提供分区key,ablecloud称其为entity group key。当我们要查找一条存储在ablecloud中的数据时,需要提供其key值,通过key值定位到具体的数据,这种用于定位具体数据的key,ablecloud称其为primary key。
注:entity group key必须是primary key的前缀,可以相同。
存储模型示例如下:
从上图我们可以看到,定义的一个存储模型如下:
- entiry group keys:deviceId,类型为字符串
String
。 - primary keys:deviceId,类型为字符串;timestamp,类型为整型
Long
。 - attributes:mode,类型为字符串;speed,类型为整型
Long
。
注:目前所有的整型,都统一支持Long,浮点型统一为Double,字符串型可以设定字符串长度
接口说明
数据查询过滤器ACFilter: 独立于数据集之外,同一个过滤器可用于在不同的数据集中进行数据查询过滤。
获取方式: ACFilter filter = ac.filter();
public class ACFilter {
// 各种关系连接符
public static long INVALID = 0;
public static long EQUAL = 1; // 等于
public static long NOT_EQUAL = 2; // 不等于
public static long GREATER = 3; // 大于
public static long GREATER_OR_EQUAL = 4; // 大于等于
public static long LESS = 5; // 小于
public static long LESS_OR_EQUAL = 6; // 小于等于
// 向查询过滤器中添加等于表达式
public ACFilter whereEqualTo(String key, Object value);
// 向查询过滤器中添加不等于表达式
public ACFilter whereNotEqualTo(String key, Object value);
// 向查询过滤器中添加大于表达式
public ACFilter whereGreaterThan(String key, Object value);
// 向查询过滤器中添加大于等于表达式
public ACFilter whereGreaterThanOrEqualTo(String key, Object value);
// 向查询过滤器中添加小于表达式
public ACFilter whereLessThan(String key, Object value);
// 向查询过滤器中添加小于等于表达式
public ACFilter whereLessThanOrEqualTo(String key, Object value);
}
务必注意:全表扫描FullScan操作非常消耗资源,建议只在后台做离线的定时任务用,为了保证在线用户数据访问的高可用性,会限制线上服务直接使用这样的接口;另外,数据扫描接口无法不能保证全局内所有数据的有序性。
FullScan操作的游标ACIterator: 注意ACIterator仅用于进行了分区数据集,对于未分区的数据集,请直接使用Scan接口。
public class ACIterator {
// 从游标中取出下一份数据结果集,每一份结果集对应一个分区键
public List<ACObject> next() throws Exception;
// 获取游标当前所在的分区键
public ACObject currentEntityGroup();
}
public abstract class ACStore {
// 各数据类型
public static long INVALID_TYPE = 0;
public static long INT_TYPE = 1; // 整型数据,目前统一为Long
public static long FLOAT_TYPE = 2; // 浮点型数据,目前统一为double
public static long BOOL_TYPE = 3; // 布尔型数据
public static long STRING_TYPE = 4; // 字符串类型数据
public static long OBJECT_TYPE = 5; // 对象数据
public static long ARRAY_TYPE = 6; // 数组/列表型数据
// 查询过滤器的连接符
public static long INVALID_CONNECTOR = 0;
public static long AND = 1; // 表示过滤器之间“且”的关系
public static long OR = 2; // 表示过滤器之间“或”的关系
// 数据排序的参数
public static long INVALID_ORDER = 0;
public static long ASC = 1; // 正序排序
public static long DESC = 2; // 倒序排序
// 创建一条数据
public interface Create {
// 将key, value写入client端内存,可以连续调用put
public Create put(String key, Object value) throws Exception;
// 该接口最终将client端的key-value(s)写入ablecloud的存储服务
public void execute() throws Exception;
}
// 查找数据
public interface Find {
// 设置查找的keys,可以不调用。如果不掉用select,则
// 返回primary key所对应的所有attributes
public Find select(String... keys);
// 该接口最终从ablecloud的存储服务进行查找并返回数据
public ACObject execute() throws Exception;
}
// 删除数据
public interface Delete {
// 该接口最终从ablecloud的存储服务中删除数据
public void execute() throws Exception;
}
// 批量删除数据
public interface BatchDelete {
// 设置第一个用于删除的条件过滤器,不允许重复调用
public BatchDelete where(ACFilter filter);
// 追加设置一个条件过滤器,与之前过滤器的关系是“且”,必须在where之后调用
// 注意在追加过滤器时,and的优先级高于or
public BatchDelete and(ACFilter filter);
// 追加设置一个条件过滤器,与之前过滤器的关系是“或”,必须在where之后调用
public BatchDelete or(ACFilter filter);
// 该接口最终从ablecloud的存储服务中删除数据
public void execute() throws Exception;
}
// 更新数据
public interface Update {
// 将已存在的key设置新的value值,可以连续调用put
public Update put(String key, Object value);
// 该接口最终将client端的key-value(s)写入ablecloud的存储服务
public void execute() throws Exception;
}
// 替换数据,和update的区别是,update只能更改已经存在的attributes,而
// replace可以设置全新的attributes
public interface Replace {
// 同Create的put
public Replace put(String key, Object value);
// 同Create的execute
public void execute() throws Exception;
}
// 扫描数据,注意每次查询最多返回1000条结果
public interface Scan {
// 设置需要返回的keys,类似find的select
public Scan select(String... keys);
// 设置扫描的起始点,需传入除entity group key之外的primary keys
// 不调用该接口默认从头开始扫描
public Scan start(Object... primaryKeys) throws Exception;
// 设置扫描的结束点,需传入除entity group key之外的primary keys
// (end需要和start一起使用,不能单独出现)
public Scan end(Object... primaryKeys) throws Exception;
// 设置扫描数据最大值
public Scan limit(int number);
// 设置第一个查询过滤器,不允许重复调用
public Scan where(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“且”,必须在where之后调用
// 注意在追加过滤器时,and的优先级高于or
public Scan and(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“或”,必须在where之后调用
public Scan or(ACFilter filter);
// 按照参数列表中的顺序,对各字段进行正向排序,可重复调用
public Scan orderByAsc(String... keys);
// 按照参数列表中的顺序,对各字段进行逆向排序,可重复调用
public Scan orderByDesc(String... keys);
// 按照参数列表中的顺序,对各字段进行分组,可重复调用
public Scan groupBy(String... keys);
// 统计结果集或者其中各个分组的记录数量
public Scan count();
// 按照参数列表中的顺序,对各字段在结果集或者各分组中的值分别统计求和,结果值将以Double型呈现
public Scan sum(String... keys);
// 按照参数列表中的顺序,对各字段在结果集或者各分组中的值分别统计求平均值,结果值将以Double型呈现
public Scan avg(String... keys);
// 按照参数列表中的顺序,对各字段在结果集或者各分组中的值分别统计求最大值
public Scan max(String... keys);
// 按照参数列表中的顺序,对各字段在结果集或者各分组中的值分别统计求最小值
public Scan min(String... keys);
// 该接口最终从ablecloud的存储服务中扫描数据,返回数据列表。
public List<ACObject> execute() throws Exception;
}
// 基于entity group key(分区键)的全表扫描,每次处理一个数据分区的数据并返回结果集
public interface FullScan {
// 设置需要返回的keys,类似find的select
public FullScan select(String... keys);
// 设置扫描的起始点,需传入除entity group key之外的primary keys
// 不调用该接口默认从头开始扫描(start和end至少需要调用一个)
public FullScan start(Object... primaryKeys) throws Exception;
// 设置扫描的结束点,需传入除entity group key之外的primary keys
// 不掉用该接口默认扫描到末尾(start和end至少需要调用一个)
public FullScan end(Object... primaryKeys) throws Exception;
// 设置扫描数据最大值
public FullScan limit(int number);
// 设置第一个查询过滤器,不允许重复调用
public FullScan where(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“且”,必须在where之后调用
// 注意在追加过滤器时,and的优先级高于or
public FullScan and(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“或”,必须在where之后调用
public FullScan or(ACFilter filter);
// 按照参数列表中的顺序,对各字段进行正向排序,可重复调用
public FullScan orderByAsc(String... keys);
// 按照参数列表中的顺序,对各字段进行逆向排序,可重复调用
public FullScan orderByDesc(String... keys);
// 按照参数列表中的顺序,对各字段进行分组,可重复调用
public FullScan groupBy(String... keys);
// 统计结果集或者其中各个分组的记录数量
public FullScan count();
// 对各字段在结果集或者各分组中的值分别统计求和,结果值将以Double型呈现,参数顺序不敏感
public FullScan sum(String... keys);
// 对各字段在结果集或者各分组中的值分别统计求平均值,结果值将以Double型呈现,参数顺序不敏感
public FullScan avg(String... keys);
// 对各字段在结果集或者各分组中的值分别统计求最大值,参数顺序不敏感
public FullScan max(String... keys);
// 对各字段在结果集或者各分组中的值分别统计求最小值,参数顺序不敏感
public FullScan min(String... keys);
// 该接口最终从ablecloud的存储服务中扫描数据,返回各分区的数据游标供调用者使用
public ACIterator execute() throws Exception;
}
// 简单全表扫描,基于用户每次设定的limit
public interface SimpleFullScan {
// 设置需要返回的keys,类似find的select
public SimpleFullScan select(String... keys);
// 设置第一个查询过滤器,不允许重复调用
public SimpleFullScan where(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“且”,必须在where之后调用
// 注意在追加过滤器时,and的优先级高于or
public SimpleFullScan and(ACFilter filter);
// 追加设置一个查询过滤器,与之前过滤器的关系是“或”,必须在where之后调用
public SimpleFullScan or(ACFilter filter);
// 该接口最终从ablecloud的存储服务中扫描数据,返回数据游标供调用者使用,调用者需要每次给定一个数据条数limit
public ACRowIterator execute() throws Exception;
}
/**
* 创建数据写入对象
* @param primaryKeys 完整的primary keys包括entity group keys
*
* @notice 不定参数primaryKeys可以有两种形态,如果是key-value对的方式
* k-v必须是成对出现。也可以传入一个ACObject对象,此时只能传入
* 一个参数,并且ACObject对象中需要将所有的primary key的value
* 值put进去。
* @return
*/
public abstract Create create(Object... primaryKeys);
/**
* 创建数据查找对象
* @param primaryKeys 同create的参数primaryKeys
* @return
*/
public abstract Find find(Object... primaryKeys);
/**
* 创建数据删除对象
* @param primaryKeys 同create的参数primaryKeys
* @return
*/
public abstract Delete delete(Object... primaryKeys);
/**
* 创建数据批量删除对象
* @param entityKeys 完整的entity group keys
* @notice 不定参数entityKeys可以有两种形态,如果是key-value对的方式
* k-v必须是成对出现。也可以传入一个ACObject对象,此时只能传入
* 一个参数,并且ACObject对象中需要将所有的entity group key的
* value值put进去。
* @return
*/
public abstract BatchDelete batchDelete(Object... entityKeys);
/**
* 创建数据更新对象
* @param primaryKeys 同create的参数primaryKeys
* @return
*/
public abstract Update update(Object... primaryKeys);
/**
* 创建数据替换对象
* @param primaryKeys 同create的参数primaryKeys
* @return
*/
public abstract Replace replace(Object... primaryKeys);
/**
* 创建数据扫描对象
* @param entityKeys 对于已分区的数据集,需要传入完整的entity group keys;
* 对于未分区的数据集,不需要传入任何参数;
* @notice 不定参数entityKeys可以有两种形态,如果是key-value对的方式
* k-v必须是成对出现。也可以传入一个ACObject对象,此时只能传入
* 一个参数,并且ACObject对象中需要将所有的entity group key的
* value值put进去。
* @return
*/
public abstract Scan scan(Object... entityKeys);
/**
* 创建全表数据扫描对象,基于数据分区
* @return
*/
public abstract FullScan fullScan();
/**
* 创建简单全表数据扫描对象
* @return
*/
public abstract SimpleFullScan simpleFullScan();
}
使用示例
以数据集"test_data"为例,假定其分区键为"deviceId"(字符串型);主键为"deviceId"(字符串型)和"timestamp"(整型);其他字段包括"status"(字符串型)、"mode"(字符串型)、"speed"(整型)和"pm25"(浮点型)等。
Create
方式一:显示的传入primary keys的k-v对
ac.store("test_data", context).create("deviceId", "12345", "timestamp", 1L) // 这里是k-v对
.put("status", "run")
.put("mode", "auto")
.put("speed", 45L)
.put("pm25", 35.5)
.execute();
方式二:传入primary keys的对象
ACObject pk = new ACObject();
pk.put("deviceId", "12345");
pk.put("timestamp", 1L);
ac.store("test_data", context).create(pk) // 这里是primary keys的对象
.put("status", "run")
.put("mode", "auto")
.put("speed", 45L)
.put("pm25", 35.5)
.execute();
Find
ACObject ao = ac.store("test_data", context)
.find("deviceId", "12345", "timestamp", 1L)
.execute();
String status = ao.get("status");
String mode = ao.get("mode");
Long speed = ao.get("speed");
Scan
由于是分区数据集,在Scan时需要传入分区键值对,这里是"deviceId"及其值。注意如果是非分区的数据集,则调用scan接口时不需要传入参数,如ac.store("test_data", context).scan()...
存储服务为了保证服务整体可用性,限制单次查询最多返回1000条结果,务必注意!!
示例一:设定start和limit,由start开始正向扫描,返回limit数量的结果集,其中各数据记录按主键自然正序排列
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.limit(10)
.execute();
示例二:设定start和end,由start开始正向扫描到end,返回start和end之间的结果集,其中各数据记录按主键自然正序排列
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.end("timestamp", 10L)
.execute();
示例三:设定end和limit,由end开始逆向扫描,返回limit数量的数据集,注意其中各数据记录按主键倒序排列。
注:我们经常遇到的获取设备最新的n条数据的需求就可以用这个接口组合来实现。
ac.store("test_data", context).scan("deviceId", "12345")
.end("timestamp", 10L)
.limit(10)
.execute();
示例四:指定查询过滤器进行查询
// 查询条件1:状态是正在运行并且转速大于等于300
ACFilter f1 = ac.filter().whereEqualTo("status", "running")
.whereGreaterThanOrEqualTo("speed", 300L);
// 查询条件2:状态是已停止并且PM2.5监控值小于50.0
ACFilter f2 = ac.filter().whereEqualTo("status", "stopped")
.whereLessThan("pm25", 50.0);
// 查询设备ID为"12345"的设备在一段时间内所有满足条件1或条件2的数据记录
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.end("timestamp", 10L)
.where(f1)
.or(f2)
.execute();
示例五:指定查询过滤器进行查询并排序,注意排序的各字段之间有优先级关系,在参数列表中越靠前优先级越高
// 查询条件:状态是正在运行
ACFilter f = ac.filter().whereEqualTo("status", "running");
// 查询设备ID为"12345"的设备在一段时间内所有满足条件的数据记录并按照转速(正序)、PM2.5监控值(倒序)以及时间戳(倒序)排序
// 本示例的意图为:查询设备ID为"12345"的设备在"1L"到"10L"这段时间内所有正在运转时上报的数据,同时进行排序,转速越低越靠前,转速相同的PM2.5越高越靠前,PM2.5也相同的时间越近越靠前
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.end("timestamp", 10L)
.where(f)
.orderByAsc("speed")
.orderByDesc("pm25", "timestamp")
.execute();
示例六:分组并进行简单的数值统计
/*
将设备ID为"12345"的设备在一段时间内的数据记录按照运行状态和控制模式分组,假设有四种情况:
-------------------------
| status | mode |
-------------------------
| running | auto |
-------------------------
| running | manual |
-------------------------
| stopped | auto |
-------------------------
| stopped | manual |
-------------------------
本示例的意图为:统计设备ID为"12345"的设备在"1L"到"10L"这段时间内所有上报的数据,按照"status"和"mode"分组,统计每个分组的记录总数、合计转速、平均转速和平均PM2.5以及最大转速和最大PM2.5.
*/
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.end("timestamp", 10L)
.groupBy("status", "mode")
.count()
.sum("speed")
.avg("speed", "pm25")
.max("speed", "pm25")
.execute();
示例七:复杂示例,各接口之间限制比较少,可以灵活组合来满足需求
// 将设备ID为"12345"的设备在一段时间内满足查询条件的数据记录进行分组、排序和聚合
ACFilter f1 = ac.filter().whereGreaterThan("speed", 0L)
.whereLessThan("speed", 50L);
ACFilter f2 = ac.filter().whereGreaterThanOrEqualTo("speed", 300L);
ACFilter f3 = ac.filter().whereLessThan("pm25", 30.0);
ac.store("test_data", context).scan("deviceId", "12345")
.start("timestamp", 1L)
.end("timestamp", 100L)
.where(f1)
.or(f2)
.or(f3)
.groupBy("status", "mode")
.orderByAsc("status", "mode")
.count()
.max("speed")
.min("speed", "pm25")
.execute();
FullScan
分区数据集还可以调用FullScan接口得到全表扫描的Iterator,每次调用Iterator的next()方法得到下一个有数据记录存在的分区中的数据,注意各分区间不保证有序! 同时注意全表扫描过程中Iterator会自动跳过没有数据的分区,整个扫描结束的条件是next()方法返回为空
// 延续Scan示例七中的查询条件进行全表所有分区的扫描
ACFilter f1 = ac.filter().whereGreaterThan("speed", 0L)
.whereLessThan("speed", 50L);
ACFilter f2 = ac.filter().whereGreaterThanOrEqualTo("speed", 300L);
ACFilter f3 = ac.filter().whereLessThan("pm25", 30.0);
ACIterator it = ac.store("test_data", context).fullScan()
.start("timestamp", 1L)
.end("timestamp", 100L)
.where(f1)
.or(f2)
.or(f3)
.groupBy("status", "mode")
.orderByAsc("status", "mode")
.count()
.max("speed")
.min("speed", "pm25")
.execute();
List<ACObject> zos;
while((zos = it.next()) != null) {
// 处理当前分区中的数据
...
}
BatchDelete
分区或者非分区的数据集都可以使用BatchDelete接口来支持批量删除。对于分区数据集,类似scan接口,每次的批量删除操作也是在某个分区键的范围内进行的,同时可以定义一个或几个ACFilter作为删除的条件;对于非分区数据集,同样类似于scan接口,batchDelete接口也是无参的,同时必须定义一个或几个ACFilter进行条件删除。
ACFilter f1 = ac.filter().whereGreaterThan("speed", 0L)
.whereLessThan("speed", 50L);
ACFilter f2 = ac.filter().whereGreaterThanOrEqualTo("speed", 300L);
ACFilter f3 = ac.filter().whereLessThan("pm25", 30.0);
ac.store("test_data", context).batchDelete("deviceId", "12345")
.where(f1)
.or(f2)
.or(f3)
.execute();
基于SimpleFullScan和Scan的全表分页浏览
全表的分页浏览也是一个重要的需求。本需求可以通过SimpleFullScan和Scan接口来实现,下面分别给出分区数据集和非分区数据集的实现示例。
非分区数据集
// limit是每个分页的最大数据条数,举例为50
int limit = 50;
List<ACObject> zos;
// 第一次调用scan,由非分区表的起始向下扫描limit+1条数据。注意每次多取一条,呈现前limit条,最后一条用作下一次取数据的start;同时注意非分区数据集的scan不需要传分区键
zos = ac.store("test_data", context)
.scan()
.limit(limit + 1)
.execute();
// 后续调用scan接口,start使用上一次扫描的结果数据集的最后一条
while (zos.size() >= limit + 1) {
zos = ac.store("test_data", context)
.scan()
.start(zos.get(limit))
.limit(limit + 1)
.execute();
}
分区数据集
// limit是每个分页的最大数据条数,举例为50
int limit = 50;
// 可以定义一些查询条件的过滤器
ACFilter f1 = ac.filter().whereGreaterThan("speed", 0L)
.whereLessThan("speed", 50L);
ACFilter f2 = ac.filter().whereGreaterThanOrEqualTo("speed", 300L);
ACFilter f3 = ac.filter().whereLessThan("pm25", 30.0);
ACRowIterator it = ac.store(TEST_CLASS, context)
.simpleFullScan()
.where(f1)
.and(f2)
.or(f3)
.execute();
// 注意这里可以直接使用每次期望获取的数据条数limit,不需要传入limit+1;同时每次迭代给定的limit可以不同
List<ACObject> zos;
while ((zos = it.next(limit)) != null) {
// 处理本次迭代取到的数据集
...
}
其它
delete/update/replace
的接口使用请参见上面的接口说明,使用方式类似,这里不一一举例了。
推送服务接口
该服务用于向当前设备的拥有者(owner)或所有用户发送推送消息(App端)
获取方式
ACNotificationMgr notificationMgr = ac.notificationMgr(ACContext context);
接口说明
public interface ACNotificationMgr {
public static long NOTIFY_ALL_USER = 0;
public static long NOTIFY_OWNER = 1;
/**
* 向当前设备owner或所有用户App发送通知
* @param deviceId 逻辑id
* @param userType 指定向当前设备的owner还是所有用户发送消息
* @param notification 指定通知标题/内容/是否振动、响铃、呼吸灯/通知点击后的动作
* @throws Exception
*/
public void sendNotification(long deviceId, long userType, ACNotification notification) throws Exception;
/**
* 向指定用户发送通知
* @param userList 用户id列表
* @param notification 指定通知标题/内容/是否振动、响铃、呼吸灯/通知点击后的动作
* @throws Exception
*/
public void sendNotification(List<Long> userList, ACNotification notification) throws Exception;
}
数据结构说明
public class ACNotification {
public static final long GO_APP = 0;
public static final long GO_URL = 1;
public static final long GO_ACTIVITY = 2;
public static final long NOTIFICATION = 0;
public static final long MESSAGE = 1;
// 通知显示类型
// NOTIFICATION:通知,MESSAGE:消息
private long displayType;
// 通知标题
private String title;
// 通知内容
private String content;
// 是否振动
private boolean vibrate;
// 是否呼吸灯
private boolean lights;
// 是否响铃
private boolean sound;
// 点击通知时的动作类型
// GO_APP:跳转到APP, GO_URL:跳转到指定url, GO_ACTIVITY:跳转到指定activity
private long openType;
// 当openType为GO_URL时指定url地址
private String url;
// 当openType为GO_ACTIVITY时指定activity
private String activity;
// 用户自定义数据
private Map<String, String> userData;
// 默认值
public ACNotification() {
this.title = "";
this.content = "";
this.vibrate = true;
this.lights = true;
this.sound = true;
this.openType = GO_APP;
this.url = "";
this.activity = "";
this.userData = new HashMap();
}
// 默认值
public ACNotification(String title, String content) {
this.title = title;
this.content = content;
this.vibrate = true;
this.lights = true;
this.sound = true;
this.openType = GO_APP;
this.url = "";
this.activity = "";
this.userData = new HashMap();
}
测试桩
有过开发经验的同学应该都知道,在开发较大项目时,通常会多个系统/模块并行开发。这多个系统/模块又相互依赖,例如上游程序相对简单,开发进度较快即将进入测试阶段,而其所依赖的下游还在开发之中,此时咱们不能等着下游完全ready后才开始测试,上游的开发人员一般会写桩程序(stub)用以模拟下游的简单实现,以使得上游程序能顺利的进行单元测试或模块测试。 开发者基于ablecloud的服务开发框架开发的服务既可能会和设备交互,也可能会和另外的服务交互,因此ablecloud的服务开发框架支持两类桩:
- 设备桩:模拟一个智能设备,对服务发过来的命令/消息做出响应
- 服务桩:模拟一个服务,对当前服务发过来的消息做出响应
设备桩
设备桩的定义非常简单,其目的是为了模拟设备,对服务发来的请求做出相应,因此只有一个处理请求并做出响应的接口,如下:
public abstract class ACDeviceStub {
public abstract void handleControlMsg(String majorDomain, String subDomain,
ACDeviceMsg req, ACDeviceMsg resp) throws Exception;
}
服务桩
服务桩没有另外定义,和真正的服务开发类似,直接继承ACService类,实现其中的handleMsg(ACMsg req, ACMsg resp)
接口,模拟另外依赖服务的行为即可。