[关闭]
@chenpbh 2018-12-13T09:00:09.000000Z 字数 12247 阅读 391

新源开放数据外部接口-企业 1.6

接口文档


变更历史

版本 日期 人员 变更内容
1.0 2016-11-30 陈鹏 版本初定义
1.1 2016-12-01 陈鹏 实时数据接口路径改动
1.2 2016-12-14 陈鹏 增加历史数据接口
1.3 2018-04-14 陈鹏 增加故障推送支持
1.4 2018-08-13 陈鹏 完善加解密和签名部分
1.5 2018-10-30 陈鹏 增加【2.2 通过VIN查询报文】
1.6 2018-12-13 陈鹏 1、调整目录结构 2、原报文查询接口标志为已过期 3、增加【2.1.5 报文查询】4、规范接口路径格式

1接入指南

1.1 数据传输规范

1.1.1 数据传输接口

所有数据接口均采用HTTP(S)协议,每个接口的URL均采用以下格式:
http(s)://[地址]/接口名称

  • 地址:接口服务所属域名或ip
  • 接口名称:所调用接口的名称,具体接口名称见各业务接口描述文档

1.1.2 接口调用方式

1、统一采用Http Post请求,采用JSON方式;
2、传输过程中应包含请求头和消息体;
3、接口请求时,要求将TOKEN存放到请求头中,供双方验证。
4、消息体转换为JSON字符串进行传输

1.1.3 请求头规范

属性 描述
Content-type application/json;charset=UTF-8 json传输
Authorization bearer token bearer预留,可以任意填,其中token为上面提供的字符串,bearer和token之间留空格。示例:bearer adc347c5667aeeafaf34556c

1.1.4 消息体规范

参数 是否必填 类型 说明
requestMsg true String 请求内容体,详细描述见 数据加解密
timestamp true long 时间戳,毫秒数 System.currentTimeMillis()
sign true String 签名值,详细描述见 签名规范

1.1.5 响应体

参数 是否必填 类型 说明
code true int 响应码,详细描述见 响应码定义
msg true String 响应提示
data true String 查询结果,传输的是经过AES加密后的字符串,接收方需要解密,解密算法见 解密算法

1.2 密钥的管理和使用

1.2.1 密钥分类

每个用户交互前需要申请分配 消息密钥、认证标识、签名密钥:

  • 消息密钥(AES_KEY): 用于所有接口中业务数据加密
  • 认证标识(TOKEN): 用于身份认证
  • 签名密钥(SIGN_SECRET): 用于数据签名,防止数据被截取篡改
    其中TOKEN需要在请求中传递,其他两项妥善保管不允许在请求中传递

1.2.2 密钥产生

密钥具备随机产生特性,弱密钥会被剔除,所有密钥均由接口管理平台生成。

1.2.3 密钥分发

密钥分发由安全方式进行,通过线下分发,邮件等方式。

1.2.4 密钥使用

1.2.4.1 加解密处理

  • 消息发送方对业务数据利用消息密钥(AES_KEY)进行加密;
  • 消息接收方根据消息密钥(AES_KEY)对消息体中的业务数据解密,校验参数合法性等后续业务处理,具体加解密方法和示例见 数据加解密

1.2.4.2 签名处理

签名采用HMAC-MD5算法,利用MD5作为散列函数,通过签名密钥(SIGN_SECRET)对整个消息主体进行签名。
具体签名方法和示例见 签名规范

1.3 数据加解密

1.3.1 加解密方法

数据传输加解密采用对称加解密AES-128算法,对业务数据进行加密传输。

1.3.2 加解密示例

requestMsg为请求内容体转化json之后,再进行AES加密得到的字符串,不同的aes-key加密得到的结果不一样,加密算法见 加密算法,示例:

  1. //原始的请求内容体
  2. {"dataIds":"2615,2202","vins":"LNBSCB3F0FD120736,LNBSCB3F1FD120728"}
  3. // 加密后的内容体
  4. "requestMsg": "xo6+oObvQ071Qw0U9pZn/+QOJsQYcX4YJGKH+Q1j/gdpKaDseLWzM3mOsT9LdYWKgB/8DcZSynck2s5Xi504fGWxpOM1Cvd8Ne7T6/2r0eU="

