丁香医生开放平台开发者文档

1. 概述

1. 阅读对象

本文档供接入合作方技术开发人员阅读,主要包含系统交互流程、详细接口文档等内容。

2. 接口使用场景

此文档上的所有接口仅供服务端之间调用,不能直接使用客户端直接调用丁香医生的开放接口(客户端调用存在接口密钥泄漏的风险)。

3. 接入方式

接入方式 API方式 H5方式
说明 丁香医生仅提供开放接口,合作方需要自实现问诊服务全流程(用户端展现、问诊费用支付、用户端消息通知等)。 丁香医生提供开放接口和HTML5网页,用户端展现由丁香医生提供的网页承载,问诊费用直接入丁香医生账户,合作方需完成用户端消息通知。
特点 可跨多端接入,定制性高、便于合作方业务整合。 对接便捷,但定制性低。
不支持场景 不支持在禁用支付宝支付的应用内使用(如:微信、手机QQ)。

2. 系统交互流程

1. 用户授权流程

图片

3. 接口规范说明

1. 接口交互方式

以 HTTPS(GET/POST)+JSON 方式进行数据交互,请求地址:https://ask.dxy.com/open/i/platform/${接口编号} 。 所有接口都只能在服务端之间调用,合作方不能通过客户端直接调用。

2. 参数类型及长度定义

名称 说明
Int 正整型,范围[0~2^63)
String(x) 字符串型,字符串长度不超过x,例如String(500)表示长度不超过500的字符串

3. 请求参数说明

当接口说明需要用户级鉴权时,需要设置请求头 ask-platform-sid:${userToken} ,获取userToken的方式见接口100010

请求头User-Agent不建议使用工具包或者编程语言默认的值,如包含"Apache,HttpClient,Java,python"等关键字的User-Agent会被限流,可使用User-Agent: DXYS-Partner

  • GET请求:所有参数需要以URLEncode(UTF-8)格式发送(先签名再URL编码),例如:key1=value1&key2=value2&key3=%E4%B8%AD%E6%96%87%E5%8F%82%E6%95%B0
  • POST参数:request headers中的Content-Type设置为application/x-www-form-urlencoded;charset=utf-8 ,有文件上传时为 multipart/form-data 。

4. 签名方式

所有请求都需要包含的公共参数如下:

参数名 类型 示例值 说明
nonce String(32) GPID5J2m5cZ4pZEYxgA2l7qIzgHSoLmF 定长32个字符的随机数字字母字符串 [0-9a-zA-Z]{32}
timestamp Int 1509419983789 Unix毫秒单位的时间戳,需取发起请求的时间,与服务端时间差超过10分钟会判定为不合法请求
appId String(32) dxy6p64jhh75oi4cpq6 丁香医生分配给合作方的appId,是调用方的身份标识
sign String(100) 26aefe8433c2aef074e041847282570abc7cb862 除sign外其他参数的签名

sign 的生成方式:除 sign 参数外,包括业务参数在内的所有参数都要参与签名,按字段名称 ASCII 码从小到大排序后,字段名和字段值都采用原始值使用 URL 键值对格式 key1=value1&key2=value2&key3=value3... 拼成字符串 string,然后对拼接后的字符串使用SHA-1算法生成签名 sign 。 注意: appSignKey 只参与签名,不作为请求参数。

Java版签名的例子 SignUtil.java:

public static void main(String[] args) throws IOException {
    String appId = "<appId>";
    String appSignKey = "<appSignKey>";
    String timestamp = String.valueOf(System.currentTimeMillis());
    String nonce = StringUtil.random(32);

    Map<String, Object> params = new HashMap<>();
    params.put("appId", appId);
    params.put("timestamp", timestamp);
    params.put("nonce", nonce);
    params.put("param1", "value1");//业务参数1
    params.put("param2", "value2");//业务参数2
    params.put("sign", getSign(params, appSignKey));

    //.发送请求
    String url = "https://ask.dxy.com/open/i/platform/130001";
    String responseBodyAsString = HttpClientUtil.post(url, params);
    System.out.println(responseBodyAsString);
}

/**
 * 获取签名
 *
 * @param params
 * @param appSignKey
 * @return
 */
public static String getSign(Map<String, Object> params, String appSignKey){
    TreeMap<String, Object> treeMap = new TreeMap<>();
    params.forEach(treeMap::put);
    treeMap.remove("sign");
    treeMap.put("appSignKey", appSignKey);
    StringBuilder signSb = new StringBuilder();
    treeMap.forEach((key, value) -> signSb.append(key).append("=").append(value).append("&"));
    if (signSb.length() > 0) {
        signSb.deleteCharAt(signSb.length() - 1);
    }
    return getSHA1(signSb.toString().getBytes());
}

