你是否还在为设计同步数据接口烦恼呢,同步数据接口需要考虑到增量同步、全量同步、网络等原因,比如网络中断,怎么才能实现断点续传呢?有多个终端需要同步数据如何处理?......
前面我们讲解了“如何设计一个安全的RESTful API协议”,安全我们就不再赘述,今天我们来讲解如何通过一个接口实现全量、增量同步机制,支持多终端同步,同时还解决了断点续传问题,下面直接看接口定义,关注标记红色的部分;
接口全局参数及结构说明:
接口格式: | http://***/apis/{function} | |||||||||||||||
协议类型: | http/https | |||||||||||||||
协议头: | Method:post/get Content-Encoding:utf-8 Content-Type:application/json | |||||||||||||||
请求参数: | 请求参数的内容将放到http form、或者url参数中提交 | |||||||||||||||
全局入参: |
| |||||||||||||||
响应结构: | { "status" : 0, "success" : true, "msg" : "OK", "data" : object }
|
1、获取汽车品牌信息
URL:/apis/getBrands
请求参数格式:
{
"gtTime": -1,
"limit":20
}
请求参数字段说明:
参数名称 | 说明 | 是否必须 | 描述 |
gtTime | long | 必须 | 查询数据更新时间大于gtTime的数据,传入-1表示从头开始同步 |
limit | int | 必须 | 单次返回的记录数 |
响应数据格式:
{
"success": true,
"msg": "OK",
"status": 0,
"data": [
{
"id": "117",
"name": "AC Schnitzer",
"bfirstletter": "A",
"logo": "/brandimgs/0_117.jpg",
"country": "德国",
"info": "1987年创建的AC Schnitzer是全世界最大的BMW专业改装厂,虽然建厂较晚,但因为是世界最大的BMW的经销商Kohl Automobile Gmbh和Schnitzer赛车集团合作创立。在经验和销售两方面都具有全面优势(早在1964年,Schnitzer就已经开始致力于改装BMW并参加各项赛事)。",
"isdel": "0",
"updateTime": "638065594093855886"
},
{
"id": "509",
"name": "AITO",
"bfirstletter": "A",
"logo": "/brandimgs/0_509.jpg",
"country": "中国",
"info": "2021年12月,小康股份旗下的赛力斯公司在重庆两江智慧工厂发布和华为合作的高端智慧汽车品牌AITO以及赛力斯纯电驱增程平台(DE-i)。AITO旗下首款搭载最新华为鸿蒙HarmonyOS智能座舱车型问界M5已于2021年12月23日正式发布。AITO意为:Adding Intelligence to AUTO.AITO与AUTO一字之差:“I”,即 Intelligence,代表 HarmonyOS 智能座舱等创新技术能力;通过“I”的赋能,AITO 将智能带入汽车,让汽车更智慧。",
"isdel": "0",
"updateTime": "638065594093855887"
}
]
}
响应数据字段说明:
段名称 | 说明 | 描述 |
status | int | 状态码(0=成功,其他表示失败;) |
msg | string | 状态码对应的描述 |
data | [{},{}] | |
id | String | ID(品牌ID,唯一键) |
name | String | 品牌名称 |
bfirstletter | String | 首字母 |
logo | String | 品牌LOGO |
country | String | 国家 |
info | String | 品牌介绍 |
isdel | int | 是否删除(0=否,1=是) |
updateTime | long | 更新时间 |
下面是服务端示例代码片段如下:
@RequestMapping(value = "/getBrands", method = {RequestMethod.POST, RequestMethod.GET})
public JSONResponse<List<BrandInfo>> getBrands(HttpServletRequest request,
@RequestParam(value = "gtTime", defaultValue = "-1") Long gtTime,
@RequestParam(value = "limit", defaultValue = "20") Integer limit) {
try {
JSONResponse checkResult = CheckSig(request);
if (!checkResult.isSuccess()) {
return checkResult;
}
if (gtTime == null) {
gtTime = -1L;
}
if (limit == null || limit <= 0 ) {
limit = 100;
}
if (limit > 1000) {
limit = 1000;
}
List<BrandInfo> list = this.brandService.findList(gtTime, limit);
return this.success(list);
} catch (Exception ex) {
ex.printStackTrace();
return this.error(ex.getMessage());
}
}
Dao层查询数据,只需要查询updateTime字段大于传入的gtTime值即可,示例SQL如下:
select * from t_brandInfo where updateTime > $gtTime order by updateTime asc limit $limit
注意,数据表必须加入isdel、updateTime两个字段,不能执行物理删除,只能执行逻辑删除,也就是删除时只能标记为删除状态,同时将更新updateTime的值,这样不论是更新数据还是删除数据,updateTime字段都会更新到最新的时间戳,这样同步数据时只需要检查本地已同步数据的最大值传入接口即可拉取最新更新的数据了,当然需要注意查询语句,必须是order by updateTime asc哦;
下面是客户端调用同步的代码片段:
public static void main(String[] args) {
long gtTime = -1;//可以从数据库提取之前数据的最大updateTime值,用于增量同步
while (true) {
JSONResponse<List<BrandInfo>> result = getBrands(gtTime, 3);
if (!result.isSuccess()) {
System.out.println("同步任务异常终止:" + result.getMsg());
break;
}
if (result.getData().size() == 0) {
System.out.println("无数据返回,同步任务正常终止:" + result.getMsg());
break;
}
//这里写保存入库的逻辑
gtTime=result.getData().stream().max(Comparator.comparing(BrandInfo::getUpdateTime)).get().getUpdateTime();//提取本次同步的最大updateTime
}
}
private final static String appid = "1001";
private final static String secretkey = "********";
private final static String apihost = "http://***";
/**
* 获取品牌
*/
public static JSONResponse<List<BrandInfo>> getBrands(long gtTime, int limit) {
try {
Map<String, Object> parms = new HashMap<String, Object>();
parms.put("gtTime", gtTime);
parms.put("limit", limit);
String content = httpGet("getBrands", parms) ;
return JSON.parseObject(content, new TypeReference<JSONResponse<List<BrandInfo>>>() {
});
} catch (Exception ex) {
ex.printStackTrace();
return JSONResponse.Create(false, ex.getMessage(), null, 501);
}
}
/**
* 构建带签名的请求URL
*
* @param function 请求的接口方法
* @param parms 接口对应的参数
* @return
*/
private static String httpGet(String function, Map<String, Object> parms) {
long timestamp = System.currentTimeMillis();
String sig = SecureUtil.md5(appid + "#" + timestamp + "#" + secretkey);
StringBuilder urlbuilder = new StringBuilder(apihost).append("/apis/").append(function).append("?appid=").append(appid).append("&sig=").append(sig).append("×tamp=").append(timestamp);
for (String key : parms.keySet()) {
urlbuilder = urlbuilder.append("&").append(key).append("=").append(parms.get(key));
}
String url = urlbuilder.toString();
String content = HttpUtil.get(url);
return content;
}
同步逻辑:
1、首先查询本地数据库最大的updateTime,如果数据库无记录则计为-1;
2、将查询到的最大updateTime值传入gtTime;
3、当有数据返回时,获取返回记录中最大的updateTime值,传入下一次查询gtTime,直到无记录返回,则同步完成;
至此一个同时支持全量、增量数据同步,支持多终端的数据同步,同时还支持断点续传的接口就完成了。