1.3.3 加密算法

  1. public static String encrypt(String content) {
  2. try {
  3. if (StringUtil.isEmpty(content))
  4. return "";
  5. String password = AES_KEY;
  6. KeyGenerator kgen = KeyGenerator.getInstance("AES");
  7. SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
  8. secureRandom.setSeed(password.getBytes());
  9. kgen.init(128, secureRandom);
  10. SecretKey secretKey = kgen.generateKey();
  11. byte[] enCodeFormat = secretKey.getEncoded();
  12. SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
  13. Cipher cipher = Cipher.getInstance("AES");// 创建密码器
  14. byte[] byteContent = content.getBytes("utf-8");
  15. cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
  16. byte[] result = cipher.doFinal(byteContent);
  17. String str = Base64.encode(result);
  18. return str; // 加密
  19. } catch (NoSuchAlgorithmException e) {
  20. // e.printStackTrace();
  21. } catch (NoSuchPaddingException e) {
  22. // e.printStackTrace();
  23. } catch (InvalidKeyException e) {
  24. // e.printStackTrace();
  25. } catch (UnsupportedEncodingException e) {
  26. // e.printStackTrace();
  27. } catch (IllegalBlockSizeException e) {
  28. // e.printStackTrace();
  29. } catch (BadPaddingException e) {
  30. // e.printStackTrace();
  31. }
  32. return null;
  33. }

1.3.4 解密算法

  1. /**
  2. * 解密
  3. * @param str 待解密内容
  4. * @return
  5. */
  6. public static String decrypt(String str) {
  7. try {
  8. String password = AES_KEY;
  9. byte[] content = Base64.decode(str);
  10. KeyGenerator kgen = KeyGenerator.getInstance("AES");
  11. SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
  12. secureRandom.setSeed(password.getBytes());
  13. kgen.init(128, secureRandom);
  14. SecretKey secretKey = kgen.generateKey();
  15. byte[] enCodeFormat = secretKey.getEncoded();
  16. SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
  17. Cipher cipher = Cipher.getInstance("AES");// 创建密码器
  18. cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
  19. byte[] result = cipher.doFinal(content);
  20. return new String(result); // 加密
  21. } catch (NoSuchAlgorithmException e) {
  22. // e.printStackTrace();
  23. } catch (NoSuchPaddingException e) {
  24. // e.printStackTrace();
  25. } catch (InvalidKeyException e) {
  26. // e.printStackTrace();
  27. } catch (IllegalBlockSizeException e) {
  28. // e.printStackTrace();
  29. } catch (BadPaddingException e) {
  30. // e.printStackTrace();
  31. }catch (Exception e){
  32. // e.printStackTrace();
  33. }
  34. return "";
  35. }

1.4 签名规范

1.4.1 签名方式

签名采用HMAC-MD5算法,签名流程:

  1. 在密钥key后面添加0来创建一个长为B(64字节)的字符串(str);
  2. 将上一步生成的字符串(str) 与ipad(0x36)做异或运算,形成结果字符串(istr)
  3. 将数据流data附加到第二步的结果字符串(istr)的末尾
  4. 做md5运算于第三步生成的数据流(istr)
  5. 将第一步生成的字符串(str) 与opad(0x5c)做异或运算,形成结果字符串(ostr)
  6. 再将第四步的结果(istr) 附加到第五步的结果字符串(ostr)的末尾
  7. 做md5运算于第6步生成的数据流(ostr),最终输出结果(out)

1.4.2 数据签名示例

  1. requestMsg 和 timestamp 参数名和参数值拼接成一个字符串A
  2. 用字符串A和签名密钥(SIGN_SECRET)调用签名算法,生成签名值,签名算法见 签名算法