/**
 * 用SHA1算法生成安全签名
 *
 * @param content 签名信息
 * @return 安全签名
 */
public static String getSHA1(byte[] content) throws NoSuchAlgorithmException {
    // SHA1签名生成
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    md.update(content);
    byte[] digest = md.digest();
    StringBuffer hexstr = new StringBuffer();
    String shaHex = "";
    for (int i = 0; i < digest.length; i++) {
        shaHex = Integer.toHexString(digest[i] & 0xFF);
        if (shaHex.length() < 2) {
            hexstr.append(0);
        }
        hexstr.append(shaHex);
    }
    return hexstr.toString();
}

5. 返回结果示例

GET返回示例:

{
    "data": {
        "items": [
            {
                "order_id": 67000032,
                "question_id": 9800001,
                "status": 0,
                "merchant_fee": 1500,
                "create_timestamp": 1509372966000,
                "confirm_timestamp": 1509373966000
            }
        ]
    }
}

POST返回示例:

{
    "data": {
        "items": [
            {
                "updated": 1509419983789,
                "id": "6200001"
            }
        ]
    }
}

错误信息返回示例:

{
    "error": {
        "code": 10003,
        "message": "请求参数不正确"
    }
}

成功的请求返回结果会包含data节点,失败的请求返回结果会包含error节点。 接口返回为错误结构体时,code为错误码,message为错误信息。详细错误码定义见附录6.2

分页数据结构示例:

{
    "data": {
        "items_per_page": 8, //每页包含元素个数
        "start_index": 9,   //当前页起始元素编号
        "page_index": 2,    //当前页码,1开始
        "total_pages": 3,   //总页码
        "total_items": 20,  //总元素个数
        "current_item_count": 8,    //当前页元素个数
        "items": []
    }
}

其中:items_per_page表示每页包含元素个数,start_index表示当前页起始元素编号,page_index表示当前页码,total_pages表示总页码,current_item_count表示当前页元素个数。 注意应该通过判断page_index与total_pages的大小关系来确定是否有下一页。

4. 接口列表

1. 用户授权

100010-获取用户身份凭证

接口编号 100010
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 为合作方用户获取用户 token。非幂等,对同一个用户多次获取token后,最新的 token 会覆盖原 token (原 token 失效)
请求参数
参数名 类型 必填 示例值 说明
uid String(30) u100821 合作方的用户唯一标识
nickname String(30) 飞翔的电灯泡 用户在合作方的昵称
返回结果
参数名 类型 必填 示例值 说明
token String(64) ASK-3yK3skDcrx66us24 用户身份凭证,重新获取前一直有效

100011-获取免登录链接

接口编号 100011
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 web开放方式接入丁香医生开放平台时,获取用户进入丁香医生系统的免登录链接。
请求参数
参数名 类型 必填 示例值 说明
uid String(30) u100821 合作方的用户唯一标识
redirect String(200) aHR0cHM6Ly9hc2suZHh5LmNvbS9pbmRleCMvZmluZA== 登入后重定向链接的base64(UTF-8)编码(有重定向需求的传入)
返回结果
参数名 类型 必填 示例值 说明
login_url String(300) https://ask.dxy.com/login/openweb/1767104/yROI2iesF9EQiM3Mi2O?redirect=aHR0cDovL2Fza3Rlc3QuZHh5Lm5ldC9pbmRleCMvbWluZS8 web 开放方式用户的免登录链接(15分钟内仅可使用1次)

2. 科室和医生信息

110001-快速提问搜索医生

