一、协议规范
1.1 请求
- 当请求中没有二进制数据时。
json格式数据 - 当请求中含有二进制数据时。
4+json格式数据+二进制数据+20字节的二进制数据SHA1 - 每个请求中都有version、nonce字段
字段 类型 version string app正使用的Rose版本。示例:1.0.9-20210516。server内部有一个能支持的最低Rose版本。一旦app的Rose版本低于那个版本,应答返回-2(app版本太低,server不支持)。如果version不是x.x.x-xxxxxxxx(x全是数字)格式, 报ParamError。 nonce int 一个整型随机数。server的应答也须有这同名、同值字段。以便app通过它们判断这是一对请求、应答
1.2 应答
字段 | 类型 | |
code | int | 成功、失败时都必须包含。值0表示成功,负数表示失败。不能出现正数。 |
msg | string | 成功、失败时都必须包含。用英文描述的处理结果。 |
nonce | int | 成功、失败时都必须包含。值等于client请求中的nonce(一个整型随机数)。 |
results | object | 在成功时必须包含,失败时则禁止出现。服务端中的被调用方法决定了该成员的值。 |
失败示例
{"code":-7,"msg":"password error", "nonce": 13778}
1.3 code值
enum | code | |
Success | 0 | 成功 |
ParamError | -1 | 参数(字段)值错误 |
ClientVersionTooLow | -2 | app版本太低,server不支持 |
InvalidDeiveSession | -5 | http sessionid错误 |
UserNotExisted | -6 | 用户不存在 |
PasswordError | -7 | 密码错误 |
SignError | -10 | SHA1签名不匹配 |
FileNotExisted | -11 | 文件不存在 |
RequestNotSupported | -15 | server不支持该命令 |
AuthorizationError | -16 | 该用户存在,但没有授权访问 |
如果错误情形不在上面的,可自加一个负数的code值。
二、命令
2.1 下载小程序
- 请求URL。
http://ip:port/cswamp/device/getapplet - 请求方式。
POST - 不需要已登录
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
type | yes | string | distribution/development。distribution:Applet Store中的小程序。development:个人中心/小程序中的小程序,如果Applet Store中已经是最新的,返回错误FileNotExisted(-11)。 |
bundleid | yes | string | 该小程序绑定的Bundle ID。 示例:aplt.leagor.blesmart |
应答
字节数 | ||
json长度 | 4 | 后面json数据字节数(大端序) |
json数据 | 见底下json数据 | |
二进制数据块 | rsp的文件长度 | *.rsp文件 |
SHA1签名 | 20 | 二进制数据块的SHA1签名 |
json数据(results)
参数 | 必选 | 类型 | 说明 |
name | yes | string | 小程序名称 |
title | yes | string | 小程序名称(留它是为和以前版本兼容,过段时间再删) |
subtitle | yes | string | 小程序副标题 |
time | yes | timestamp | 秒。distribution:小程序在商店开售时间。development:小程序上传时间 |
username | yes | string | 小程序开发者 |
rspfsize | yes | int | 二进制数据中*.rsp字节数 |
app | yes | string | 该小程序能运行在的app。语义等同findapplet应答中的app字段。 |
2.2 搜索小程序
- 请求URL。
http://ip:port/cswamp/device/findapplet - 请求方式。
POST - 需要/不需要已登录
只在Applet Store中搜索。
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | no | string | login时返回的sessionid |
app | no | string | 值:launcher或kdesktop。1)sessionid不空时,要得该人名下和该app关联的、该人的名下小程序。2)sessionid空时,app可以是空,如果不空,搜索条件上小程序还需能在该app运行,并且client最多只会传一个,即不是launcher就是kdesktop。 |
maxapplets | no | int | 指示只返回最前面的maxapplets个小程序。maxapplets不在[1, 30]范围时,强制认为30。 |
bundleids | no | string | 小程序Bundle ID,多个时用逗号分隔。 |
name | no | string | 小程序名称。一旦不为空,名称中需含有name字符串。 |
依sessionid是否为空,分两情况。
- sessionid不空时,sessionid必须是一个正有效的sessionid,此时app不能为空,忽略maxapplets、bundleids、name。目的是得到该用户名下、该类app的小程序。什么是该用户名下、某类app的小程序,见“2.5 同步小程序列表”。
- sessionid为空时,此时须检查app、maxapplets、bundleids、name。bundleds、name是两个条件,当中至少有一个不能为空,当两个都不空时,总条件是逻辑与。bundleids不为空时,返回那些小程序。如果此时name也不为空,这些小程序名称都需要含有name字符串。如果app同时不为空,查到的小程序还需要能在这app运行。(旧版本:sessionid为空时,此时须检查maxapplets、bundleids、name,忽略app。bundleds、name是两个条件,当中至少有一个不能为空,当两个都不空时,总条件是逻辑与。bundleids不为空时,返回那些小程序。如果此时name也不为空,这些小程序名称都需要含有name字符串。)
应答(json数据(results))
count:实际搜索到满足条件的小程序数目。
{ "code": 0, "msg": "success", "nonce": 13778, "results": { "count": 34 "applets": [{ "bundleid": "aplt.leagor.blesmart", "name": "BLE Smart", "subtitle": "BLE测试工具", "time": 1622509363, "username": "兰栖科技", "rspfsize": 15949608, "version": "1.0.2-20210131" …… }] } }
参数 | 必选 | 类型 | 说明 |
bundleid | yes | string | 小程序bundleid |
name | yes | string | 小程序名称 |
subtitle | yes | string | 小程序副标题 |
time | yes | timestamp | 秒。distribution:小程序在商店开售时间。 |
username | yes | string | 小程序开发者 |
rspfsize | yes | int | 该小程序*.rsp的文件字节数 |
version | yes | string | 该小程序版本。示例:1.0.9-20210516 |
roseversion | yes | string | 该小程序基于的Rose版本。示例:1.0.8 |
app | yes | string | 该小程序能运行在的app,多个时逗号隔开。示例:launcher,kdesktop |
2.3 上传文件
- 请求URL
http://ip:port/cswamp/device/uploadfile - 请求方式
POST - 需要登录/不需要已登录
请求
字节数 | ||
json长度 | 4 | 后面json数据字节数(大端序) |
json数据 | 见底下json数据 | |
二进制数据块 | json块中size值 | 文件内容 |
SHA1签名 | 20 | 二进制数据块的SHA1签名 |
json数据
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求”。注1。 |
sessionid | opt | string | 须要登录后才能上传的文件时,必须提供。login时返回的sessionid |
file | yes | string | 文件。注2 |
offset | yes | int | 此次要传输的开始偏移。注3 |
size | yes | int | 此次要上传的字节数 |
end | yes | bool | true:后面已没有文件数据,可关闭。false:后面还会有该文件数据。注4 |
注1。除公用功能之外,此命令中的nonce还一个作用:传输同一个文件时,会用同一个noce值。如果nonce不同,即使是offset都接上了,那也不能认为是一个文件。
注2。file指示要传什么文件。文件可能是哪平台下哪app的升级包。后面还会有用户地图、定时任务、小程序等。
file | sessionid | 功能说明 | 文件说明 |
launcher.android | 不须要 | android平台launcher升级包 | *.rsp。Tag3-L4=2,android apk |
kdesktop.android | 不须要 | android平台kdesktop升级包 | *.rsp。Tag3-L4=2,android apk |
注3。offset值是0时,先把同名文件清空,打开,写入数据。非0时,要同时满足三个条件才算正确:同名文件需存在、nonce和之前一样、已有长度是offset,一旦有一个不满足,不必等待,返回一个用response_code指示错误的应答。
注4。server一旦处理完一个end=false请求,溢出时间设为30秒,如果30秒后没有收到下一个uploadfile,认为上传此次上传文件失败。
应答(results)
参数 | 必选 | 类型 | 说明 |
2.4 下载文件
- 请求URL
http://ip:port/cswamp/device/downloadfile - 请求方式
POST
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | opt | string | 须要登录后才能上传的文件时,必须提供。login时返回的sessionid |
file | yes | string | 文件。见“2.3 上传文件”中的file |
offset | yes | int | 此次要传输的开始偏移。 |
size | yes | int | 此次要传输的字节数 |
应答
字节数 | ||
json长度 | 4 | 后面json数据字节数(大端序) |
json数据 | 见底下json数据 | |
二进制数据块 | <=req.size | 当文件中字节小于请求中的size时,只返回实际剩余字节 |
SHA1签名 | 20 | 二进制数据块的SHA1签名 |
json数据(results object)
参数 | 必选 | 类型 | 说明 |
fsize | yes | int | 此文件长度。不是请求中的size |
req.offset == fsize时,不必传“二进制数据块”和SHA1签名,但json数据还是按成功时填充。
如果server没法传输数据,像该文件不存在,参数req.offset超过文件长度等,不必传“二进制数据块”和SHA1签名,json部分指示错误。
2.5 登录
- 请求URL
http://ip:port/cswamp/device/login - 请求方式
POST - 需要/不需要已登录
设备登录。
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
type | yes | string | password/cookie |
username | yes | string | 用户名 |
password | yes | string | password:密码。cookie:上次login时返回的pwcookie |
deviceid | yes | string | 设备唯一标识。值是32个字符的uuid。可以这么认为,只要是同一台设备,这个值总是一样的。 |
devicename | yes | string | 登录时设备易记名。示例:iOS(kDesktop)、Android设备。cswamp不解析,原样存储。 |
登录示例。用户名:test,密码:123456。
- [机器人]test在机器人(launcher)手工输入密码的方式登录。发向cswamp的login请求中,deviceid:a11ee49e9fe14e72a295e94d3263af73,devicename:launcher(Android),type:password。
- [cswamp]收到这个请求,判断password是否正确。一旦正确,生成两个uuid,一个是sessionid,一个是pwcookie。同时生成一条以a11ee49e9fe14e72a295e94d3263af73(deviceid)为key的登录记录。
- [机器人]收到应答,把pwcookie存储在本地配置文件preferences,它将作为后绪cookie方式时的登录密码。
- [机器人]发生重启。
- [机器人]自动登录。发向cswamp的login请求中,deviceid:a11ee49e9fe14e72a295e94d3263af73,devicename:launcher(Android),type:cookie,password:填preferences文件中的pwcookie,即上次login收到的pwcookie。
- [cswamp]收到这个请求,以a11ee49e9fe14e72a295e94d3263af73(deviceid)为key在登录记录表中搜索,发现表中有以收到的pwcookie为key的记录,认为正确,可以登录。并生成两个uuid,一个是sessionid,一是个pwcookie。同时修改这条以a11ee49e9fe14e72a295e94d3263af73(deviceid)为key的登录记录,像pwcookie、最后登录时间。
- [iPhone]test在控制端手机(kdesktop)手工输入密码的方式登录。发向cswamp的login请求中,deviceid:3cc51905c1a64d8a89fb981f6ea35d86,devicename:kdesktop(iOS),type:password。
- [cswamp]收到这个请求,判断password是否正确,并生成两个uuid,一个是sessionid,一是个pwcookie。同时生成一条以3cc51905c1a64d8a89fb981f6ea35d86(deviceid)为key的登录记录。——至此,test这个用户已有两条登录记录,而且这两条是同时有效,即这两个sessionid要同时有效。
登录这么复杂,主要是两个原因。
- 为安全,设备端禁止存储密码到文件。为解决这问题,自动登录改用pwcookie。由于pwcookie和密码没任何关系,即使别人拿到它也猜不出密码。为更安全,每次login后,pwcookie都要变一次。这样pwcookie真泄露了,本人很快能通过login操作让泄露的pwcookie失效。
- 同一个用户名会同时在多个设备上正登录着。而且同时登录,会是个普遍现象。
cswamp在“个人中心”——“App”页,提供显示、清除登录记录功能。需要这功能,有三个原因。
- 设备和deviceid之间不是真的唯一。deviceid即使和某一硬件绑定,像网卡MAC地址,也会发生维修换了网卡。Rose生成devcieid没关联硬件,是判断配置文件pereferences是否存在。不存在时,用操作系统api生成一个uuid。由于只要不卸载app,pereferences就会一直存在,deviceid也就一直不变了。但是,万一app卸载又重装,这设备就会生成新uuid,针对这设备,cswamp的登录记录表就有了两个deviceid。
- deviceid+pwcookie可能泄露。泄露会造成何样影响分两种情况。第一种,原用户test继续用该deviceid。这时test的login操作会自动使泄露的deviceid+pwcookie无效,相对来说恶果要小些。第二种,test不再用该deviceid。这时泄露的deviceid+pwcookie将一直有效。别人就可用它持续获得test私有信息,甚至篡改数据。
- 用户一旦发现数据有异常,通过查看登录记录,可判断是否有人入侵过,并自行清除。一旦清除所有登录记录,意味着设备都要进行一次密码登录。
在“App”页,同时可查看本用户名下的launcher、kdesktop类的小程序列表。
应答(json数据(results))
参数 | 必选 | 类型 | 说明 |
sessionid | yes | string | cswample生成的、唯一指示此次登录的sessionid |
pwcookie | yes | string | app后绪以cookie方式登录时,填写的password |
2.6 退出登录
- 请求URL
http://ip:port/cswamp/device/logout - 请求方式
POST - 需要已登录
退出登录。cswamp在登录记录表中删除此次deviceid为key的记录。
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string |
应答(json数据(results))
参数 | 必选 | 类型 | 说明 |
2.7 同步小程序列表
- 请求URL
http://ip:port/cswamp/device/syncappletlist - 请求方式
POST - 需要已登录
向server上的小程序列表添加、删除小程序。对每种app,server需各维护一个列表。目前两种app,字符串标识分别是launcher、kdesktop,应而需维护两个小程序列表。
为什么每种app各需一个?假设aplt.leagor.pan是个机器人底盘驱动小程序,launcher(机器人)会安装,(kdesktop)iphone控制端则不会,安装了也用不了。而这两个设备可能登录的是同一个用户名test。于是test在机器人登录时,那它下的launcher列表将包含aplt.leagor.pan这个小程序;iphone上登录时,下的kdesktop列表则不包含它。
请求
参数 | 必选 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string | |
app | yes | string | launcher或kdesktop |
add | no | string | 要添加的小程序bundleid。是多个时,中间用逗号隔开 |
remove | no | string | 要移除的小程序bundleid。是多个时,中间用逗号隔开 |
add、remove可能都为空,此时client只是想快速得到该人、该app下小程序的bundleid列表。
应答(json数据(results))
参数 | 必选 | 类型 | 说明 |
applet | yes | string | 经过此次操作后,该人、该app下、且正存在于商店的小程序bundleid,bundleid之间用逗号隔开。client可能add了错误bundleid、该bundleid可能已下架,要剔除 |
2.8 添加事件
事件是什么?设想下机器人在超市巡检,发现商品摆放异位,为提醒,要把“现场图像加文字描述”发到管理员手机,这里的现场图像+文字描述就是事件。在内容上,事件就是图片加一句文字描述。在一事件,可能有多张图像。
在判断事件是否有效上,机器人名称(devicename)不能超过48字节,文字描述(desc)不能超过512字节,总图像字节数不能超过5M。
在cswamp,事件最多存储两天,即48小时。(第二阶段,在个人中心有个“事件”入口,第一步先可以看到已有事件数)。
- 请求URL
http://ip:port/cswamp/device/addevent - 请求方式
POST - 需要登录
请求
字节数 | ||
json长度 | 4 | 后面json数据字节数(大端序) |
json数据 | 最多1M字节 | 见底下json数据 |
二进制数据块 | json块中imagesize值 | 图像内容 |
SHA1签名 | 20/0(图像内容是0字节,即没有二进数据块) | 二进制数据块的SHA1签名 |
json数据
字段 | 必填 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string | 须要登录后才能添加事件。login时返回的sessionid |
time | yes | timestamp | 事件发生的时间 |
devicename | yes | string | 产生事件的机器人名称。一旦超过48字节,认为此个事件无效,返回错误 |
desc | yes | string | 事件关联的描述性文字。一旦超过512字节,认为此个事件无效,返回错误 |
imagesize | yes | int | 事件关联图像的总字节数。也是二进制数据块字节数。字节数超过5M,认为此个事件无效,返回错误。 |
imageformat | yes | string | 图像格式。目前只会两种:png(png格式)、jpeg(jpeg格式)。此事件所有图像会用同一种格式。当imagesize=0时,也会传。 |
images | yes | array | 事件关联图像。当imagesize=0时,也会传,值是空数组,即“[]”。 |
{ "version": "1.0.9-20241112", "nonce": 32, "sessionid": "dc8d64d546d36b8d8413c2cb81552f18" "time": 1525827441 "devicename": "益民超市" "desc": "商品摆放异位" "imageformat": "jpeg" "imagesize": 266708 "images": [{ "desc": "A区", "offset": 0, "size": 110932, } { "desc": "B区", "offset": 110923, "size": 155785, }] }
字段 | 必填 | 类型 | 说明 |
desc | yes | string | 此个图像相关描述,像发生位置。一旦超过512字节,认为此个事件无效,返回错误 |
offset | yes | int | 此个图像在二进制数据区块内的偏移 |
size | yes | int | 此个图像数据的字节数。 |
应答(json数据(results))
字段 | 必填 | 类型 | 说明 |
2.9 心跳
- 请求URL
http://ip:port/cswamp/device/keepalive - 请求方式
POST - 需要登录
机器人把事件发向cswamp,那手机如何从cswamp得到事件。如果要做到手机即时获取,要估计要用上稍微复杂方法。前期为可以使用,用上种“心跳+获取事件”。实现逻辑大致是这样的。
- (手机)每隔N秒,像10秒,向cswamp发心跳(keepalive)。
- (cswamp)收到心跳请求,返回该用户最新那个事件的发生时间,存储在event_time。
- (手机)比较收到的event_time,发现本地已收到的最后事件时间比这个小,认为有发生新的事件,于是向cswamp发获取事件(getevent),并把本地最后一个事件时间放在event_time字段。
- (cswamp)收到获取事件请求,把大于event_time的事件放到应答,返回给手机。
请求
字段 | 必填 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string | 须要登录后才能发心跳。login时返回的sessionid |
应答(json数据(results))
字段 | 必填 | 类型 | 说明 |
event_time | yes | timestamp | 该用户最新那个事件的发生时间 |
2.10 获取事件
- 请求URL
http://ip:port/cswamp/device/getevent - 请求方式
POST - 需要登录
cswamp判断下来,可能发现要回传好多事件,这时返回多少事件,要满足两个要求。
- 最多返回20条。
- 总计图像部分字节数不能超过5M字节。
请求
字段 | 必填 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string | 须要登录后才能获取事件。login时返回的sessionid |
event_time | yes | timestamp | 得到比这个时间大的那些事件。一般是存储本地的最后那个事件的发生时间。 |
应答
字节数 | ||
json长度 | 4 | 后面json数据字节数(大端序) |
json数据 | 见底下json数据 | |
二进制数据块 | json块中imagesize值 | 图像内容 |
SHA1签名 | 20/0(图像内容是0字节,即没有二进数据块) | 二进制数据块的SHA1签名 |
应答(json数据(results))
字段 | 必填 | 类型 | 说明 |
count | yes | int | 实际搜索到满足条件的事件数目 |
imagesize | yes | int | 所有事件关联图像的总字节数。也是二进制数据块字节数。 |
events | yes | array | 事件数组。注1 |
注1。返回的事件(events)填写离请求给的时间(event_time)最近的那几条。举个例子,请求中event_time是10:00:00,现在已是20:00:00,中间有100条事件。距离10:00:00,按时间排序依次是10:00:01、10:00:10、10:00:37、10:00:59……。对图像字节数,只要01、10、37这三条就超过5M了,那events只填01、10这两条,同时count置为100。在手机端,收到此次应答后,看到count是100,而events只有两条,那认为至少还有98条未接收。它就会发出下一个getevent,此个请求中的event_time会置为上次收到、最后一条的10:00:10。
{ "code": 0, "msg": "success", "nonce": 13778, "results": { "count": 34 "imagesize": 366719 "events": [{ "time": 1525827441 "devicename": "益民超市" "desc": "商品摆放异位", "images": [{ "desc": "A区", "offset": 0, "size": 110932, } { "desc": "B区", "offset": 110923, "size": 155785, }] } { …… }] } }
events中每个事件字段:time、desc、images,相关说明见“2.8 添加事件”。因为事件关联图像字节数已放在根下的imagesize,单个event的就不须要imagesize了。
2.11 问询上菜进度
- 请求URL
http://ip:port/cswamp/device/querytablecooking - 请求方式
POST - 需要登录