示例:

  1. // 业务参数加密后数据
  2. "requestMsg":"xo6+oObvQ071Qw0U9pZn/+QOJsQYcX4YJGKH+Q1j/gdpKaDseLWzM3mOsT9LdYWKgB/8DcZSynck2s5Xi504fGWxpOM1Cvd8Ne7T6/2r0eU="
  3. // 时间戳
  4. "timestamp"=1508983789981
  5. // 拼接后
  6. String input = "requestMsgxo6+oObvQ071Qw0U9pZn/+QOJsQYcX4YJGKH+Q1j/gdpKaDseLWzM3mOsT9LdYWKgB/8DcZSynck2s5Xi504fGWxpOM1Cvd8Ne7T6/2r0eU=timestamp1508983789981"
  7. // 假设SIGN_SECRET为 "test" 调用签名算法
  8. HmacUtil.byteArrayToHexString(HmacUtil.encryptHMAC(input.getBytes(), "test"))
  9. // 签名结果为
  10. abc48a4267d64921a55c30b33abd18c1

1.4.3 签名算法

  1. private static final String KEY_MAC = "HmacMD5";
  2. /**
  3. * HMAC加密
  4. *
  5. * @param data 数据
  6. * @param key 秘钥
  7. * @return 签名结果
  8. */
  9. public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
  10. SecretKey secretKey = new SecretKeySpec(key.getBytes(), KEY_MAC);
  11. Mac mac = Mac.getInstance(secretKey.getAlgorithm());
  12. mac.init(secretKey);
  13. return mac.doFinal(data);
  14. }
  15. /**
  16. * byte数组转换为HexString
  17. */
  18. public static String byteArrayToHexString(byte[] bytes) {
  19. StringBuilder sb = new StringBuilder(bytes.length * 2);
  20. for (byte aByte : bytes) {
  21. int v = aByte & 0xff;
  22. if (v < 16) {
  23. sb.append('0');
  24. }
  25. sb.append(Integer.toHexString(v));
  26. }
  27. return sb.toString();
  28. }
  29. public static void main(String[] args) throws Exception {
  30. String inputStr = "{\"somek\":\"somev\"}";
  31. byte[] inputData = inputStr.getBytes();
  32. String key = "somekey";
  33. System.out.println(HmacUtil.byteArrayToHexString(HmacUtil.encryptHMAC(inputData, key)));
  34. }

2. 接口列表

2.1 车辆动态相关

2.1.1 实时数据

发起请求,应将参数的requestMsg进行加密,然后组合成完整的请求参数。

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值,暂时留空,为以后扩展

请求内容体定义

参数 是否必填 类型 说明
vins true String VIN列表,用逗号分隔
dataIds true String 数据项ID,用逗号分隔
  1. {
  2. "requestMsg": "xo6+oObvQ071Qw0U9pZn/+QOJsQYcX4YJGKH+Q1j/gdpKaDseLWzM3mOsT9LdYWKgB/8DcZSynck2s5Xi504fGWxpOM1Cvd8Ne7T6/2r0eU=", //这里的requestMsg为请求内容体转化json之后,再进行AES加密得到的字符串,不同的aes-key加密得到的结果不一样
  3. "sign": ""
  4. }
  5. //原始的请求内容体
  6. {"dataIds":"2615,2202","vins":"LNBSCB3F0FD120736,LNBSCB3F1FD120728"}
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true String 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "查询成功",
  4. "data": {
  5. "vin1": {
  6. "2502": "40.111",
  7. "2503": "120.0"
  8. },
  9. "vin2": {
  10. "2502": "40.111",
  11. "2503": "120.5"
  12. },
  13. "vin3": {
  14. "2502": "40.111",
  15. "2503": "120.5"
  16. }
  17. }
  18. }

2.1.2 历史数据

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值,暂时留空,为以后扩展

请求内容体定义