接口编号 110001
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 根据文本内容匹配医生
请求参数
参数名 类型 必填 示例值 说明
q String(500) "感冒" 搜索文本
min_price Int 1000 价格筛选的下限,单位分
max_price Int 8000 价格筛选的上限,单位分
job_title Int 4 医生职称:2医师,3主治医师,4副主任医师,5主任医师
department Int 2 科室ID,见接口110002
page_index Int 1 页码,默认1
items_per_page Int 10 分页大小,默认10
返回结果
参数名 类型 必填 示例值 说明
user_id Int 666 医生ID
avatar String(255) https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg 头像
nickname String(30) 王小明 医生姓名
hospital_name String(60) 北京协和医院 医院名称
hospital_info Array[HospitalInfo] 详见下方"HospitalInfo数据结构" 详见下方"HospitalInfo数据结构"
job_title_name String(30) 主治医师 职称
section_name String(30) 儿科 科室名称
ask_fee Int 1000 问诊费用,单位:人民币分
vip Boolean true 是否加V认证
star String(5) 4.99 患者给医生的评分(保留两位小数格式化后的字符串)
tags Array[String] ["感冒","发烧"] 擅长疾病,列表长度不超过50,单元素字符串长度不超过30。
display_tags Array[String] ["从业10年","三甲医院"] 展示标签,列表长度不超过50,单元素字符串长度不超过50。
HospitalInfo数据结构
参数名 类型 示例值 说明
name String(100) 浙江大学医学院附属儿童医院滨江院区 医院名称
grade_name String(30) 三级甲等 医院级别 如 "三级甲等","未知"
trade String(30) 公立 医院性质 如 "公立","未知"

110002-科室列表

接口编号 110002
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 获取丁香医生对应问题类型可用的科室列表
请求参数
参数名 类型 必填 示例值 说明
question_type int 0 当传0时,返回图文问诊可用的科室列表,传6时,返回电话急诊可用的科室列表,传7时,返回快速图文可用的科室列表
返回结果
参数名 类型 必填 示例值 说明
id Int 12 科室ID
name String(30) 疼痛科&麻醉科 科室名称
member_count Int 213 科室中可用的医生数
description String(128) 手术麻醉、坐骨神经痛、三叉神经痛、癌痛 科室简介

110003-科室下的医生列表

接口编号 110003
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 科室下的医生列表(可分页)
请求参数
参数名 类型 必填 示例值 说明
department Int 12 科室ID,见接口110002
rank_type Int 1 排序规则:0综合排序,1月回答次数降序,2星级评分降序,4价格升序,5价格降序,6响应时间升序
min_price Int 0 价格筛选,最小价格,单位:人民币分
max_price Int 5000 价格筛选,最大价格,单位:人民币分
job_title Int 4 医生职称:2医师,3主治医师,4副主任医师,5主任医师
area_id int 110000 各个地区的行政划分码, 完整版需要向丁香医生平台方获取
page_index Int 1 页码,从1开始
items_per_page Int 10 页长,默认10,最大200
返回结果
参数名 类型 必填 示例值 说明
user_id Int 4592076 医生id
avatar String(255) https://askpub.dxycdn.com/avatar.jpg 医生头像URL
nickname String(30) 戴梦成 医生姓名
hospital_name String(60) 青岛XXXX医院 医院名称
hospital_info Array[HospitalInfo] 详见下方"HospitalInfo数据结构" 详见下方"HospitalInfo数据结构"
location_str String(30) 山东 青岛市 医生所在地区
section_name String(30) 皮肤科 科室名称
job_title_name String(30) 主治医师 医生职称
ask_fee Int 1000 问诊费用,单位:人民币分
reply_count Int 200 月回答数(最近30天回答数)
vip Boolean true 是否加V认证
star String(5) 4.99 患者给医生的评分(保留两位小数格式化后的字符串)
tags Array[String] ["痤疮","脱发","过敏"] 擅长疾病列表,列表长度不超过50,单元素字符串长度不超过30。
display_tags Array[String] ["从业10年","三甲医院"] 医生标签列表,列表长度不超过50,单元素字符串长度不超过50。
start_work_year Int 1997 开始工作年份(非必传字段)
introduction String(512) 皮肤病学博士。擅长湿疹皮炎、荨麻疹、过敏、痤疮(痘痘)、银屑病牛皮癣、癣、灰趾甲、白斑、脱发提问放清晰图片 医生个人简介
status Int 0 医生状态:0正常,1封禁,2停诊
HospitalInfo数据结构
参数名 类型 示例值 说明
name String(100) 浙江大学医学院附属儿童医院滨江院区 医院名称
grade_name String(30) 三级甲等 医院级别 如 "三级甲等","未知"
trade String(30) 公立 医院性质 如 "公立","未知"

110004-医生详细信息

