# 第三方登录接口文档 > 版本:V2.2 > 更新日期:2026-07-15 > 变更说明:新增 `remarks` 字段,用于传递拒绝原因等备注信息 --- ## 1. 概述 本套接口用于第三方系统与药汇购之间的用户数据互通,包含**两个接口**: | # | 接口 | 路径 | 鉴权 | 职责 | |---|------|------|------|------| | **登录接口** | 按手机号签发 Token | `POST /api/auth/external/miniapp-token` | X-Api-Key | 查找/注册用户 + 更新资料 + 同步入驻信息,返回 Token | | **同步接口** | 信息同步(独立) | `POST /api/auth/external/sync` | X-Api-Key | 更新用户资料 + 同步入驻信息(需用户已存在) | > 💡 **两种使用方式**: > - **一步到位**:登录接口直接传入全部字段(手机号 + 用户信息 + 入驻信息),一次请求完成登录和信息同步 > - **分步调用**:先调登录接口获取 Token,再调同步接口更新详细资料和入驻资质 **核心特性:** - 按手机号自动查找或注册用户 - **支持双渠道**:`loginSource=MINIAPP`(小程序,30天过期,不踢PC) / `loginSource=PC`(PC端,1小时过期,踢旧PC会话),不传默认 MINIAPP - 支持传入 `skipRefreshToken=true` 仅获取 accessToken,不生成/覆盖 refreshToken - 新用户自动初始化 **PLUS 会员(普通会员)** - 第三方已审核用户(`status=1`)自动赠送 **30 天 PRO 高级会员** - 支持传入小程序 `openId` / `unionId`,支付场景无需重复授权 - **已存在用户的入驻信息仅允许更新审核状态,其他字段保留原值**(后续由运营端确认更新) --- ## 2. 鉴权方式 两个接口均使用 **API Key** 鉴权,在请求头中传入: ``` X-Api-Key: {我方提供的API Key} ``` | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `X-Api-Key` | String | 是 | 接口鉴权密钥 | | `Content-Type` | String | 是 | 固定值 `application/json` | > ⚠️ 请妥善保管 API Key,不要泄露到前端代码或客户端。请在后端服务中调用此接口。 --- ## 3. 登录接口(`miniapp-token`) ### 3.1 接口信息 | 项目 | 说明 | |------|------| | **接口名称** | 外部系统换取登录 Token | | **请求方式** | `POST` | | **接口路径** | `/api/auth/external/miniapp-token` | | **Content-Type** | `application/json` | > 💡 登录接口支持**登录 + 信息同步一体化**:除了必传手机号外,可选择性传入用户资料和入驻信息字段,一步完成登录和信息同步。 ### 3.2 请求参数 #### 必填字段 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `phone` | String | 是 | 用户手机号 | #### 登录控制字段 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `inviteCode` | String | 否 | 邀请码(仅新用户注册时生效) | | `loginSource` | String | 否 | 登录来源。`MINIAPP`(小程序,30天过期,不踢PC) / `PC`(PC端,1小时过期,踢旧PC会话)。**不传默认 `MINIAPP`** | | `skipRefreshToken` | Boolean | 否 | `true`=仅返回 accessToken(refreshToken 返回 null),适用于客户端已持有 refreshToken 仅需换 accessToken 的场景;**不传默认 `false`** | #### 用户信息字段(全部可选,非空才写入/覆盖) | 参数名 | 类型 | 必填 | 说明 | 对应我方字段 | |--------|------|------|------|--------------| | `username` | String | 否 | 用户名(保存到 userName 字段;若未传 nickname,则自动作为 nickname 的兜底值) | `user_name` | | `nickname` | String | 否 | 昵称(不传则依次取 username、手机号后4位生成) | `nickname` | | `pharmacyName` | String | 否 | 药店名称 | `pharmacy_name` | | `province` | String | 否 | 省 | `province` | | `city` | String | 否 | 市 | `city` | | `district` | String | 否 | 区 | `district` | | `pharmacyAddress` | String | 否 | 药店详细地址 | `pharmacy_address` | | `contactPerson` | String | 否 | 联系人 | `contact_person` | | `contactPhone` | String | 否 | 联系电话 | `contact_phone` | | `businessLicenseNo` | String | 否 | 营业执照号(统一社会信用代码) | `business_license_no` | | `openId` | String | 否 | 微信小程序 openId(保存到 wechat_mini_open_id) | `wechat_mini_open_id` | | `unionId` | String | 否 | 微信开放平台 unionId(跨应用用户统一标识) | `wechat_union_id` | #### 入驻信息字段(全部可选,传入后同步写入 t_business_license) | 参数名 | 类型 | 必填 | 说明 | 对应我方字段 | |--------|------|------|------|--------------| | `storeName` | String | 否 | 店铺名称(不传则使用 pharmacyName 兜底) | `store_name` | | `terminalType` | String | 否 | 终端类型:`SINGLE`-单店 / `CHAIN`-连锁 / `CLINIC`-诊所 / `COMMUNITY_HEALTH`-社康 | `terminal_type` | | `businessLicenseUrl` | String | 否 | 营业执照图片URL | `license_image_url` | | `drugLicenseUrl` | String | 否 | 药品经营许可证图片URL | `drug_license_url` | | `medicalDeviceClass2Url` | String | 否 | 二类医疗器械备案图片URL | `medical_device_class2_url` | | `medicalDeviceClass3Url` | String | 否 | 三类医疗器械备案图片URL | `medical_device_class3_url` | | `status` | Integer | 否 | **审核状态**:`0`-审核中 / `1`-审核成功 / `2`-审核失败(不传默认`0`) | — | | `remarks` | String | 否 | 备注(如拒绝原因等) | `remark` | ### 3.3 请求示例 #### 最简请求(默认小程序,30天过期) ```json { "phone": "13800138000" } ``` #### PC端登录(1小时过期) ```json { "phone": "13800138000", "loginSource": "PC" } ``` #### 仅刷新 accessToken(不生成新 refreshToken) ```json { "phone": "13800138000", "loginSource": "MINIAPP", "skipRefreshToken": true } ``` #### 一步到位(登录 + 用户信息 + 入驻信息) ```json { "phone": "13800138000", "loginSource": "MINIAPP", "username": "yaohuigou001", "nickname": "药汇购001", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "pharmacyAddress": "建国路88号", "contactPerson": "张三", "contactPhone": "13800138000", "businessLicenseNo": "91110105MA12345678", "openId": "oJx1x5xxxxxxxxxxxxxxxxx", "unionId": "oABCD1234xxxxxxxxxxxxxx", "storeName": "北京同仁堂大药房(朝阳分店)", "terminalType": "SINGLE", "businessLicenseUrl": "https://oss.example.com/yyzz.jpg", "drugLicenseUrl": "https://oss.example.com/ypjy.jpg", "medicalDeviceClass2Url": "https://oss.example.com/elqx.jpg", "medicalDeviceClass3Url": "https://oss.example.com/slqx.jpg", "status": 1, "remarks": "资料审核不通过,缺少药品经营许可证" } ``` ### 3.4 响应参数 #### 通用响应结构 ```json { "code": 200, "message": "success", "data": { } } ``` | 字段 | 类型 | 说明 | |------|------|------| | `code` | int | 状态码,`200` 表示成功 | | `message` | String | 提示信息 | | `data` | Object | 登录响应数据 | #### data 字段 | 参数名 | 类型 | 说明 | |--------|------|------| | `userId` | String | 用户ID(字符串,避免JS大数精度丢失) | | `accessToken` | String | 访问令牌 | | `refreshToken` | String | 刷新令牌(`skipRefreshToken=true` 时为 null) | | `expiresIn` | Long | accessToken有效期(秒):MINIAPP=2592000(30天),PC=3600(1小时) | | `newUser` | Boolean | 是否为新注册用户 | | `needBindPhone` | Boolean | 始终返回`false` | | `role` | String | 固定值`"USER"` | | `userInfo` | Object | 用户基本信息 | #### userInfo 字段 | 参数名 | 类型 | 说明 | |--------|------|------| | `id` | String | 用户ID | | `phone` | String | 手机号 | | `nickname` | String | 昵称 | | `userName` | String | 第三方传入的原始用户名 | | `avatar` | String | 头像 URL | | `wechatBound` | Boolean | 是否已绑定微信 | | `loginSource` | String | 登录来源,`"MINIAPP"` 或 `"PC"` | | `pharmacyName` | String | 药店名称 | | `province` | String | 省 | | `city` | String | 市 | | `district` | String | 区 | | `membershipLevel` | String | 会员等级代码(PLUS / PRO / ULTRA) | | `membershipLevelName` | String | 会员等级名称 | | `licenseStatus` | String | 营业执照审核状态 | | `inviterId` | String | 邀请人用户ID | | `inviterNickname` | String | 邀请人昵称 | | `hasBoundInviteCode` | Boolean | 是否已绑定邀请码 | | `createTime` | String | 注册时间 | #### 响应示例 ```json { "code": 200, "message": "success", "data": { "userId": "1234567890123456789", "accessToken": "eyJhbGciOiJIUzI1NiJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiJ9...", "expiresIn": 2592000, "newUser": false, "needBindPhone": false, "role": "USER", "userInfo": { "id": "1234567890123456789", "phone": "138****8000", "nickname": "用户8000", "userName": null, "avatar": null, "wechatBound": false, "loginSource": "MINIAPP", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "membershipLevel": "PRO", "membershipLevelName": "高级会员", "licenseStatus": "APPROVED", "inviterId": "1234567890123456788", "inviterNickname": "邀请人昵称", "hasBoundInviteCode": true, "createTime": "2025-01-01T12:00:00" } } } ``` --- ## 4. 信息同步接口(`sync`) ### 4.1 接口信息 | 项目 | 说明 | |------|------| | **接口名称** | 外部系统信息同步 | | **请求方式** | `POST` | | **接口路径** | `/api/auth/external/sync` | | **Content-Type** | `application/json` | > ⚠️ 用户必须先通过登录接口创建后,才能调用本接口同步信息。如果手机号对应的用户不存在,将返回错误。 ### 4.2 请求参数 #### 必填字段 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `phone` | String | 是 | 用户手机号(用于定位用户) | #### 用户信息字段(全部可选,非空才覆盖) | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `username` | String | 否 | 用户名 | | `nickname` | String | 否 | 昵称 | | `pharmacyName` | String | 否 | 药店名称 | | `province` | String | 否 | 省 | | `city` | String | 否 | 市 | | `district` | String | 否 | 区 | | `pharmacyAddress` | String | 否 | 药店详细地址 | | `contactPerson` | String | 否 | 联系人 | | `contactPhone` | String | 否 | 联系电话 | | `businessLicenseNo` | String | 否 | 营业执照号 | | `openId` | String | 否 | 微信小程序 openId | | `unionId` | String | 否 | 微信开放平台 unionId | #### 入驻信息字段(全部可选) > ⚠️ **重要**:入驻信息已存在记录时,**仅允许更新审核状态(status)**,其他字段(店名、地区、证照图片等)保留原值,不允许通过第三方接口覆盖。后续修改需通过运营端确认。 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | `storeName` | String | 否 | 店铺名称(不传则用 pharmacyName 兜底) | | `terminalType` | String | 否 | 终端类型 | | `businessLicenseUrl` | String | 否 | 营业执照图片URL | | `drugLicenseUrl` | String | 否 | 药品经营许可证图片URL | | `medicalDeviceClass2Url` | String | 否 | 二类医疗器械备案图片URL | | `medicalDeviceClass3Url` | String | 否 | 三类医疗器械备案图片URL | | `status` | Integer | 否 | **审核状态**:`0`-审核中 / `1`-审核成功 / `2`-审核失败(不传默认`0`) | | `remarks` | String | 否 | 备注(如拒绝原因等) | ### 4.3 审核状态说明 - `status=1`:审核状态设为 `APPROVED`(审核通过),**首次通过赠送 30 天 PRO 高级会员** - `status=0` 或不传:审核状态设为 `PENDING`(待运营审核) - `status=2`:审核状态设为 `REJECTED`(审核失败) - PENDING/REJECTED 状态可随 `status` 更新(含互转、升级到 APPROVED) - **已 APPROVED 的记录再次同步时**:更新入驻字段并重置为 PENDING,需重新审核;奖励仅首次发放,不会重复 ### 4.4 入驻信息更新规则 > 💡 **核心原则:以第三方传入的最新数据为准**,非空字段直接覆盖本地记录。 | 场景 | 行为 | |------|------| | **首次同步**(无入驻记录) | 创建新记录,写入全部字段 | | **已存在记录(非 APPROVED)** | 以第三方传入字段覆盖本地,同时更新审核状态 | | **已存在记录(已 APPROVED)** | 以第三方传入字段覆盖本地,状态重置为 PENDING 待重新审核;奖励标记保留,不重复发放 | | 需修改入驻信息 | 第三方再次调用同步接口即可更新,无需人工介入 | ### 4.5 请求示例 ```json { "phone": "13800138000", "username": "yaohuigou001", "nickname": "药汇购新昵称", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "status": 1, "remarks": "资料审核不通过" } ``` ### 4.6 响应 ```json { "code": 200, "message": "success", "data": null } ``` > 响应 `code=200` 表示同步成功,无 data 内容。 --- ## 5. 错误码 | 错误码 | 说明 | 处理建议 | |--------|------|----------| | `200` | 成功 | — | | `401` | API Key 无效或缺失 | 检查`X-Api-Key`请求头 | | `400` | 参数校验失败 | 检查`phone`是否为空 | | `400` | 用户不存在(同步接口) | 先调用登录接口创建用户 | | `500` | 服务器内部错误 | 重试,持续失败联系我方 | --- ## 6. Token 使用说明 ### 6.1 携带 Token ``` Authorization: Bearer {accessToken} ``` ### 6.2 Token 刷新 ``` POST /api/auth/refresh Content-Type: application/json { "refreshToken": "{refreshToken}" } ``` > 刷新后旧 refreshToken 立即失效(Token 旋转机制)。 ### 6.3 心跳保活 ``` POST /api/auth/heartbeat Authorization: Bearer {currentAccessToken} ``` > **建议间隔**:PC 端约 48 分钟(3600 × 80%),小程序端约 24 天(2592000 × 80%)。 > 💡 **小程序端建议**:已持有有效 refreshToken 时,调用登录接口传 `skipRefreshToken=true` 仅换新 accessToken,避免频繁生成 refreshToken。 --- ## 7. 注意事项 | # | 说明 | |---|------| | 1 | 登录接口支持一步完成登录+信息同步(传入全部字段即可) | | 2 | 同步接口要求用户已存在(先调用登录接口创建),否则返回错误 | | 3 | PC端单设备互踢(递增 tokenVersion),小程序与PC互不干扰 | | 4 | Token 过期时间:PC=1小时,小程序=30天 | | 5 | 手机号未注册时自动创建账号并初始化 **PLUS 会员** | | 6 | `inviteCode`仅新用户注册时生效 | | 7 | API Key请在后端调用,勿暴露到客户端 | | 8 | `skipRefreshToken=true` 时 refreshToken 返回 null | | 9 | 每次刷新后旧 refreshToken 立即作废,请保存最新的 | | 10 | 同步接口仅更新非空字段,不会清空已有数据 | | 11 | `status=1` 时入驻状态直接审核通过,**首次**通过赠送 30 天 PRO 高级会员(不重复) | | 12 | 已审核通过的入驻记录再次同步时,更新字段并重置为 PENDING 待重新审核,不重复奖励 | | 13 | 传入 `openId`/`unionId` 后,小程序支付无需再次 `wx.login` 授权 | | 14 | **入驻信息已存在时允许更新字段**,审核通过后的记录重新同步会触发重新审核流程 | --- ## 8. 调用示例 ### 完整流程(cURL) ```bash # 一步到位:登录 + 同步用户信息和入驻资质 curl -X POST "https://your-domain.com/api/auth/external/miniapp-token" \ -H "Content-Type: application/json" \ -H "X-Api-Key: your-api-key-here" \ -d '{ "phone": "13800138000", "loginSource": "PC", "username": "yaohuigou001", "nickname": "药汇购001", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "pharmacyAddress": "建国路88号", "contactPerson": "张三", "contactPhone": "13800138000", "businessLicenseNo": "91110105MA12345678", "storeName": "北京同仁堂大药房(朝阳分店)", "terminalType": "SINGLE", "businessLicenseUrl": "https://oss.example.com/yyzz.jpg", "drugLicenseUrl": "https://oss.example.com/ypjy.jpg", "status": 1 }' # 后续独立更新信息(如仅变更用户昵称) curl -X POST "https://your-domain.com/api/auth/external/sync" \ -H "Content-Type: application/json" \ -H "X-Api-Key: your-api-key-here" \ -d '{ "phone": "13800138000", "nickname": "新昵称" }' ``` --- ## 9. 字段映射表 ### 9.1 用户信息(写入 t_users) | 第三方传入字段 | 我方数据库字段 | 说明 | |----------------|----------------|------| | `phone` | `phone` | 手机号(必填,用于定位用户) | | `username` | `user_name` | 用户名 | | `nickname` | `nickname` | 昵称 | | `pharmacyName` | `pharmacy_name` | 药店名称 | | `province` | `province` | 省 | | `city` | `city` | 市 | | `district` | `district` | 区 | | `pharmacyAddress` | `pharmacy_address` | 药店详细地址 | | `contactPerson` | `contact_person` | 联系人 | | `contactPhone` | `contact_phone` | 联系电话 | | `businessLicenseNo` | `business_license_no` | 营业执照号 | | `openId` | `wechat_mini_open_id` | 微信小程序 openId | | `unionId` | `wechat_union_id` | 微信开放平台 unionId | ### 9.2 入驻信息(写入 t_business_license) | 第三方传入字段 | 我方数据库字段 | 说明 | |----------------|----------------|------| | `storeName` | `store_name` | 店铺名称(不传则用 pharmacyName) | | `terminalType` | `terminal_type` | 终端类型 | | `businessLicenseUrl` | `license_image_url` | 营业执照图片URL | | `drugLicenseUrl` | `drug_license_url` | 药品经营许可证图片URL | | `medicalDeviceClass2Url` | `medical_device_class2_url` | 二类医疗器械备案图片URL | | `medicalDeviceClass3Url` | `medical_device_class3_url` | 三类医疗器械备案图片URL | | `status` | `review_status` | 审核状态:`0`→PENDING / `1`→APPROVED / `2`→REJECTED | | `remarks` | `remark` | 备注(如拒绝原因等) | > 💡 入驻记录保存时,以下字段由系统自动填入: > - `store_address` ← 从 `pharmacyAddress` 提取 > - `credit_code` ← 从 `businessLicenseNo` 提取 > - `sync_source` ← 固定为 `MINIAPP_SYNC` > - `show_verified_badge` ← 首次审核通过时设为 `true` --- ## 10. 联系方式 如有问题,请联系我方技术支持。 --- ## 11. 变更记录 | 版本 | 日期 | 变更内容 | |------|------|----------| | V1.0 ~ V1.7 | 2026-07-07 ~ 2026-07-11 | 初始版本、字段扩展、状态字段、loginSource/skipRefreshToken | | V2.0 | 2026-07-11 | 拆分为登录接口 + 信息同步接口;登录接口仅接收登录字段 | | **V2.1** | 2026-07-11 | **登录接口恢复全部字段**(登录与信息同步可在同一请求完成);同步接口已存在入驻记录时仅允许更新审核状态 | | **V2.2** | 2026-07-15 | 新增 `remarks` 字段(登录+同步接口均支持),用于传递拒绝原因等备注信息 |