参数 是否必填 类型 说明
vins true String 车辆vin
dataIds true String 数据项ID,用逗号分隔
startTime true String 开始时间,精确到秒
endTime true String 结束时间,精确到秒
  1. {
  2. "dataIds":"2502,2503",
  3. "vins":"LNBSCB3F0FD120736",
  4. "startTime":"2016-10-10 10:22:00",
  5. "endTime":"2016-10-10 10:23:00",
  6. }
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true String 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "查询成功",
  4. "data": {
  5. "total": "3",
  6. "rows":[
  7. {"time":"2016-10-10:22:00","2502":"11.00000","2503":"22.00"},
  8. {"time":"2016-10-10:22:00","2502":"11.00000","2503":"22.00"},
  9. {"time":"2016-10-10:22:00","2502":"11.00000","2503":"22.00"},
  10. ]
  11. }
  12. }

2.1.3 车辆静态数据

发起请求,应将参数的requestMsg进行加密,然后组合成完整的请求参数。

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值,暂时留空,为以后扩展

请求内容体定义

参数 是否必填 类型 说明
vins true String VIN列表,用逗号分隔
  1. //原始请求内容体
  2. {
  3. "size":2,
  4. "rows": ["LNBSCB3F0FD120736","LNBSCB3F1FD120728" ]
  5. }
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true object 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "OK",
  4. "rows": {
  5. "LNBSCB3F0FD120736":{
  6. "licensePlate":"京A12346", //车牌号
  7. "vin":"LNBSCB3F0FD120736", //VIN
  8. "rule": //规约 0:GB/T 32960-3 1:DB11-北京地标
  9. "useUnit":"郑州客车股份有限公司", //车辆运营单位
  10. "storePoint":"郑州客车股份有限公司", //车辆存放地点
  11. "manuUnit":"郑州客车股份有限公司(汽车厂商)", //车辆制造厂商
  12. "vehType":"电动公交车", //车辆类别
  13. "vehModel":"ZK6805BEVG17A", //车辆类型
  14. "termUnit":"深圳科技股份有限公司", //终端厂商
  15. "termModel":"YT_G2_v02/S_v02", //终端类型
  16. "driveMode":"0", //驱动方式 0:纯电动 1:油电混合...
  17. "vehArea":"深圳", //车辆销售区域
  18. "ownerPeople":"小李", //车主姓名
  19. "connecterPeople":"小张", //联系人姓名
  20. "simCard":"1.0", //SIM卡号
  21. "factoryDate":"2017-04-11", //出厂日期
  22. "firstReg":"2017-04-11", //首次上线时间
  23. "singleMileageSum":"0" //单次行驶里程总和
  24. "mileageSum":"0" //累计行驶里程
  25. }
  26. "LNBSCB3F1FD120728":{
  27. "licensePlate":"京A12347", //车牌号
  28. "vin":"LNBSCB3F1FD120728", //VIN
  29. ......
  30. }
  31. }
  32. }

2.1.4 通过VIN查询报文(已过时)

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值,暂时留空,为以后扩展

请求内容体定义

参数 是否必填 类型 说明
vin true String 单辆车vin
startTime true String 开始时间,精确到秒
endTime true String 结束时间,精确到秒
page true int 页码
pageRecorders true int 每页条数
type true String 报文类型
  1. //原始的请求内容体
  2. {
  3. "vin":"LNBSC123456767",
  4. "startTime":"2016-10-10 10:22:00",
  5. "endTime":"2016-10-10 10:23:00",
  6. "page":1,
  7. "pageRecorders":10,
  8. "type":""
  9. //"":全部 "1":车辆登入 "2":实时信息上报 "3":补发信息上报 "4":车辆登出 "5":平台登入 "6":平台登出 "7":心跳 "8":终端校时(国标)
  10. }
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true String 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "查询成功",
  4. "data": {
  5. [
  6. {
  7. "vin": "LA95T7276H1LC0031",//VIN
  8. "srvrRecvTime": "2017-10-30 17:24:25",//服务器接收时间
  9. "recvTime": "2017-10-30 17:24:41",//报文时间
  10. "type": "车辆登出",//类型
  11. "verify": "失败",//校验
  12. "dataLength": 33,//报文长度
  13. "data": "232304FE4C413935543732373648314C4330303331010008110A1E1118290003F0"//原始报文
  14. },
  15. {
  16. "vin": "LVVDB17B5FB063266",//VIN
  17. ......
  18. }
  19. ]
  20. }
  21. }