接口编号 110004
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 获取医生详细信息
请求参数
参数名 类型 必填 示例值 说明
user_id Int 4592076 医生id
返回结果
参数名 类型 必填 示例值 说明
user_id Int 4592076 医生id
avatar String(255) https://askpub.dxycdn.com/avatar.jpg 医生头像URL
nickname String(30) 戴梦成 医生姓名
hospital_name String(60) 中国医学科学院皮肤病医院 医院名称
hospital_info Array[HospitalInfo] 详见下方"HospitalInfo数据结构" 详见下方"HospitalInfo数据结构"
location_str String(30) 山东 青岛市 医生所在地区
section_name String(30) 皮肤科 科室名称
job_title_name String(30) 主治医师 医生职称
ask_fee Int 1000 问诊费用,单位:人民币分
vip Boolean true 是否加V认证
star String(5) 4.99 患者给医生的评分(保留两位小数格式化后的字符串)
avg_reply_time Int 120 医生的平均响应时间,单位秒
tags Array[String] ["痤疮","脱发","过敏"] 擅长疾病列表,列表长度不超过50,单元素字符串长度不超过30。
display_tags Array[String] ["从业10年","三甲医院"] 医生标签列表,列表长度不超过50,单元素字符串长度不超过50。
start_work_year Int 1997 开始工作年份
introduction String(512) 北京协和医学院皮肤病学博士。擅长湿疹皮炎、荨麻疹、过敏、痤疮(痘痘)、银屑病牛皮癣、癣、灰趾甲、白斑、脱发提问放清晰图片 医生个人简介
status Int 0 医生状态:0正常,1封禁,2停诊
practice_experience String 1.复旦医学院毕业\n2.全国首例颅脑手术\n3.完成心脏搭桥手术240例 执业经历
academic_experience String 1.中华医学会科研项目1项\n2.主持国家自然基金4项\n3.参与编写《支气管镜诊疗专家共识》 学术经历
professional_profile String 1.中华医学会皮肤病理学组委员\n2.北京医师协会新生儿儿科医师分会理事\n3.中国医师协会皮肤分会专业委员会委员 专业资历
HospitalInfo数据结构
参数名 类型 示例值 说明
name String(100) 浙江大学医学院附属儿童医院滨江院区 医院名称
grade_name String(30) 三级甲等 医院级别 如 "三级甲等","未知"
trade String(30) 公立 医院性质 如 "公立","未知"

110005-通过提问内容推荐科室

接口编号 110005
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 通过提问内容推荐科室
请求参数
参数名 类型 必填 示例值 说明
content String(600) 得了灰指甲怎么办 描述问诊的语句或者关键词
返回结果
参数名 类型 必填 示例值 说明
id Int 12 科室ID
name String(30) 疼痛科&麻醉科 科室名称

3. 问诊相关

120001-创建问题

接口编号 120001
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 创建问题(需要15分钟内调用接口130001确认订单后问诊正式生效)
请求参数
参数名 类型 必填 示例值 说明
content String(600) 感冒了怎么办 问题内容
doctor_user_id Int 条件 12196047 医生ID(type=0普通图文问诊时必填)
ask_fee Int 3100 问诊费用,单位人民币分,用于价格校验。
ip String(30) 100.100.100.100 用户的ip
attachments MultipartFile[] 此字段不参与签名,multipart/form-data方式上传图片文件,支持批量上传,最多9个文件,单文件不超过10M,文件总大小不超过20M
attachments_urls String(2000) https://cdndomain.com/img1.jpg 支持批量,最多9个文件,单文件不超过10M,文件总大小不超过20M。多个URL可以先UrlEncode后再半角逗号连接,例如:"https%3A%2F%2Fcdndomain.com%2Fimg1.jpg,https%3A%2F%2Fcdndomain.com%2Fimg2.jpg"
type Int 0 问诊类型:0普通图文问诊,6电话急诊,7快速图文问诊。
cellphone String(30) 18888888888 患者手机号,当问题为电话急诊时需要传入
department Int 9 科室ID,见接口110002。电话急诊、快速图文时建议传入。
返回结果
参数名 类型 必填 示例值 说明
order_id Int 1 订单ID
question_id Int 1 问题ID

120002-获取问题详情

