平台接口客户端
介绍
平台客户端 SDK 用于访问平台接口,提供了平台接口的 Java 实现, 可以很方便实现第三方系统与平台的集成。平台客户端 SDK 中包括常用的 租户管理
、项目管理
、 用户管理
、角色管理
、工作表及数据管理
、系统变量(数据字典)
、报警管理
等接口. 示例项目目上传至 https://github.com/air-iot/sdk-java-examples/tree/master/sdk-client-http-example.
使用方式
1. 在项目中引入依赖
在 pom.xml
中引入依赖:
<dependency>
<groupId>io.github.air-iot</groupId>
<artifactId>sdk-client-http-starter</artifactId>
<version>4.x.x</version>
</dependency>
2. 配置平台访问信息
在使用平台客户端之前, 需要在 application.yml
中配置平台的访问信息, 包括平台的 host
、app-key
、app-secret
等信息, 配置示例如下:
airiot:
client:
authorization: # 授权信息
type: project # 授权类型, 可选值: project, tenant
project-id: 1438f01ec22a859dedb3b98c # 授权项目ID, 当授权类型为 project 时必填. 如果平台为单项目版本时, 可以填 `default` 或不配置该项
app-key: e2233587-a2c9-ecf4-ed6e-c4a6122d2308 # 授权应用ID
app-secret: 3b74042c-b3ea-e548-a18c-cc78a20ffd0b # 授权应用密钥
http: # http 协议客户端配置
host: http://192.168.1.100:31000 # 平台网关地址. 平台网关默认端口为 31000
default-config: # 请求默认配置, 当具体服务未配置时, 使用默认配置
connect-timeout: 3000 # 连接超时, 可以使用 `3s`, `1m` 等格式
read-timeout: 5000 # 请求超时
services: # 具体服务的配置
core: # 服务名称, 可选值: core, data-service, spm, warning
connect-timeout: 5000
read-timeout: 15000
当授权类型为 project
时, 只能访问该项目中的相关资源。 当授权类型为 tenant
时, 可以访问该租户下的所有项目内的资源,但需要在请求项目内的资源时需要指定目标项目ID. 点击查看。
关于如何创建二次开发应用授权, 请查看 二次开发-第三方应用添加。
3. 在需要调用平台接口的类中注入客户端对象
在引入客户端 SDK 后,各客户端对象会自动注入到 Spring 容器中, 可以直接在需要调用平台接口的类中注入客户端对象, 并调用相应的接口方法。示例代码如下:
@Component
public class Demo {
/**
* 注入用户客户端接口
*/
@Autowired
private UserClient userClient;
/**
* 注入角色客户端接口
*/
@Autowired
private RoleClient roleClient;
/**
* 创建用户
*/
public void createUser() {
User user = new User();
user.setName("admin");
user.setPassword("123456");
user.setPhone("187xxxx1234");
// 调用平台接口并获取返回结果
ResponseDTO<InsertResult> responseDTO = this.userClient.create(user);
// 处理返回结果
}
}
4. 统一响应格式说明
所有客户端接口方法统一返回 ResponseDTO<T>
结构, 泛型 T
代表请求返回的数据类型, 该类型可能为 Void
。 ResponseDTO<T>
结构如下:
public class ResponseDTO<T> {
/**
* 请求是否成功标识. 如果为 {@code true} 表明请求成功, 否则请求失败
*/
private boolean success;
/**
* 请求状态码
*/
private int code;
/**
* 响应信息
*/
private String message;
/**
* 详细信息
*/
private String detail;
/**
* 字段级别的错误信息
*/
private String field;
/**
* 总记录数
* <br>
* 当查询请求设置了 {@code cn.airiot.sdk.client.builder.Query#withCount} 为 {@code true} 时, 该字段为匹配的记录数量.
*/
private long count;
/**
* 响应数据
*/
private T data;
}
客户端列表
空间管理
空间管理服务(spm)客户端主要包括 项目管理
等接口。在需要为该服务内的接口配置独立的超时时间时, 可以在 application.yml
中配置 airiot.client.http.services.spm
节点。
包含的客户端接口如下:
资源对象 | 客户端类 | 内容 |
---|---|---|
项目信息 | io.github.airiot.sdk.client.service.spm.ProjectClient | 包括对项目信息的增删改查以及授权等操作 |
项目管理客户端
/**
* 项目信息管理客户端接口
*/
public interface ProjectClient {
/**
* 创建项目
*
* @param project 项目信息
* @return 项目信息或错误信息
*/
ResponseDTO<ProjectCreateResult> create(@Nonnull Project project);
/**
* 查询项目信息
*
* @param query 查询条件
* @return 项目信息
*/
ResponseDTO<List<Project>> query(@Nonnull Query query);
/**
* 查询全部项目信息
*
* @return 项目信息
*/
ResponseDTO<List<Project>> queryAll();
/**
* 根据项目ID查询项目信息
*
* @param projectId 项目ID
* @return 项目信息
*/
ResponseDTO<Project> queryById(@Nonnull String projectId);
/**
* 更新项目信息
* <br>
* 如果字段的值为 {@code null} 则不更新
*
* @param project 更新后的项目信息
* @return 更新结果
*/
ResponseDTO<Void> update(@Nonnull Project project);
/**
* 替换项目信息
*
* @param project 替换后的项目信息
* @return 替换结果
*/
ResponseDTO<Void> replace(@Nonnull Project project);
/**
* 删除项目
*
* @param projectId 项目ID
* @return 删除结果
*/
ResponseDTO<Void> deleteById(@Nonnull String projectId);
}
核心服务
核心服务(core
)客户端主要包括 用户管理
、角色管理
、工作表及数据管理
、系统变量(数据字典)
等接口,并且不断在丰富和完善中。
在需要为该服务内的接口配置独立的超时时间时, 可以在 application.yml
中配置 airiot.client.http.services.core
节点。包含的客户端接口如下:
资源对象 | 客户端类 | 内容 |
---|---|---|
用户信息 | io.github.airiot.sdk.client.service.core.UserClient | 包括对用户信息的增删改查操作 |
角色信息 | io.github.airiot.sdk.client.service.core.RoleClient | 包括对角色信息的增删改查, 以及对象角色权限信息的修改操作 |
系统变量(数据字典) | io.github.airiot.sdk.client.service.core.SystemVariableClient | 包括对系统变量(数据字典)的增删改查操作 |
工作表定义 | io.github.airiot.sdk.client.service.core.TableSchemaClient | 包括对工作表的定义的查询操作 |
工作表数据 | io.github.airiot.sdk.client.service.core.SpecificTableDataClient | 包括对工作表内的数据进行增删除改查等操作 |
时序数据 | io.github.airiot.sdk.client.service.core.TimingDataClient | 包括对数据点历史数据和最新数据的查询操作 |
用户管理客户端
/**
* 用户管理客户端
*/
public interface UserClient extends PlatformClient {
/**
* 创建用户
*
* @param user 用户信息
* @return 用户ID或错误信息
*/
ResponseDTO<User> create(@Nonnull User user);
/**
* 更新用户信息
*
* @param userId 要更新的用户ID
* @param user 要更新的用户信息
* @return 更新结果
*/
ResponseDTO<Void> update(@Nonnull String userId, @Nonnull User user);
/**
* 替换用户全部信息
*
* @param userId 要替换的用户ID
* @param user 替换后的用户信息
* @return 替换结果
*/
ResponseDTO<Void> replace(@Nonnull String userId, @Nonnull User user);
/**
* 根据用户ID删除用户
*
* @param userId 用户ID
* @return 删除结果
*/
ResponseDTO<Void> deleteById(@Nonnull String userId);
/**
* 根据条件查询用户信息
*
* @param query 查询条件
* @return 用户信息或错误信息
*/
ResponseDTO<List<User>> query(@Nonnull Query query);
/**
* 根据用户ID查询用户信息
*
* @param userId 用户ID
* @return 用户信息或错误信息
*/
ResponseDTO<User> queryById(@Nonnull String userId);
/**
* 根据用户名查询用户信息
*
* @param name 用户名
* @return 用户信息或错误信息
*/
default ResponseDTO<List<User>> queryByName(@Nonnull String name) {
return query(Query.newBuilder().select(User.class).filter().eq(User::getName, name).end().build());
}
}
角色管理客户端
/**
* 角色客户端
*/
public interface RoleClient {
/**
* 创建角色
*
* @param role 角色信息
* @return 创建结果. 如果创建成功, 则返回角色ID
*/
ResponseDTO<InsertResult> create(@Nonnull Role role);
/**
* 查询角色信息
*
* @param query 查询条件
* @return 角色信息
*/
ResponseDTO<List<Role>> query(@Nonnull Query query);
/**
* 根据角色ID查询角色信息
*
* @param roleId 角色ID
* @return 角色信息
*/
ResponseDTO<Role> queryById(@Nonnull String roleId);
/**
* 根据角色名称查询角色信息
*
* @param roleName 角色名称
* @return 角色信息列表
*/
default ResponseDTO<List<Role>> queryByName(@Nonnull String roleName) {
return query(Query.newBuilder().select(Role.class).filter().eq(Role::getName, roleName).end().build());
}
/**
* 查询全部角色信息
*
* @return 角色信息列表
*/
default ResponseDTO<List<Role>> queryAll() {
return query(Query.newBuilder().select(Role.class).build());
}
/**
* 替换角色全部信息
*
* @param roleId 被替换角色ID
* @param role 替换后的角色信息
* @return 替换结果
*/
ResponseDTO<Void> replace(@Nonnull String roleId, @Nonnull Role role);
/**
* 更新角色信息
*
* @param roleId 被更新角色ID
* @param role 要替换的角色信息(值为 null 的字段不会被更新)
* @return 更新结果
*/
ResponseDTO<Void> update(@Nonnull String roleId, @Nonnull Role role);
/**
* 删除角色信息
*
* @param roleId 角色ID
* @return 删除结果
*/
ResponseDTO<Void> deleteById(@Nonnull String roleId);
}
系统变量客户端
/**
* 系统变量(数据字典)客户端
*/
public interface SystemVariableClient {
/**
* 查询系统变量信息
*
* @param query 查询条件
* @return 系统变量信息
*/
ResponseDTO<List<SystemVariable>> query(@Nonnull Query query);
/**
* 查询全部系统变量信息
*
* @return 系统变量信息
*/
default ResponseDTO<List<SystemVariable>> queryAll() {
return query(Query.newBuilder().select(SystemVariable.class).build());
}
/**
* 根据系统变量编号查询系统变量信息
*
* @param uid 系统变量编号
* @return 系统变量信息
*/
default ResponseDTO<SystemVariable> queryByUId(@Nonnull String uid) {
ResponseDTO<List<SystemVariable>> response = query(Query.newBuilder()
.select(SystemVariable.class)
.filter()
.eq(SystemVariable::getUid, uid).end()
.build());
if (!response.isSuccess()) {
return new ResponseDTO<>(response.isSuccess(), response.getCode(), response.getMessage(), response.getDetail(), null);
}
List<SystemVariable> systemVariables = response.getData();
if (systemVariables != null && systemVariables.size() > 1) {
throw new IllegalStateException("根据 uid '" + uid + "' 查询到多个系统变量, " + systemVariables);
}
return new ResponseDTO<>(response.isSuccess(), response.getCode(),
response.getMessage(), response.getDetail(),
systemVariables == null || systemVariables.isEmpty() ? null : systemVariables.get(0));
}
/**
* 根据系统变量ID查询系统变量信息
*
* @param id 系统变量ID
* @return 系统变量信息
*/
ResponseDTO<SystemVariable> queryById(@Nonnull String id);
/**
* 根据系统变量名称查询系统变量信息
*
* @param name 系统变量名称
* @return 系统变量信息
*/
default ResponseDTO<List<SystemVariable>> queryByName(@Nonnull String name) {
return query(Query.newBuilder().select(SystemVariable.class)
.filter().eq(SystemVariable::getName, name).end()
.build());
}
/**
* 创建系统变量信息
*
* @param systemVariable 系统变量信息
* @return 创建结果
*/
ResponseDTO<InsertResult> create(@Nonnull SystemVariable systemVariable);
/**
* 替换系统变量信息
*
* @param id 系统变量ID
* @param systemVariable 系统变量信息
* @return 替换结果
*/
ResponseDTO<Void> replace(@Nonnull String id, @Nonnull SystemVariable systemVariable);
/**
* 更新系统变量信息
*
* @param id 系统变量ID
* @param systemVariable 系统变量信息
* @return 更新结果
*/
ResponseDTO<Void> update(@Nonnull String id, @Nonnull SystemVariable systemVariable);
/**
* 更新系统变量的值
*
* @param id 系统变量ID
* @param value 系统变量值
* @return 更新结果
*/
default ResponseDTO<Void> updateValue(@Nonnull String id, @Nonnull Object value) {
SystemVariable variable = new SystemVariable();
variable.setId(id);
variable.setValue(value);
return this.update(id, variable);
}
/**
* 删除系统变量信息
*
* @param id 系统变量ID
* @return 删除结果
*/
ResponseDTO<Void> deleteById(@Nonnull String id);
}
工作表定义客户端
/**
* 工作表定义客户端
*/
public interface TableSchemaClient {
/**
* 查询工作表定义
*
* @return 工作表定义信息
*/
ResponseDTO<List<TableSchema>> query(@Nonnull Query query);
/**
* 查询全部工作表定义
*
* @return 工作表定义信息
*/
default ResponseDTO<List<TableSchema>> queryAll() {
return query(Query.newBuilder().select(TableSchema.class).build());
}
/**
* 查询工作表定义
*
* @param tableId 表标识
* @return 工作表定义信息
*/
ResponseDTO<TableSchema> queryById(@Nonnull String tableId);
/**
* 根据工作表标题查询工作表定义
*
* @param tableTitle 工作表标题
* @return 工作表定义信息
*/
default ResponseDTO<List<TableSchema>> queryByTitle(@Nonnull String tableTitle) {
return query(Query.newBuilder().select(TableSchema.class).filter().eq(TableSchema::getTitle, tableTitle).end().build());
}
}
工作表数据客户端
由于每个工作表的结构都不相同, 因此工作表数据的客户端接口是动态生成的, 通过 TableDataClientFactory
工厂类获取, 在操作具体的工作表数据时, 需要先获取工作表数据客户端, 然后再进行操作。
TableDataClientFactory
对象已经注入到 Spring 容器中, 可以直接注入使用。
/**
* 工作表记录客户端工厂, 可用于创建指定工作表记录客户端
*/
public abstract class TableDataClientFactory {
/**
* 根据工作表记录类创建指定工作表记录客户端. 要求该类必须标注 {@link WorkTable} 注解
*
* @param clazz 工作表记录类
* @param <T> 工作表记录类型
* @return 工作表记录客户端
*/
public <T> SpecificTableDataClient<T> newClient(Class<T> clazz) {
// 创建工作表记录客户端
}
/**
* 根据工作表记录类和表标识创建指定工作表记录客户端
*
* @param tableId 表标识
* @param clazz 工作表记录类
* @param <T> 工作表记录类型
* @return 工作表记录客户端
*/
public <T> SpecificTableDataClient<T> newClient(String tableId, Class<T> clazz) {
// 创建工作表记录客户端
}
}
创建具体工作表数据客户端对象示例如下:
/**
* 学生信息表, 使用 @WorkTable 注解标注工作表标识
*/
@WorkTable("student")
public class Student {
/**
* 学号
*/
private String id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private String age;
/**
* 性别.
* 如果工作表定义中的字段名与类中的属性名不一致, 可以使用 @Field 注解标注.
* <br>
* 例如: 性别在工作表定义中的名称为 "sex", 可以按下面方式标注.
*/
@Field("sex")
private String gender;
}
@Service
public class DemoService {
/**
* 学生信息表的数据客户端
*/
private final SpecificTableDataClient<Student> studentClient;
public DemoService(TableDataClientFactory factory) {
// 创建学生信息表的数据客户端
// 方式一: 要求工作表记录类必须标注 @WorkTable 注解
this.studentClient = factory.newClient(Student.class);
// 方式二: 手动指定工作表标识
// this.studentClient = factory.newClient("student", Student.class);
}
public void createStudent() {
Student student = new Student();
student.setId("1001");
student.setName("张三");
student.setAge("18");
student.setGender("男");
// 调用学生信息表的数据客户端, 向学生信息表中添加记录
ResponseDTO<InsertResult> response = this.studentClient.create(student);
// 处理响应结果
}
}
注: TableDataClientFactory
对会对已创建的工作表数据客户端缓存(工作表标识作为缓存的 key), 因此也可以在需要使用的时候创建.
在使用 newClient(Class<T> clazz)
方法创建工作表数据客户端时, 要求类型上必须添加 @WorkTable
注解.
/**
* 指定工作表的数据客户端
* @param <T> 承载工作表记录的类型的泛型
*/
public abstract class SpecificTableDataClient<T> {
/**
* 向工作表中添加记录
*
* @param row 记录
* @return 数据添加结果
*/
public abstract ResponseDTO<InsertResult> create(@Nonnull T row);
/**
* 向工作表中批量添加记录
*
* @param rows 记录列表
* @return 数据添加结果
*/
public abstract ResponseDTO<BatchInsertResult> create(@Nonnull List<T> rows);
/**
* 更新工作表记录
*
* @param rowId 记录ID
* @param data 要更新的记录
* @return 数据更新结果
*/
public abstract ResponseDTO<UpdateOrDeleteResult> update(@Nonnull String rowId, @Nonnull T data);
/**
* 批量更新工作表记录.
* <br>
* 更新所有与 {@code query} 匹配的记录
*
* @param query 更新条件
* @param data 要更新的数据
* @return 数据更新结果
*/
public abstract ResponseDTO<UpdateOrDeleteResult> update(@Nonnull Query query, @Nonnull T data);
/**
* 替换记录全部信息
*
* @param rowId 记录ID
* @param data 替换后的记录信息
*/
public abstract ResponseDTO<Void> replace(@Nonnull String rowId, @Nonnull T data);
/**
* 根据记录ID删除数据
*
* @param rowId 记录ID
* @return 数据删除结果
*/
public abstract ResponseDTO<Void> deleteById(@Nonnull String rowId);
/**
* 批量删除工作表记录
*
* @param query 删除条件
* @return 数据删除结果
*/
public abstract ResponseDTO<UpdateOrDeleteResult> deleteByQuery(@Nonnull Query query);
/**
* 根据条件查询用户信息
*
* @param query 查询条件
* @return 用户信息或错误信息
*/
public abstract ResponseDTO<List<T>> query(@Nonnull Query query);
/**
* 根据ID查询记录信息
*
* @param rowId 记录ID
* @return 记录信息或错误信息
*/
public abstract ResponseDTO<T> queryById(@Nonnull String rowId);
}
时序数据客户端
/**
* 时序数据客户端
*/
public interface TimingDataClient {
/**
* 查询数据点的历史数据
*
* @param queries 查询信息, 可以对数据进行过滤、分组、汇总和排序等操作
* @return 查询结果
*/
List<TimingData> query(List<TimingDataQuery> queries);
/**
* 查询指定数据点的最新数据. 可同时查询多个设备的数据点最新数据.
*
* @param query 要查询的数据点列表
* @return 数据点的最新数据
*/
List<LatestData> queryLatest(LatestDataQuery query);
}
LatestDataQuery
的构建方式与 Query 类似.
媒体库客户端
/**
* 媒体库客户端
*/
public interface MediaLibraryClient extends PlatformClient {
/**
* 创建目录
*
* @param catalog 目录. 例如: /a/b/c
* @return 创建目录请求结果
*/
default ResponseDTO<Void> mkdir(@Nonnull String catalog) {
int lastIndex = catalog.indexOf("/");
if (lastIndex < 0) {
return this.mkdir(new MkdirDTO("", catalog));
} else {
return this.mkdir(new MkdirDTO(catalog.substring(0, lastIndex), catalog.substring(lastIndex + 1)));
}
}
/**
* 创建目录
*
* @param dir 目录信息
* @return 创建目录请求结果
*/
ResponseDTO<Void> mkdir(@Nonnull MkdirDTO dir);
/**
* 上传文件到媒体库
*
* @param catalog 上传目录
* @param action 出重同名文件时执行的动作. 可选值: cover: 覆盖, rename: 文件名自动加1
* @param filename 文件名
* @param fileData 文件数据
* @return 如果上传成功, 则返回该文件的 url
*/
ResponseDTO<UploadFileResult> upload(@Nonnull String catalog, @Nonnull String action, @Nonnull String filename, @Nonnull byte[] fileData);
/**
* 将远程文件上传到媒体库
* @param params 参数列表
* @return 如果上传成功, 则返回该文件的 url
*/
ResponseDTO<UploadFileResult> uploadFromUrl(@Nonnull MediaLibrarySaveFileFromUrlParams params);
/**
* 上传文件到媒体库
*
* @param catalog 上传目录
* @param action 出重同名文件时执行的动作. 可选值: cover: 覆盖, rename: 文件名自动加1
* @param file 文件
* @return 如果上传成功, 则返回该文件的 url
* @throws IOException 如果文件不存在
*/
default ResponseDTO<UploadFileResult> upload(@Nonnull String catalog, @Nonnull String action, @Nonnull File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] fileData = new byte[fis.available()];
fis.read(fileData);
return upload(catalog, action, file.getName(), fileData);
}
}
/**
* 上传文件到媒体库
*
* @param catalog 上传目录
* @param action 重同名文件时执行的动作. 可选值: cover: 覆盖, rename: 文件名自动加1
* @param filename 自定义文件名
* @param file 文件
* @return 如果上传成功, 则返回该文件的 url
* @throws IOException 如果文件不存在
*/
default ResponseDTO<UploadFileResult> upload(@Nonnull String catalog, @Nonnull String action, String filename, @Nonnull File file) throws IOException {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] fileData = new byte[fis.available()];
fis.read(fileData);
return upload(catalog, action, filename, fileData);
}
}
/**
* 将远程文件上传到媒体库
*
* @param catalog 上传目录
* @param filename 文件名
* @param sourceUrl 远程文件地址
* @return 如果上传成功, 则返回该文件的 url
*/
default ResponseDTO<UploadFileResult> uploadFromUrl(@Nonnull String catalog, @Nonnull String filename, @Nonnull String sourceUrl) {
return this.uploadFromUrl(new MediaLibrarySaveFileFromUrlParams(sourceUrl, catalog, filename));
}
}
报警服务
报警服务(warning
)客户端主要包括 报警规则
、报警记录
等接口。
在需要为该服务内的接口配置独立的超时时间时, 可以在 application.yml
中配置 airiot.client.http.services.warning
节点。
资源对象 | 客户端类 | 内容 |
---|---|---|
报警规则 | io.github.airiot.sdk.client.service.warning.RuleClient | 包括对报警规则操作 |
报警记录 | io.github.airiot.sdk.client.service.warning.WarnClient | 包括对报警记录的增删改查等操作 |
报警规则客户端
/**
* 告警规则客户端
*/
public interface RuleClient {
/**
* 查询报警规则
*
* @param query 查询条件
* @return 报警规则信息
*/
ResponseDTO<List<Rule>> query(@Nonnull Query query);
}
报警记录客户端
/**
* 告警信息客户端
*/
public interface WarnClient {
/**
* 查询告警信息
*
* @param query 查询条件
* @param archive 是否在归档数据中查询
* @return 告警信息
*/
ResponseDTO<List<Warning>> query(@Nonnull Query query, String archive);
/**
* 根据告警信息ID查询告警信息
*
* @param warningId 告警信息ID
* @param archive
* @return 告警信息
*/
ResponseDTO<Warning> queryById(@Nonnull String warningId, String archive);
/**
* 创建告警
*
* @param warning 告警信息
* @return 创建结果
*/
ResponseDTO<InsertResult> create(@Nonnull Warning warning);
}
数据源服务
数据源服务(data-service
)客户端主要为调用项目中已添加的数据源。
在需要为该服务内的接口配置独立的超时时间时, 可以在 application.yml
中配置 airiot.client.http.services.data-service
节点。
资源对象 | 客户端类 | 内容 |
---|---|---|
数据源 | io.github.airiot.sdk.client.service.ds.DataServiceClient | 调用项目中已添加的数据接口 |
数据源客户端
/**
* 数据接口客户端, 用于调用平台已创建的数据接口
*/
public interface DataServiceClient {
/**
* 调用数据接口
*
* @param tClass 接口返回值类型
* @param dsId 接口标识
* @param params 参数列表, 即数据接口中添加的参数, 如果没有定义参数则传 {@code null}. key: 参数名, value: 参数值.
* @return 请求结果
*/
<T> ResponseDTO<T> call(@Nonnull Class<T> tClass, @Nonnull String dsId, @Nullable Map<String, Object> params);
}
其它
客户端 SDK 中提供了一些工具类, 用于简化和辅助开发。
请求上下文
当二次开发的服务需要同时操作多个项目内的资源时, 可以使用请求上下文 (RequestContext
) 来切换当前请求的项目, 该上下文是线程安全的, 所有数据是通过 ThreadLocal
存储的, 不同请求之间相互隔离. RequestContext
主要包括以下方法:
/**
* 请求上下文
*/
public class RequestContext {
/**
* 设置当前请求的项目ID
* @param projectId 项目ID
*/
public static void setProjectId(String projectId) {
// ...
}
/**
* 获取当前已设置的项目ID
* @return 项目ID
*/
public static String getProjectId() {
// ...
}
/**
* 清空当前请求的项目ID
*/
public static void clearProjectId() {
// ...
}
}
使用示例:
@RestController
public class MyController {
@GetMapping("/doSomething")
public void doSomething(HttpServletRequest request) {
// 获取请求头中的项目ID
String projectId = request.getHeader("X-Project-Id");
// 设置当前请求的项目ID
RequestContext.setProjectId(projectId);
try {
// ...
} finally {
// 清空当前请求的项目ID
RequestContext.clearProjectId();
}
}
}
查询构造器
客户端接口中的很多查询接口, 其结构比较复杂, 不易构造且容易出错, 为此 SDK 中提供了一个查询构造器 Query
来简化查询条件的构造.
查询参数的整体结构如下所示:
{
"project": {},
"filter": {},
"sort": {},
"limit": 30,
"skip": 20,
"withCount": true
}
字段说明如下:
project
查询请求需要返回的字段列表. 例如:{"id": 1, "name": 1, "address": {"city": 1}}
.key
为字段名,value
为1
或Map
. 如果为为一级字段需要设置为1
例如:{"id": 1, "name": 1}
, 如果要返回嵌套对象内的字段, 则需要设置为Map
, 例如:{"address": {"city": 1}}
filter
查询条件, 如果没有添加任何条件则查询全部数据.key
为字段名,value
为过滤的值或逻辑运算符, 例如:{"name": "Tom", "age": {"$gt": 20, "$lt": 30}}
.sort
排序条件,key
为字段名,value
为1
表示升序,-1
表示降序, 例如:{"age": 1, "name": -1}
.limit
查询结果的最大数量, 可用于分页查询或限制返回的记录数量.skip
查询结果的偏移量, 即忽略前 N 记录, 可用于分页查询.withCount
是否返回符合条件的记录总数, 如果为true
则会在查询结果记录数量会保存在响应对象ResponseDTO<T>
中的count
字段.
注意事项
- 如果查询条件需要使用逻辑或, 可以在
filter
中添加$or
字段, 其值为Map<String, Object>
结构与filter
一致, 任一条件成立时表示记录匹配. - 如果同一字段存在多个逻辑条件, 则需要将多个条件放在一个
Map
中, 例如:{"age": {"$gt": 20, "$lt": 30}}
, 表示查询20 < age < 30
的记录.
逻辑运算符
符号 | 说明 | 示例 |
---|---|---|
$not | 不相等, 与 SQL 中的 <> 作用相同 | {"age": {"$not": 18}} |
$in | 在指定列表内, 与 SQL 中的 in 作用相同 | {"id": {"$in": [1,3,4]}} |
$nin | 不在指定列表内, 与 SQL 中的 not in 作用相同 | {"id": {"$nin": [1,3,4]}} |
$gt | 大于指定的值, 与 SQL 中的 > 作用相同 | {"age": {"$gt": 18}} |
$gte | 大于等于指定的值, 与 SQL 中的 >= 作用相同 | {"age": {"$gte": 18}} |
$lt | 小于指定的值, 与 SQL 中的 < 作用相同 | {"age": {"$lt": 18}} |
$lte | 小于等于指定的值, 与 SQL 中的 <= 作用相同 | {"age": {"$lte": 18}} |
$regex | 正则匹配, 与 SQL 中的 like 相似 | {"name": {"$regex": "张"}} |
/**
* 查询构造器
*/
public class Query {
public static class Builder {
/**
* 构造过滤条件
*/
public FilterBuilder filter() {
// ...
}
/**
* 构造逻辑或过滤条件
*/
public FilterBuilder or() {
// ...
}
/**
* 添加要返回的字段列表
*
* @see #select(Collection)
*/
public Builder select(String... fields) {
// ...
}
/**
* 添加要返回的字段列表
*
* @see #select(Collection)
*/
public <T> Builder select(SFunction<T, ?>... columns) {
// ...
}
/**
* 获取指定类型中定义的全部字段
* <br>
* 如果字段被 static 和 transient 修饰则会跳过. 如果字段上带有 {@link Field} 则使用该注解定义的名称
*
* @param tClass 类型
*/
public <T> Builder select(Class<T> tClass) {
// ...
}
/**
* 设置查询返回的字段列表
*
* @param fields 字段名称列表
*/
public Builder select(Collection<String> fields) {
// ...
}
/**
* 设置查询返回的嵌套对象内的字段列表
*
* @see #selectSubFields(String, Map)
*/
public <T> Builder selectSubFields(SFunction<T, ?> column, String... subFields) {
// ...
}
/**
* 设置查询返回的嵌套对象内的字段列表
*
* @see #selectSubFields(String, Map)
*/
public <T> Builder selectSubFields(String field, String... subFields) {
// ...
}
/**
* 设置查询返回的嵌套对象内的字段列表
*
* @see #selectSubFields(String, Map)
*/
public <T> Builder selectSubFields(SFunction<T, ?> column, Map<String, Object> subFields) {
// ...
}
/**
* 设置查询返回的嵌套对象内的字段列表
*
* @param field 字段名称
* @param subFields 嵌套对象内的字段列表
*/
public Builder selectSubFields(String field, Map<String, Object> subFields) {
// ...
}
/**
* 排除要查询的字段列表, 即查询结果中不返回该字段
* <br>
* 通常情况下, 在使用 {@link #select(Class)} 添加所有字段后, 再使用该方法排除不需要的字段
*
* @param columns 要排除的字段名称列表
*/
public Builder exclude(Collection<String> columns) {
// ...
}
/**
* 排除要查询的字段列表
*
* @see #exclude(Collection)
*/
public Builder exclude(String... columns) {
// ...
}
/**
* 排除要查询的字段列表
*
* @see #exclude(Collection)
*/
public <T> Builder exclude(SFunction<T, ?>... columns) {
// ...
}
/**
* 汇总查询字段
*
* @param field 字段名
*/
public AggregateBuilder summary(String field) {
// ...
}
/**
* 汇总查询字段
*
* @param column 列
*/
public <T> AggregateBuilder summary(SFunction<T, ?> column) {
// ...
}
/**
* 分组
*
* @param field 字段名
*/
public GroupByBuilder groupBy(String field) {
// ...
}
/**
* 分组汇总
*/
protected Builder groupBy(Map<String, Object> field) {
// ...
}
/**
* 分组汇总
*
* @param column 列
*/
public <T> GroupByBuilder groupBy(SFunction<T, ?> column) {
// ...
}
/**
* 添加升序字段
*
* @param fields 字段名称列表
*/
public Builder orderAsc(Collection<String> fields) {
// ...
}
/**
* 添加升序字段
*
* @see #orderAsc(Collection)
*/
public Builder orderAsc(String... fields) {
// ...
}
/**
* 添加升序字段
*
* @see #orderAsc(Collection)
*/
public <T> Builder orderAsc(SFunction<T, ?>... fields) {
// ...
}
/**
* 添加降序字段
*
* @see #orderDesc(Collection)
*/
public Builder orderDesc(String... fields) {
// ...
}
/**
* 添加降序字段
*
* @see #orderDesc(Collection)
*/
public <T> Builder orderDesc(SFunction<T, ?>... fields) {
// ...
}
/**
* 添加降序字段
*
* @param fields 字段名称列表
*/
public Builder orderDesc(Collection<String> fields) {
// ...
}
/**
* 设置跳过的记录数, 用于分页
*
* @param skip 跳过数量
*/
public Builder skip(int skip) {
// ...
}
/**
* 设置返回的记录数
*
* @param limit 返回的记录数量
*/
public Builder limit(int limit) {
// ...
}
/**
* 返回总记录数
*/
public Builder withCount() {
// ...
}
/**
* 完成构建并返回查询以象
*/
public Query build() {
// ...
}
}
}
通过 filter()
方法创建过滤条件构造器, 或通过 or()
方法创建逻辑或过滤条件, 在过滤条件构造完成后需要调用 end()
方法完成构造. 构造器如下所示:
public class FilterBuilder {
/**
* 结束查询条件构建, 并返回查询构建器
*/
public Query.Builder end() {
// ...
}
/**
* 等于
*
* @param field 字段名
* @param value 字段值
*/
public <T> FilterBuilder eq(String field, T value) {
// ...
}
public <Type, T> FilterBuilder eq(SFunction<Type, ?> column, T value) {
// ...
}
/**
* 不等于
*
* @param field 字段名
* @param value 字段值
*/
public <T> FilterBuilder ne(String field, T value) {
// ...
}
/**
* 不等于
*
* @see #ne(String, Object)
*/
public <Type, T> FilterBuilder ne(SFunction<Type, ?> column, T value) {
// ...
}
/**
* 在指定列表之内
*
* @see #in(String, Object[])
*/
public <T> FilterBuilder in(String field, T... value) {
// ...
}
/**
* 在指定列表之内
*
* @see #in(String, Object[])
*/
public <Type, T> FilterBuilder in(SFunction<Type, ?> column, T value) {
// ...
}
/**
* 在指定列表之内
*
* @param field 字段名
* @param value 字段值列表
*/
public <T> FilterBuilder in(String field, Collection<T> value) {
// ...
}
/**
* 不在指定列表之内
*
* @see #notIn(String, Object...)
*/
public <T> FilterBuilder notIn(String field, T... values) {
// ...
}
/**
* 不在指定列表之内
*
* @see #notIn(String, Object...)
*/
public <Type, T> FilterBuilder notIn(SFunction<Type, ?> column, T... values) {
// ...
}
/**
* 不在指定列表之内
*
* @param field 字段名
* @param values 字段值列表
*/
public <T> FilterBuilder notIn(String field, Collection<T> values) {
// ...
}
/**
* 正则表达式匹配
*
* @see #regex(String, String)
*/
public <Type> FilterBuilder regex(SFunction<Type, ?> column, String regex) {
// ...
}
/**
* 正则表达式匹配
*
* @param field 字段名
* @param regex 正则表达式
*/
public FilterBuilder regex(String field, String regex) {
// ...
}
/**
* 小于
*
* @param field 字段名
* @param value 字段值
*/
public FilterBuilder lt(String field, Object value) {
// ...
}
/**
* 小于
*
* @see #lt(String, Object)
*/
public <T> FilterBuilder lt(SFunction<T, ?> column, Object value) {
// ...
}
/**
* 小于或等于
*
* @param field 字段名
* @param value 字段值
*/
public FilterBuilder lte(String field, Object value) {
// ...
}
/**
* 小于或等于
*
* @see #lte(String, Object)
*/
public <T> FilterBuilder lte(SFunction<T, ?> column, Object value) {
// ...
}
/**
* 大于
*
* @param field 字段名
* @param value 字段值
*/
public FilterBuilder gt(String field, Object value) {
// ...
}
/**
* 大于
*
* @see #gt(String, Object)
*/
public <T> FilterBuilder gt(SFunction<T, ?> column, Object value) {
// ...
}
/**
* 大于或等于
*
* @param field 字段名
* @param value 字段值
*/
public FilterBuilder gte(String field, Object value) {
// ...
}
/**
* 大于或等于
*
* @see #gte(String, Object)
*/
public <T> FilterBuilder gte(SFunction<T, ?> column, Object value) {
// ...
}
/**
* 在指定取值范围之内, 左闭右闭. 即: [minValue, maxValue].
*
* <pre>
* 例如:
* // 查询年龄在 15 - 30 岁之间的用户. 包括 30 岁
* Query query = Query.newBuilder()
* .select(User.class)
* .between("age", 15, 30)
* .build();
*
* // 查询 2020 年新注册的用户数
* Query query = Query.newBuilder()
* .groupField("count(id) as count")
* .between("createTime", "2020-01-01 00:00:00", "2020-12-31 23:59:59")
* .build();
* </pre>
*
* @param field 字段名
* @param minValue 最小值. 包含最小值
* @param maxValue 最大值. 包含最大值
*/
public FilterBuilder between(String field, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 包含最小值和最大值. 即: [minValue, maxValue].
*
* @see #between(String, Object, Object)
*/
public <T> FilterBuilder between(SFunction<T, ?> column, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左开右闭. 即: (minValue, maxValue].
*
* <pre>
* 例如:
* // 查询年龄在 15 - 30 岁之间的用户. 不包括 15 岁
* Query query = Query.newBuilder()
* .select(User.class)
* .between("age", 15, 30)
* .build();
*
* // 查询 2020 年新注册的用户数
* Query query = Query.newBuilder()
* .groupField("count(id) as count")
* .between("createTime", "2020-01-01 00:00:00", "2020-12-31 23:59:59")
* .build();
* </pre>
*
* @param field 字段名
* @param minValue 最小值. 不包含最小值
* @param maxValue 最大值. 包含最大值
*/
public FilterBuilder betweenExcludeLeft(String field, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左开右闭. 即: (minValue, maxValue].
*
* @see #betweenExcludeLeft(String, Object, Object)
*/
public <T> FilterBuilder betweenExcludeLeft(SFunction<T, ?> column, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左闭右开. 即: [minValue, maxValue).
*
* <pre>
* 例如:
* // 查询年龄在 15 - 30 岁之间的用户. 不包括 30 岁
* Query query = Query.newBuilder()
* .select(User.class)
* .between("age", 15, 30)
* .build();
*
* // 查询 2020 年新注册的用户数
* Query query = Query.newBuilder()
* .groupField("count(id) as count")
* .between("createTime", "2020-01-01 00:00:00", "2020-12-31 23:59:59")
* .build();
* </pre>
*
* @param field 字段名
* @param minValue 最小值. 包含最小值
* @param maxValue 最大值. 不包含最大值
*/
public FilterBuilder betweenExcludeRight(String field, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左闭右开. 即: [minValue, maxValue).
*
* @see #betweenExcludeRight(String, Object, Object)
*/
public <T> FilterBuilder betweenExcludeRight(SFunction<T, ?> column, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左开右开. 即: (minValue, maxValue).
*
* <pre>
* 例如:
* // 查询年龄在 15 - 30 岁之间的用户. 不包括 15 和 30 岁
* Query query = Query.newBuilder()
* .select(User.class)
* .between("age", 15, 30)
* .build();
*
* // 查询 2020 年新注册的用户数
* Query query = Query.newBuilder()
* .groupField("count(id) as count")
* .between("createTime", "2020-01-01 00:00:00", "2020-12-31 23:59:59")
* .build();
* </pre>
*
* @param field 字段名
* @param minValue 最小值. 不包含最小值
* @param maxValue 最大值. 不包含最大值
*/
public FilterBuilder betweenExcludeAll(String field, Object minValue, Object maxValue) {
// ...
}
/**
* 在指定取值范围之内, 左开右开. 即: (minValue, maxValue).
*
* @see #betweenExcludeAll(String, Object, Object)
*/
public <T> FilterBuilder betweenExcludeAll(SFunction<T, ?> column, Object minValue, Object maxValue) {
// ...
}
}
select(Class)
方法, 会通过反射获取该类型中所有的字段并作为查询字段, 如果字段被 static
和 transient
修饰则会跳过. 如果字段上带有 @Field
则使用该注解定义的名称.
也可以在 select(Class)
的基础上再使用 exclude
方法排除不需要的字段.
示例
示例数据如下所示:
{
"project": {
"name": 1,
"model": 1,
"warning": {
"hasWarning": 1
}
},
"filter": {
"name": "Tom",
"fullname": {
"$regex": "la"
},
"modelId": "5c6121b9982d2073b1a828a1",
"warning": {
"hasWarning": true
},
"$or": [{
"score": {
"$gt": 70,
"$lt": 90
}
}, {
"views": {
"$gte": 1000
}
}]
},
"sort": {
"age": -1,
"posts": 1
},
"limit": 30,
"skip": 20,
"withCount": true
}
使用构造器创建上述查询条件的代码如下所示:
public class Demo {
public static void main(String[] args) {
Query query = Query.newBuilder()
.select("name", "model")
.selectSubFields("warning", "hasWarning")
.filter()
.eq("name", "Tom")
.eq("modelId", "5c6121b9982d2073b1a828a1")
.regex("fullname", "la")
.end()
.or()
.gt("score", 70)
.lt("score", 90)
.gte("views", 1000)
.end()
.orderAsc("posts").orderDesc("age")
.limit(30)
.skip(20)
.withCount()
.build();
}
}
对照图