2.1.5 报文查询

@since 1.6

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值

请求内容体定义

参数 是否必填 类型 说明
page true int 页码
pageSize true int 每页条数
params.vin true String 单辆车vin
params.startTime true String 开始时间,精确到秒
params.endTime true String 结束时间,精确到秒
params.type false int 报文类型

parmas为map对象

  1. //原始的请求内容体
  2. {
  3. "page":1,
  4. "pageSize":10,
  5. "params":{
  6. "vin":"LNBSC123456767",
  7. "startTime":"2016-10-10 10:22:00",
  8. "endTime":"2016-10-10 10:23:00",
  9. "type": null
  10. }
  11. //"":全部 1:车辆登入 2:实时信息上报 3:补发信息上报 4:车辆登出 5:平台登入 6:平台登出 7:心跳 8:终端校时(国标)
  12. }
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true String 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "查询成功",
  4. "data": {
  5. "total" : 500,
  6. "data"
  7. [
  8. {
  9. "vid": "LA95T7276H1LC0031",//VIN
  10. "srvrRecvTime": "2017-10-30 17:24:25",//服务器接收时间
  11. "recvTime": "2017-10-30 17:24:41",//报文时间
  12. "type": 1,//类型
  13. "verify": 0,//校验
  14. "dataLength": 33,//报文长度
  15. "data": "232304FE4C413935543732373648314C4330303331010008110A1E1118290003F0"//原始报文
  16. },
  17. {
  18. "vin": "LVVDB17B5FB063266",//VIN
  19. ......
  20. }
  21. ]
  22. }
  23. }

2.2 故障报警相关

2.2.1 故障推送

车网平台车辆故障开始和故障结束,会主动向订阅故障通知的第三方平台推送故障消息。

由接收方提供

参数 是否必填 类型 说明
requestMsg true String 请求内容体
sign true String 签名值,暂时留空,为以后扩展

请求内容体定义

参数 是否必填 类型 说明
id true String 故障唯一标识
name true String 故障名称
ruleId true String 规则ID
level true Integer 故障级别
licensePlate true String 车牌号
vin true String 车架号
status true Integer 故障状态 0: 故障开始,1故障结束
lng true String 故障时经度(原始经度)
lat true String 故障时纬度(原始纬度)
startTime true String 故障开始时间
endTime true String 故障结束时间
参数 是否必填 类型 说明
code true int 响应码
msg true String 响应提示
data true String 查询结果,code=0时必填。实现传输的是经过AES加密后的字符串,下面只是方便查看,没有加密处理
  1. {
  2. "code": 0,
  3. "msg": "推送成功",
  4. "data": null
  5. }

附录

响应码定义

描述
0 请求正确
1001 token不合法或无权限
1002 参数缺失
1003 令牌为空
1004 该账号处于无效状态
1005 该账号已过期
1006 您无当前接口的访问权限
1011 该账号当天访问次数已达到限制
1012 该账号每分钟访问次数已达到限制
1013 您当前接口当天的访问次数已达到限制
1014 您当前接口每分钟访问次数已达到限制
1021 请求已过期,请确认时间戳正常
1022 签名验证失败

数据项定义