接口编号 120002
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 获取问题详情(用户创建问诊时的内容算作第1个对话dialog)
请求参数
参数名 类型 必填 示例值 说明
question_id Int 1 问题ID
返回结果
参数名 类型 必填 示例值 说明
id Int 1 问题ID
doctor 同接口110003返回数据结构 接诊的医生信息,非定向的问诊(快速图文、电话急诊)没有医生接诊时无当前节点。
content String(600) “感冒怎么办” 问题内容
status Int 问题状态 0:待回答,1:已回答,2:用户取消,3:医生拒绝回答,4:超时退回,5:已结束,6:待支付,7:已取消支付
finish_status Int 问题完成状态 0:未完成,1:系统自动完成,2:提问者关闭,3:用户投诉后结束,4:审核不通过结束,5:管理后台关闭
live_call_state Int 电话急诊状态,只有当问题类型为电话急诊时才会返回。 1:待接听,2:医生接诊,3:通话完成,4:已结束(正常),5:已结束(无人接听),6:已结束(医生通话异常),7:已结束(问诊总结异常),10:已结束(通话遇到问题),11:正在接听(医生已拨打),12:已结束(医生通话异常,未拨打过患者手机)13:已结束(丁香医生后台发起的退款)
create_timestamp long 1509361251000 创建Unix时间戳(单位:毫秒)
finish_timestamp long 1509361251000 问题结束时间。13 位毫秒级时间戳
attachment_url_list Array[String] ["https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg", "https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg"] 图片URL(临时链接,5分钟实效性)
refused_message String(512) 症状不明 医生拒绝理由
appended Boolean true 当前是否为被追问状态
ask_count Int 1c 询问次数(含提问)
last_reply_timestamp Int 1509361251000 医生最后一次回复Unix时间戳(单位:毫秒)
order_id Int 3377217 订单id
dialogs Array[Dialog] 对话内容(第一条即为提问),Dialog定义如下
Dialog数据结构
参数名 类型 示例值 说明
id Int 1 对话ID
question_id Int 1 问题ID
user_id Int 1 用户ID
user_nickname String(30) 阿wing 用户昵称
user_avatar String(255) https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg 用户头像
type Int 0 对话类型:0提问,1回答
content String(600) "多喝热水" 对话内容
attachment_url_list Array[String] ["https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg", "https://askpub.dxycdn.com/2017/09/07/06/pcbedzr9.jpg"] 图片url(5分钟内有效),最多9个链接,每个链接最长255个字符。
voice_list Array[VoiceDetail] 录音文件列表,VoiceDetail定义如下
create_timestamp Int 1509361251000 创建Unix时间戳(单位:毫秒)
VoiceDetail数据结构
参数名 类型 示例值 说明
id Int 1 对话ID
url String(255) https://video1p.dxycdn.com/2019/0912/743/33678485709834 录音文件URL(5分钟内有效)
duration Int 600000 录音时长,单位为毫秒
file_format String(10) M4A 文件后缀名(M4A,WAV)

120003-用户追问

接口编号 120003
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 用户追问(问诊没有被医生回复或者医生回复的24小时内可追问,最多追问2次)
请求参数
参数名 类型 必填 示例值 说明
content String(600) 接下来怎么办 追问内容
question_id Int 1 对应问题ID
attachments MultipartFile[] multipart/form-data方式上传图片文件,支持批量上传,最多9个文件,单文件不超过10M,文件总大小不超过20M[无法识别的内容]
attachments_urls String(2000) https://cdndomain.com/img1.jpg 支持批量,最多9个文件,单文件不超过10M,文件总大小不超过20M。多个URL可以先UrlEncode后再半角逗号连接,例如:"https%3A%2F%2Fcdndomain.com%2Fimg1.jpg,https%3A%2F%2Fcdndomain.com%2Fimg2.jpg"
dialog_index Int 2 用户对话的顺序编号,追问从2开始依次递增。可用来避免因接口重复请求导致用户对话重复的问题,重复请求可保证幂等,建议传入。
返回结果
参数名 类型 必填 示例值 说明
id Int 1 追问对话ID
updated Int 1509419983789 处理完成的Unix时间戳(单位:毫秒)

120004-问题列表

接口编号 120004
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 获取某用户所问的问题列表
请求参数
参数名 类型 必填 示例值 说明
page_index Int 1 页码默认1
items_per_page Int 10 页大小 默认10
返回结构

同接口120002返回数据结构

4. 投诉处理

140001-发起投诉

接口编号 140001
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 投诉分为两种类型(详细投诉状态流转见 附录6.3):

0 发起投诉
这里的投诉应该是由合作方初审过的确实需要交由丁香医生处理的用户投诉,当合作方对问诊发起投诉后,丁香医生会有专人负责审核,审核结果会通过状态通知接口反馈给合作方。当审核状态为“通过”时表示丁香医生已经认可这起投诉,结束了用户的问诊并且将问诊费用已经退回到合作方的账户中,但和用户相关的处理如退款、消息通知需要合作方自行处理。