- 顾客用人工点菜、或小程序点菜,点菜结果送到cswamp服务器。
- 烧菜中。工作人员根据进度,在cswamp提供的界面,修改各菜进度。
- 在餐厅,顾客按下餐桌铃,或语音,问询指定餐桌的上菜进度。机器人收到这问询后,向cswamp发问询请求,cswmap返回结果。
请求
参数 | 必填 | 类型 | 说明 |
version | yes | string | 见“1.1 请求” |
nonce | yes | int | 见“1.1 请求” |
sessionid | yes | string | 须要登录后才能问询上菜进度。login时返回的sessionid |
table | yes | int | 餐桌索引。从0开始。 |
机器人通过“table”字段告知此次要查询的是哪张餐桌。在机器人端,会让用户维护一个餐桌数组。在生成这个数组时,不会给让设置索引号,而是最先出现是0,后面逐个增1。用户命名餐桌时,可能是从1开始,像“1号桌”、“2号桌”,“1号包厢”,“2号包厢”,这里从0开始,是为和C/Java的数组索引从0开始,保持一致。
同时,在服务器端,用户也会有个餐桌数组,这个索引号就作为这个数组索引。或这或那原因,可能出现机器人、服务器对同一索引餐桌,写着不同名情况,这时机器人说餐桌名时以服务器上的为准,所以此命令的应答要有个餐桌名。
{ "version": "1.0.9-20241112", "nonce": 32, "sessionid": "dc8d64d546d36b8d8413c2cb81552f18" "table": 3 }
上面这请求问询索引3餐桌的上菜进度,即餐桌数组中第4张餐桌。
应答(json数据(results))
参数 | 必填 | 类型 | 说明 |
table_name | yes | string | 餐桌名称。如果请求要求的table不存在,填空。 |
total_amount | no | double | 总金额。若该餐桌处于空闲状态,置0。若餐桌不存在,不发送 |
order_time | no | timestamp | 下单时间。若该餐桌处于空闲状态,置0。若餐桌不存在,不发送 |
cooking | no | array | 还未上桌的菜。如果没有,可以不发,或发一个空数组 |
done | no | array | 已上桌的菜。如果没有,可以不发,或发一个空数组 |
如果请求的餐桌不存在,像"table"超过了服务器上餐桌数量,应答不要返回是个错误,而只是把table_name置空。
cooking(未上桌菜)
参数 | 必填 | 类型 | 说明 |
name | yes | string | 菜名 |
count | yes | int | 该菜还没上桌份数 |
time | yes | int | 预计还有多少时间上菜。单位秒。如果有多份,取最快那份的剩余时间 |
done(已上桌菜)
参数 | 必填 | 类型 | 说明 |
name | yes | string | 菜名 |
count | yes | int | 该菜已上桌份数 |
{ "code": 0, "msg": "success", "nonce": 32 "results": { "table_name": "4号桌", "total_amount": 56.7 "order_time": 1525827441 "cooking": [{ name: "椒盐虾", time: 60, count: 1, } { name: "干锅花菜", time: 240, count: 1, }] "done": [{ name: "毛豆", count: 2, } { name: "麻婆豆腐", count: 1, }] } }
浮点数用double类型。