Files
xj-platform/health-emergency/API文档-移动端接口.md
pengjieandClaude e9360d1ad9 feat(emergency): searchExpert接口返回ConDoctor专家数据 + 修复IM UserSig缓存key未区分secretKey的问题
1. searchExpert新增ConDoctor专家字段:
   - 新增ConDoctorMapper直接查询qh_con_doctor表(同库,避免跨模块依赖)
   - EmergencyProfessionUser新增17个@TableField(exist=false)字段承载专家数据
   - searchExpert方法中批量查询ConDoctor并逐字段填充
   - 用户非专家时ConDoctor字段为null
   - 更新API文档-移动端接口.md

2. IM签名缓存优化:
   - TencentCloudImUtil新增secretKey注入
   - Redis key从 silence:im_user_sig:{sdkAppId}:{id}
     改为 silence:im_user_sig:{hash(sdkAppId+secretKey)}:{id}
   - 解决secretKey变更时旧缓存签名仍被命中导致IM请求失败的问题

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 11:08:03 +08:00

423 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 急救模块移动端 API 文档
## 🆕 本次变更(前端联调)
| # | 接口 | 变更类型 | 说明 |
|---|------|:---:|------------------------------------------------------------------------------|
| 1 | `GET /createGroupMagFirstAider` | **废除** | 已废除,功能由 `createGroupMag` 承接 |
| 2 | `GET /order/myParticipatedOrders` | **新增** | App端查询当前登录人参与的应急工单列表(sessionNow=1),返回被救助人信息 |
---
> **基础信息**
> 网关地址: `http://{gateway-host}:{port}`(前端加 `/health-emergency` 前缀)
> 服务直连: `http://{host}:7011`(调试用,不加前缀)
> 认证方式: 请求头 `X-Access-Token: {token}`
> Content-Type: `application/json; charset=utf-8`
> 统一响应格式: `{"success": bool, "message": "...", "code": 200, "result": {...}, "timestamp": ...}`
> 日期格式: `yyyy-MM-dd HH:mm:ss`(东八区)
---
## 目录
1. [应急群聊拉人](#1-应急群聊拉人-apiemergency)
2. [宣教资源](#2-宣教资源-apiemergencyfirstaidresource)
3. [工单列表](#3-工单列表-apiemergencyorder)
---
## 1. 应急群聊拉人 `api/emergency`
> 网关完整路径: `/health-emergency/api/emergency`
### 1.1 搜索专家(专业人员) `GET /order/searchExpert`
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| realname | String | 否 | 姓名,模糊搜索 |
| centerId | String | 否 | 应急中心ID |
| excludeSessionId | String | 否 | 群组ID,用于排除已在群中的人 |
| pageNo | Integer | 否 | 页码,默认1 |
| pageSize | Integer | 否 | 每页条数,默认10 |
```json
// 响应 result
{
"records": [
{
"userId": "xxx", "realname": "张医生", "avatar": "https://...",
"type": "6", "centerId": "xxx",
"post": "主任医师", "school": "北京大学", "edu": "博士",
"goodAt": "心血管内科", "deptName": "急诊科", "phone": "13800000001",
"doctorName": "张医生", "doctorNo": "XJ-001", "photo": "https://...",
"resourceName": "新疆人民医院", "departmentId": "xxx",
"departmentName": "急诊科", "doctorTitle": "主任医师",
"doctorJob": "科长", "doctorStatus": "1", "isAccept": "1",
"doctorType": "1", "audioStatus": "1", "tfJumpHoliday": "0",
"experience": "从事心血管内科临床工作20年...",
"sort": 100, "tfRecommend": "1",
"introduction": "擅长心血管疾病的诊治..."
}
],
"total": 50, "size": 10, "current": 1
}
```
| 字段 | 说明 |
|------|------|
| userId | 用户ID,邀请时传入 memberList |
| realname | 姓名 |
| avatar | 头像 |
| type | 类型,"6"=专业人员 |
| centerId | 应急中心ID |
| post | 职称 |
| school | 毕业院校 |
| edu | 学历 |
| goodAt | 擅长领域 |
| deptName | 科室 |
| phone | 手机号 |
| **↓ 以下字段来源于咨询服务专家表(qh_con_doctor),非专家时为 null** |
| doctorName | 医生姓名 |
| doctorNo | 专家编号 |
| photo | 医生头像 |
| resourceName | 所属医院名称 |
| departmentId | 科室ID |
| departmentName | 科室名称 |
| doctorTitle | 职务(z_doct_lev 字典) |
| doctorJob | 职称(z_doct_job 字典) |
| doctorStatus | 专家状态(doc_status 字典) |
| isAccept | 是否开启咨询(z_doct_sta 字典: 1开启/3关闭) |
| doctorType | 专家类型(z_doct_typ 字典: 1收费/2免费/3其他/4义诊) |
| audioStatus | 是否提供音视频 |
| tfJumpHoliday | 是否跳过节假日 |
| experience | 执业经历 |
| sort | 排序 |
| tfRecommend | 是否推荐(0否/1是) |
| introduction | 简介 |
### 1.2 搜索员工 `GET /order/searchEmployee`
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| realname | String | 否 | 姓名,模糊搜索 |
| orgCode | String | 否 | 部门编码 |
| workNo | String | 否 | 工号 |
| phone | String | 否 | 手机号 |
| excludeSessionId | String | 否 | 群组ID,用于排除已在群中的人 |
| pageNo | Integer | 否 | 页码,默认1 |
| pageSize | Integer | 否 | 每页条数,默认10 |
```json
// 响应 result
{
"records": [
{
"userId": "xxx", "realname": "张三", "avatar": "https://...",
"orgCode": "650000001001", "orgName": "新疆油田公司",
"deptName": "安全环保部", "phone": "13800000000", "workNo": "A00123"
}
],
"total": 200, "size": 10, "current": 1
}
```
| 字段 | 说明 |
|------|------|
| userId | 用户ID,邀请时传入 memberList |
| realname | 姓名 |
| avatar | 头像 |
| orgCode | 部门编码 |
| orgName | 单位名称(二级单位) |
| deptName | 部门名称 |
| phone | 手机号 |
| workNo | 工号 |
### 1.3 创建群聊 `GET /createGroupMag`
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| name | String | 是 | 群聊名称 |
| longitude | String | 是 | 经度 |
| latitude | String | 是 | 纬度 |
| type | String | 否 | 类型 |
> 返回 `groupId`(群组ID),作为后续拉人接口的入参。**此接口会发起音视频通话。**
### 1.4 创建群聊-急救员 `GET /createGroupMagFirstAider` ⚠️ 已废除
> ⚠️ 本接口已废除,功能由 `createGroupMag` 承接,请勿调用。
### 1.5 邀请进群 `POST /addGroupUser`
拉专家或员工加入应急群聊。
```json
// Request Body
{
"groupId": "1907659384834752513",
"memberList": ["userId1", "userId2"],
"memberType": 1
}
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| groupId | String | 是 | 群组ID(即 `createGroupMag` 返回的 `groupId` |
| memberList | List\<String\> | 是 | 要邀请的用户ID列表,最多100个 |
| memberType | Integer | 否 | `0`=专家/专业人员, `1`=员工,**默认0** |
```json
// 响应
{"success": true, "message": "", "result": true}
```
> 已在群中的人员会自动跳过,不会报错。所有选中的人都在群中时返回失败。
### 1.6 移除群成员 `POST /removeGroupUser`
```json
// Request Body
{ "groupId": "1907659384834752513", "memberList": ["userId1"] }
```
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| groupId | String | 是 | 群组ID |
| memberList | List\<String\> | 是 | 要移除的用户ID列表 |
```json
// 响应
{"success": true, "message": "", "result": true}
```
> 只能移除后续拉入的协助人员,不能移除发起人和初始操作/专业人员。
### 1.7 群成员列表 `GET /order/getGroupMemberList`
> ⚠️ 本接口仅查本地 DB,不验证腾讯 IM。**建议改用 1.8 `/order/getActualGroupMemberList`。**
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| sessionId | String | 是 | 群组ID |
```json
// 响应 result
[
{ "orderId": "...", "userId": "发起人id", "userName": "李四", "avatar": "https://...", "phone": "13800000001", "isGroupMember": 1, "memberType": -1 },
{ "orderId": "...", "userId": "操作人员id", "userName": "王五", "avatar": "https://...", "phone": "13800000002", "isGroupMember": 1, "memberType": 0 },
{ "orderId": "...", "userId": "专业人员id", "userName": "张医生", "avatar": "https://...", "phone": "13800000003", "isGroupMember": 1, "memberType": 1 },
{ "orderId": "...", "userId": "员工id", "userName": "张三", "avatar": "https://...", "phone": "13800000004", "isGroupMember": 1, "memberType": 2 },
{ "orderId": "...", "userId": "其他专业人id", "userName": "赵医生", "avatar": "https://...", "phone": "13800000005", "isGroupMember": 0, "memberType": 1 }
]
```
| 字段 | 说明 |
|------|------|
| orderId | 应急单ID |
| userId | 用户ID |
| userName | 姓名 |
| avatar | 头像 |
| phone | 手机号 |
| isGroupMember | `1`=在群中, `0`=可邀请 |
| memberType | `-1`=发起人, `0`=操作人员, `1`=专业人员, `2`=员工 |
### 1.8 实际群成员列表 `GET /order/getActualGroupMemberList` 🆕
> **与 1.7 的区别**:本接口先查本地参与人员,再调用腾讯 IM `get_group_member_info` API 获取群内实际成员,以 IM 成员为准取交集,确保返回的都是 IM 群中真实存在的成员。**不过滤本人,通过 `isSelf` 标识。**
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| sessionId | String | 是 | 群组ID |
```json
// 响应 result(与 getGroupMemberList 格式完全一致,但不过滤本人)
[
{ "orderId": "...", "userId": "当前用户id", "userName": "我", "avatar": "https://...", "phone": "13800000000", "isGroupMember": 1, "memberType": 0, "isSelf": 1 },
{ "orderId": "...", "userId": "发起人id", "userName": "李四", "avatar": "https://...", "phone": "13800000001", "isGroupMember": 1, "memberType": -1, "isSelf": 0 },
{ "orderId": "...", "userId": "专业人员id", "userName": "张医生", "avatar": "https://...", "phone": "13800000003", "isGroupMember": 1, "memberType": 1, "isSelf": 0 }
]
```
| 字段 | 说明 |
|------|------|
| orderId | 应急单ID |
| userId | 用户ID |
| userName | 姓名 |
| avatar | 头像 |
| phone | 手机号 |
| isGroupMember | 始终为 `1`(所有返回成员均经 IM 验证在群中) |
| memberType | `-1`=发起人, `0`=操作人员, `1`=专业人员, `2`=员工 |
| isSelf | `0`=不是本人, `1`=是本人 |
> **建议**:前端优先使用本接口获取群成员列表,1.7 接口后续可淘汰。
### 1.9 当前登录人参与的应急工单列表 `GET /order/myParticipatedOrders` 🆕
> App端接口:查询当前登录人参与过的、正在应急(`sessionNow=1`)的工单列表,返回被救助人信息。
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| pageNo | Integer | 否 | 页码,默认1 |
| pageSize | Integer | 否 | 每页条数,默认10 |
```json
// 响应 result
{
"records": [
{
"orderId": "2074305914885640194",
"sessionId": "2074305910070706176",
"salvageUserName": "张三",
"salvageUserSex": "1",
"salvageUserAvatar": "https://fileserver.yg.dt.io/xxx.png",
"salvageUserAge": 35,
"salvageUserMobile": "13800000000",
"orgName": "新疆油田公司",
"deptName": "安全环保部",
"orderStatus": "1",
"initTime": "2026-07-20 10:30:00"
}
],
"total": 5, "size": 10, "current": 1
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| orderId | String | 应急单ID |
| sessionId | String | 会话ID |
| salvageUserName | String | 被救助人姓名 |
| salvageUserSex | String | 被救助人性别(字典: sex2) |
| salvageUserAvatar | String | 被救助人头像 |
| salvageUserAge | Integer | 被救助人年龄 |
| salvageUserMobile | String | 被救助人手机号 |
| orgName | String | 单位名称(二级单位) |
| deptName | String | 部门名称 |
| orderStatus | String | 应急单状态(字典: emergency_order_status |
| initTime | Date | 发起时间 |
### 1.10 memberType 字典
| 值 | 含义 | 场景 |
|:---:|------|------|
| -1 | 发起人 | 创建应急的人 |
| 0 | 操作人员 | 应急中心调度员 |
| 1 | 专业人员 | 专家/医生 |
| 2 | 员工 | 普通员工(被拉入群) |
### 典型调用流程
**应急就医(带音视频通话)**
```
1. 创建群聊
GET /health-emergency/api/emergency/createGroupMag?name=xxx&longitude=xxx&latitude=xxx
2. 查看当前成员 → 搜索要拉的人 → 确认邀请 → 移除成员 → 刷新列表
GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId}
(以下步骤同急救员流程)
```
**急救员**
```
1. 创建群聊
GET /health-emergency/api/emergency/createGroupMag?name=xxx&longitude=xxx&latitude=xxx
2. 查看当前成员(🔀 替换原 /order/getGroupMemberList
GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId}
→ 返回经腾讯IM验证的实际群成员,新增 isSelf 字段区分本人
3. 搜索要拉的人
GET /health-emergency/api/emergency/order/searchExpert?realname=张&excludeSessionId={groupId}
GET /health-emergency/api/emergency/order/searchEmployee?realname=李&excludeSessionId={groupId}
→ 展示搜索结果,用户勾选
4. 确认邀请
POST /health-emergency/api/emergency/addGroupUser
{"groupId": "{groupId}", "memberList": ["id1","id2"], "memberType": 0}
5. 移除成员
POST /health-emergency/api/emergency/removeGroupUser
{"groupId": "{groupId}", "memberList": ["id1"]}
6. 刷新成员列表(🔀 替换原 /order/getGroupMemberList
GET /health-emergency/api/emergency/order/getActualGroupMemberList?sessionId={groupId}
```
---
## 2. 宣教资源 `api/emergency/firstAid/resource`
> 网关完整路径: `/health-emergency/api/emergency/firstAid/resource`
### 2.1 根据宣教ID查询详情 `GET /resource/queryById`
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| id | String | 是 | 宣教资源ID |
```json
// 响应 result
{
"id": "...",
"title": "心肺复苏指南",
"summary": "摘要简介",
"contentType": "IMAGE_TEXT",
"categoryId": "cat1",
"categoryName": "心肺复苏",
"applicableScenario": "适用场景",
"tags": "tag1,tag2",
"tagNameList": ["成人", "初级"],
"content": "<h1>富文本HTML正文</h1>",
"coverImage": "https://example.com/cover.jpg",
"fileUrl": "[\"https://example.com/file1.pdf\"]",
"viewCount": 100,
"favoriteCount": 20,
"isFavorited": false,
"effectiveTime": "2026-07-01 00:00:00",
"expiryTime": "2026-12-31 23:59:59",
"createTime": "2026-07-01 10:00:00",
"updateTime": "2026-07-05 14:30:00"
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| id | String | 资源ID |
| title | String | 标题 |
| summary | String | 摘要/简介 |
| contentType | String | 内容类型:`IMAGE_TEXT`/`VIDEO`/`AUDIO`/`PDF` |
| categoryId | String | 分类ID |
| categoryName | String | 分类名称 |
| applicableScenario | String | 适用场景 |
| tags | String | 标签ID(逗号分隔) |
| tagNameList | List\<String\> | 标签名称列表 |
| content | String | 富文本正文HTML |
| coverImage | String | 封面图URL |
| fileUrl | String | 附件URL列表(JSON数组字符串) |
| viewCount | Integer | 浏览次数 |
| favoriteCount | Integer | 收藏次数 |
| isFavorited | Boolean | 当前用户是否已收藏 |
| effectiveTime | Date | 生效时间 |
| expiryTime | Date | 失效时间 |
| createTime | Date | 创建时间 |
| updateTime | Date | 更新时间 |
---
## 3. 工单列表 `/api/emergency/order`
> 网关完整路径: `/health-emergency/api/emergency/order`
### 3.1 当前登录人参与的应急工单列表 `GET /myParticipatedOrders` 🆕
**业务逻辑**
1.`QH_EME_ORDER_OPERATOR` 表获取当前登录人参与的所有工单ID(去重)
2.`QH_EME_ORDER` 表筛选 `sessionNow=1` 的工单,按创建时间倒序
3. 返回被救助人(`salvage` 字段)信息 + 单位/部门名称
> **调用时机**:登录用户在急救页面查看自己正在参与中的应急工单。仅返回 IM 会话进行中的工单(`sessionNow=1`)。
请求和响应格式见 **[1.9 当前登录人参与的应急工单列表](#19-当前登录人参与的应急工单列表-get-ordermyparticipatedorders-)**。