1 发起申诉(申请取消提问)
根据问题的状态来确认是否能够直接进行取消本次提问的操作,会在1min内告知是否“通过”申诉
请求参数
参数名 类型 必填 示例值 说明
content String(1024) 问题没有被解决,医生态度也不好,回应非常慢 投诉内容,长度需要控制在10到500字。
question_id Int 1 对应问题ID
cellphone String(11) 13713366666 联系方式
type Int 1 投诉类型:
0 发起投诉
1 发起申诉(取消提问)
返回结构
参数名 类型 必填 示例值 说明
id Int 1 投诉ID
updated Int 1509419983789 处理完成的Unix时间戳(单位:毫秒)

140002-获取投诉

接口编号 140002
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 获取某个问题下的投诉
请求参数
参数名 类型 必填 示例值 说明
question_id Int 1 对应问题ID
返回结构
参数名 类型 必填 示例值 说明
id Int 1 投诉ID
question_id Int 1 对应问题ID
status Int 0 0:未审核,1:审核通过,2:审核不通过
content String(1024) 没有效果 投诉内容
cellphone String(11) "13713366666" 联系方式
create_timestamp Int 1509361251000 创建Unix时间戳(单位:毫秒)
reason String(1024) 同意 审核原因

5. 订单相关

130002-订单查询

接口编号 130002
请求方 合作方
服务方 丁香医生
请求方式 GET
用户级鉴权
接口说明 根据订单id查询一条订单记录
请求参数
参数名 类型 必填 示例值 说明
order_id Int 62000121 丁香医生统一订单id
返回结果
参数名 类型 必填 示例值 说明
order_id Int 62000121 丁香医生统一订单id
question_id Int 98000232 问题id
status Int 1 订单状态:0待确认,4已确认,5退款中,6已退款,10已关闭
ask_order_fee Int 1500 丁香医生订单金额(单位:分,币种CNY)
merchant_fee Int 1500 合作方订单金额(单位:分,币种CNY)
create_timestamp Int 1509361251000 订单创建Unix时间戳(单位:毫秒)
confirm_timestamp Int 1509361351000 订单确认Unix时间戳(单位:毫秒)

130001-订单确认

接口编号 130001
请求方式 POST
请求方 合作方
服务方 丁香医生
用户级鉴权
接口说明 确认一笔订单,关联的问诊正式生效
请求参数
参数名 类型 必填 示例值 说明
order_id Int 62000121 丁香医生统一订单id
返回结果
参数名 类型 必填 示例值 说明
id Int 62000121 请求时的丁香医生统一订单id
updated Int 1509419983789 处理完成的Unix时间戳(单位:毫秒)

130003-关闭订单

接口编号 130003
请求方 合作方
服务方 丁香医生
请求方式 POST
用户级鉴权
接口说明 关闭一笔未确认的订单。若订单已调用130001(订单确认)接口,则不能再关闭。
请求参数
参数名 类型 必填 示例值 说明
order_id Int 62000121 丁香医生统一订单id
返回结果
参数名 类型 必填 示例值 说明
id Int 62000121 请求时的丁香医生统一订单id
updated Int 1509419983789 处理完成的Unix时间戳(单位:毫秒)

5. 消息推送

除了通过调用API的方式与开放平台产生交互,还可以通过消息推送及时接收到新的状态信息。避免轮询API,大大提高API调用效率。 合作方接入时向丁香医生开发平台提供接口地址即可接收推送。