编号 数据项名称 描述
1001 注册时间
1002 注册流水号
1003 车牌号
1004 车载终端编号
1005 车辆动力类型
1006 动力蓄电池包总数
1007 动力蓄电池代码列表
1008 发动机编码
1009 燃油种类
1010 最大输出功率
1011 最大输出转矩
1012 注册预留
1020 终端登入/登出流水号
1021 ICCID
1022 可充电储能子系统数
1023 可充电储能系统编码长度
1024 可充电储能系统编码
1025 终端登入/登出时间
1026 平台登入/登出时间
1027 平台登入/登出流水号
1028 平台用户名
1029 平台密码
1031 终端登出时间
1033 终端登出流水号
2000 实时数据时间
2001 单体蓄电池总数
2002 动力蓄电池包总数
2003 单体蓄电池电压值
2004 动力蓄电池预留
2005 动力蓄电池包总数
2101 动力蓄电池包温度探针总数
2102 动力蓄电池包总数
2103 温度值
2110 燃料电池电压
2111 燃料电池电流
2112 燃料消耗率
2113 燃料电池温度探针总数
2114 探针温度值
2115 氢系统中最高温度
2116 氢系统中最高温度探针代号
2117 氢气最高浓度
2118 氢气最高浓度传感器代号
2119 氢气最高压力
2120 氢气最高压力传感器代号
2121 高压DC/DC状态
2201 车速
2202 里程
2203 档位
2204 制动状态
2205 驱动状态
2206 预留
2207 预留
2208 加速踏板行程值
2209 制动踏板行程值
2211 整车数据预留
2212 实时数据信息类型预留
2213 运行模式
2214 DC-DC状态
2301 充放电状态
2302 电机控制器温度
2303 电机转速
2304 电机温度
2305 电机电压
2306 电机母线电流
2307 驱动电机个数
2308 驱动电机总成信息列表
2309 驱动电机序号
2310 驱动电机状态
2311 驱动电机转矩
2401 发动机状态
2402 ECU温度
2403 车辆电池电压
2404 发动机温度
2405 进气歧管气压
2406 进气温度
2407 废气排出温度
2408 燃料喷射压力
2409 燃料喷射量
2410 点火提前角
2411 曲轴转速
2412 油门开度
2413 燃料消耗率
2501 定位状态
2502 经度
2503 纬度
2506 定位数据预留
2601 最高电压动力蓄电池包序号
2602 最高电压单体蓄电池序号
2603 电池单体电压最高值
2604 最低电压动力蓄电池包序号
2605 最低电压单体蓄电池序号
2606 电池单体电压最低值
2607 最高温度动力蓄电池包序号
2608 最高温度探针序号
2609 最高温度值
2610 最低温度动力蓄电池包序号
2611 最低温度探针序号
2612 最低温度值
2613 总电压
2614 总电流
2615 SOC
2617 正极对地电阻
2618 极值数据预留
2701 自定义数据长度
2702 自定义数据
2802 动力蓄电池其他故障总数n
2803 动力蓄电池其他故障代码列表
2804 电机故障总数N
2805 电机故障代码列表
2806 发动机故障总数N1
2807 发动机故障列表(2308)
2808 其他故障总数M
2809 其他故障代码列表
2901 温度差异报警
2902 电池极柱高温报警
2903 动力蓄电池包过压报警
2904 动力蓄电池包欠压报警
2905 SOC低报警
2906 单体蓄电池过压报警
2907 单体蓄电池欠压报警
2908 SOC太低报警
2909 SOC过高报警
2910 动力蓄电池包不匹配报警
2911 动力蓄电池一致性差报警
2912 绝缘故障
2920 最高报警等级
2921 可充电储能装置故障总数N1
2922 可充电储能装置故障代码列表
2923 发动机故障总数N3
2924 发动机故障列表(2924)
3101 信息采集时间
3102 通电状态
3103 电源正常
3104 通信传输状态
3105 其他异常
3106 车载终端预留
3201 车辆状态
3202 状态标志
3301 系统预留1
3302 系统预留2
3303 系统预留3
3401 终端参数查询
3402 终端参数设置
3403 终端控制命令
3801 通用报警状态
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注