内嵌云端服务

顾名思义,内嵌云端服务,是指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的前缀,可以相同。

存储模型示例如下: store

从上图我们可以看到,定义的一个存储模型如下:

注:目前所有的整型,都统一支持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)接口,模拟另外依赖服务的行为即可。