注意: 出于接口安全性的考虑,callback地址必须支持https,且必须是标准的443端口(即url格式为https://domain.com/xxx,不能带端口号)

为了保证系统安全,合作方作为服务端接收丁香医生发起的通知请求时,需要对请求参数进行验证签名。状态通知接口的业务参数不参与签名,仅对appId、appSignKey、nonce、timestamp参数按照上面「接口规范说明」-「签名方式」对数据进行签名得到签名字符串sign2。通过比较sign和sign2的相等与否来实现验签。

附上一段接收通知Java版示例供参考:

public class AskCallBackSample {

    private static final String APP_ID = "<appId>";
    private static final String APP_SIGN_KEY = "<appSignKey>";

    public String askCallback(HttpServletRequestMock httpServletRequest) throws Exception {
        Map<String, Object> params = new HashMap();
        params.put("nonce", httpServletRequest.getParameterValues("nonce")[0]);
        params.put("timestamp", httpServletRequest.getParameterValues("timestamp")[0]);
        String appIdParam = httpServletRequest.getParameterValues("appId")[0];
        params.put("appId", appIdParam);
        if (!APP_ID.equals(appIdParam)) {
            return "appId error";
        }
        String signLocal = SignUtil.getSign(params, APP_SIGN_KEY);
        if (signLocal == null || !signLocal.equals(httpServletRequest.getParameterValues("sign")[0])) {
            return "sign error";
        }
        //todo 业务处理...
        return "SUCCESS";
    }
}

/**
 * mock的Request类,实际情况应该使用{@link javax.servlet.http.HttpServletRequest}
 */
class HttpServletRequestMock {

    public String[] getParameterValues(String nonce) {
        return new String[]{""};
    }
}

具体的接口定义如下:

状态通知接口

请求方 丁香医生
服务方 合作方
请求方式 POST (Content-Type:application/x-www-form-urlencoded;charset=utf-8)
用户级鉴权
接口说明 问诊状态发生变化时,丁香医生向合作方推送问诊状态变化通知。 合作方收到验签通过的通知后,合作方后应该同步返回成功处理的的结果(如下描述或者"SUCCESS"字符串)。当收不到成功处理的结果,丁香医生会采用重试机制再次推送,最多重试5次,间隔时间分别为5分钟、10分钟、20分钟、30分钟、1小时。为避免发生异常,合作方需保证接收接口的幂等性(可以通过msg_id实现)。
请求参数
参数名 类型 必填 示例值 说明
msg_id Int 600232 消息id,标识1条消息
notice_type Int 1 1 问诊退回(一般为医生拒绝)
2 问诊退回(超时未被回复)
3 医生回复了问诊
4 问诊结束
5 投诉审核通过
6 投诉审核不通过
7 问诊被用户主动取消
8 医生补充回答(对追问的回答)
9 医生接诊(快速图文,电话问诊)
10 通话完成(电话问诊)
11 问诊已生效(订单已确认)
12 丁香医生后台发起的退款
question_id Int 980000232 问题id
uid String(30) u100821 合作方的用户唯一标示
返回结果
参数名 类型 必填 示例值 说明
id Int 98000232 请求时的问题id
updated Int 1509419983789 处理完成的 Unix 时间戳(单位:毫秒)

6. 附录

1. 线下对账要素

商户订单ID 丁香医生订单ID 状态:1.待结算,2.已退款 创建时间 支付时间 退款时间
String(30) String(10) Int yyyy-MM-dd HH:mm:ss yyyy-MM-dd HH:mm:ss yyyy-MM-dd HH:mm:ss

附模版文件:商户订单模版.xls

2. 错误码定义

错误码 含义
10002 请求参数为空
10003 请求参数不正确
10004 请求过于频繁,请稍后重试
10005 系统内部错误
10006 上传的文件大小超过限制
10007 上传的文件个数超过限制
100002 当前用户未登录
100707 暂无可接诊医生
200001 医生不存在
200003 医生被封禁
200004 医生已停诊
200301 当前用户不是问题的所有者
240001 应用不存在
240002 签名错误
240003 账户余额不足
240004 应用账号已停用
240006 请求路径错误
240007 用户不存在
240201 已有退款记录
240301 投诉记录不存在
240302 问题已被投诉
300001 问题不存在
300004 当前问题不能被追问
300005 超出最大追问限制
300006 问题已关闭
300014 无法查看该问题
300108 问题创建失败
300113 问题当前的状态不能被投诉
300132 对话顺序错误
600001 订单不存在
600003 订单状态错误
600009 订单创建失败
600013 当前用户不是该订单的所有者
600015 丢失订单信息
600502 订单关闭失败
600506 订单已关闭
640001 电话问诊状态不正确
640004 电话问诊信息不存在
640005 手机号为空
640006 电话问诊信息缺失
640013 非电话急诊问诊
640014 手机号不正确
690002 医生已接诊
690003 快速图文问诊状态异常

3. 投诉状态流转

流转状

7. 常见对接问题(Q&A)

Q:问题详情中的音频、视频等资源文件的有效期是多长时间?

A:有效期为5min,合作方需要自行对资源文件进行本地化操作

Q:如何进行测试?

A:目前丁香医生开放平台可以对接三种问诊类型

  • 普通图文问诊
    • 向测试医生 小新(doctor_user_id:12196047) 提问即可,可以使用110004-医生详细信息接口获取医生信息
  • 快速图文问诊&电话问诊
    • 需要双方商务商定好开通该服务,并提供一个已经调用过100010-获取用户身份凭证接口的uid添加到测试白名单

Q:在110003-科室下的医生列表接口中找不到测试医生?

A:测试环境是沙盒模式,为防止正式用户问到测试医生产生不必要的麻烦,在这个接口中对测试医生进行了屏蔽。其他接口可以正常使用测试医生

Q:如何进行联调?

A:为了节省双方的精力,丁香医生平台针对部分问诊流程测试进行了自动化操作

  • 普通图文问诊&快速图文问诊医生接诊后,适用以下规则 (PS. 1和2仅适用于普通图文问诊)其中 x<=3,y<=9

    1. 回复【超时】问题被超时退回

    2. 回复【拒绝】医生拒绝当前问题

    3. 没有关键字(语音,图片,文字)

      医生回复:系统的测试文字

    4. 回复【文字】

      医生回复:患者的文字

    5. 回复【语音x】

      医生回复:语音x条,会分x次消息 发送

    6. 回复【图片y】

      医生回复:y张图片+默认的测试文字

    7. 回复【图片y】+【文字】

      医生回复:患者的文字+y张图片

    8. 回复【语音x】+【文字】

      医生回复:第一条:1条语音+患者发送的文字内容;x-1后面的语音都是单独发送

    9. 回复【语音x】+【图片y】

      医生回复:第一条:1条语音+y张图片;x-1后面的语音都是单独发送

    10. 回复【语音x】+【图片y】+【文字】

    医生回复:第一条:1条语音+y张图片+患者的文字;x-1后面的语音都是单独发送

  • 电话问诊

    • 添加完测试白名单之后,联系丁香医生的测试

Q:如何测试医生的语音回复?

A:针对每个平台,医生的语音回复功能默认是不开启的。若需要测试和使用,请联系丁香医生的技术或者测试开通后进行测试

Q:联调过程中发现问题如何反馈?

A:丁香医生开放平台已经对接了多家合作方,一般情况下不会是平台方的错误,请反馈问题前先根据以下步骤进行自测

  1. 仔细查看文档对比自己的代码是否存在遗漏的地方
  2. 检查传递参数的编码格式以及请求方式
  3. 检查签名的生成是否有误
  4. 消息推送接口的返回值是否符合丁香医生平台方的规则

如果以上方法检查后依然无法排查出问题, 请携带以下俩种信息的一种进行反馈,以方便快速定位问题

  • 【curl】

  • 请求接口的详细参数,格式如下

    • 请求方法:POST/GET

    • 请求头:User-Agent: xxxx、ask-platform-sid: xxxxx

    • 请求参数(Json格式):{"user_id": 12196047}

    • 请求接口返回值(Json格式):{"error":{"code":10003,"message":"请求参数不正确"}}

Q:测试医生小新,为什么经常停诊?停诊对用户有什么影响?

A:医生由于手术、忘记关诊等原因发生超时退回后,如果有新的提问,可能还会超时,用户体验不好。所以当月累计多个超时退回的问题后,将会自动停诊,测试医生由于大家经常测试超时退回,所以一有新的超时退回问题,就会停诊。停诊之后,已经提问的问题医生可以继续回复,只是接不到新的问题。小新医生只要测试了「超时未回复」的问题,就会停诊,测过这类问题以后就可以找我们开诊。

Q:如何申请预发环境或者线上环境的APPID?

A:请向丁香医生商务这边申请,需要提供回调url,可以先给预发的回调,上线后再替换成线上的回调。

Q:如何线上环境测试?

A:分为以下两种情况:

  • 线上appId可以问线上和测试医生,测试appId只能问测试医生,线上appid测试的话,会走对账流程,会产生结算费用。如果线上对正式的医生进行提问,不要使用「测试」等语句,对医生造成不必要的困扰。请使用真实用户的问诊场景语句进行提问,且医生正常回复后,不予退款。

  • 测试appId就是线上的数据,小新医生就是线上环境的医生,只是对权限进行了隔离,不能问正式医生。线上appId也可以提问测试医生小新,只不过不能在医生列表里找到小新医生,和测试环境流程一样向小新医生提问,需要手动指定,也可以自动回复,向小新医生提问,也会走结算流程。

Q:医生响应时间出现0秒?

A:因为医生数据都是取的最近30天的数据,部分医生最近30天没有接诊问题,所以数据都是0,用户侧可以显示「暂无」。

Q:快速图文如何自动回复?

小新医生属于儿科,请向儿科进行提问:

  • 【接诊并回复】 小新接诊后立即回复。用户再问 就会走原来自动回复的逻辑

  • 【接诊不回复】 小新接诊后不回复。 用户再问 就会走原来自动回复的逻辑