第三方登录接口文档.md 19 KB

第三方登录接口文档

版本:V1.6
更新日期:2026-07-10
变更说明:licenseApproved(Boolean)改为 status(Integer:0-审核中 / 1-审核成功 / 2-审核失败),支持审核失败状态;调用第三方同步接口同步传 status


1. 概述

本接口用于第三方系统通过用户手机号换取登录 Token,实现免密登录。系统会自动查找或注册用户,返回访问令牌和刷新令牌。同时支持第三方系统传入用户详细信息(用户名、药店名称、地址、联系人等),由我方负责存储,第三方无需我方再次维护用户信息。

核心特性:

  • 按手机号自动查找或注册用户
  • 支持第三方传入用户名、药店信息等,自动保存到用户表
  • 支持第三方传入入驻信息(资质图片URL等),同步写入 t_business_license,免去额外调用入驻接口
  • 不递增 tokenVersion,不踢出 PC 端已有会话
  • 新用户自动初始化 PLUS 会员(普通会员)
  • 第三方已审核用户(status=1)自动赠送 30 天 PRO 高级会员
  • 支持传入小程序 openId / unionId,一次性保存后后续支付等场景直接从用户表读取,无需重复授权

2. 接口信息

项目 说明
接口名称 外部系统换取小程序 Token
请求方式 POST
接口路径 /api/auth/external/miniapp-token
Content-Type application/json
字符编码 UTF-8

3. 鉴权方式

使用 API Key 鉴权,在请求头中传入:

X-Api-Key: {我方提供的API Key}

API Key 格式建议:32 位随机十六进制字符串(大小写字母 + 数字)。
参考示例:a3f8c21e67b94d0e5f1a6c8d3b9e2071

⚠️ 请妥善保管 API Key,不要泄露到前端代码或客户端。请在后端服务中调用此接口。


4. 请求参数

4.1 请求头(Headers)

参数名 类型 必填 说明
X-Api-Key String 接口鉴权密钥
Content-Type String 固定值 application/json

4.2 请求体(Body)

基础字段

参数名 类型 必填 说明
phone String 用户手机号
inviteCode String 邀请码(仅新用户注册时生效)

用户信息字段(第三方传入,自动保存到用户表)

参数名 类型 必填 说明 对应我方字段
username String 用户名(保存到 userName 字段;若未传 nickname,则自动作为 nickname 的兜底值) nickname / userName
nickname String 昵称(不传则使用 username,再不传则按手机号生成) 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(第三方从小程序 wx.login 获取后传入,保存到 wechat_mini_open_id。传入后支付场景无需重复授权) wechat_mini_open_id
unionId String 微信开放平台 unionId(用于跨应用用户统一标识,保存到 wechat_union_id) wechat_union_id

入驻信息字段(对应 /api/business-license/upload,均为可选,传入后同步写入 t_business_license)

参数名 类型 必填 说明 对应我方字段
storeName String 店铺名称(不传则使用 pharmacyName) store_name
terminalType String 终端类型:1-单店 / 2-连锁 / 3-诊所、社康等 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)。1 = 第三方已审核通过,无需我方再次审核,直接设为 APPROVED 并赠送 30 天高级会员

💡 审核状态说明

  • 如果第三方系统已完成资质审核,传 status: 1,我方将直接标记为"审核通过"并赠送 30 天 PRO 高级会员
  • 如果第三方审核失败,传 status: 2,我方将标记为"审核失败"
  • 如果第三方尚未审核或不确定,传 status: 0 或不传(默认 PENDING),由我方运营审核
  • 已审核通过的入驻记录不会因后续调用而降级
  • 只要请求中包含任一入驻信息字段(storeName / terminalType / businessLicenseUrl / drugLicenseUrl / medicalDeviceClass2Url / medicalDeviceClass3Url),即自动写入 t_business_license
  • status=1:审核状态设为 APPROVED(审核通过),首次通过赠送 30 天 PRO 高级会员
  • status=0 或不传:审核状态设为 PENDING(待运营审核)
  • status=2:审核状态设为 REJECTED(审核失败)
  • 已存在入驻记录:已 APPROVED 状态不降级,PENDING/REJECTED 状态可随 status 更新(含互转、升级到 APPROVED)
  • 省/市/区/地址/联系人/电话等字段会自动从用户信息字段中提取填入

💡 字段更新规则

  • 新用户会写入所有传入字段
  • 已存在用户仅更新非空字段(不会清空已有数据)

💡 nickname 优先级nickname > username > 手机号生成(如 用户8000

💡 建议:第三方每次调用时传入最新的用户信息,我方会自动同步更新,无需额外维护。

4.3 请求示例

最简请求(仅必填字段)

{
  "phone": "13800138000"
}

完整请求(含用户信息 + 入驻信息)

{
  "phone": "13800138000",
  "inviteCode": "ABC123",
  "username": "yaohuigou001",
  "nickname": "药汇购001",
  "pharmacyName": "北京同仁堂大药房",
  "province": "北京市",
  "city": "北京市",
  "district": "朝阳区",
  "pharmacyAddress": "建国路88号",
  "contactPerson": "张三",
  "contactPhone": "13800138000",
  "businessLicenseNo": "91110105MA12345678",
  "openId": "oJx1x5xxxxxxxxxxxxxxxxx",
  "unionId": "oABCD1234xxxxxxxxxxxxxx",
  "storeName": "北京同仁堂大药房(朝阳分店)",
  "terminalType": "1",
  "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
}

5. 响应参数

5.1 通用响应结构

{
  "code": 200,
  "message": "success",
  "data": { }
}
字段 类型 说明
code int 状态码,200 表示成功
message String 提示信息
data Object 登录响应数据

5.2 data 字段

参数名 类型 说明
userId String 用户ID(字符串,避免JS大数精度丢失)
accessToken String 访问令牌
refreshToken String 刷新令牌
expiresIn Long accessToken有效期(秒),默认7200
newUser Boolean 是否为新注册用户
needBindPhone Boolean 始终返回false
role String 固定值"USER"
userInfo Object 用户基本信息

5.3 userInfo 字段说明

参数名 类型 说明
id String 用户ID
phone String 手机号
nickname String 昵称(即第三方传入的 nickname 或 username)
userName String 第三方传入的原始用户名
avatar String 头像 URL
wechatBound Boolean 是否已绑定微信
loginSource String 登录来源,"MINIAPP" 表示小程序
pharmacyName String 药店名称
province String
city String
district String
membershipLevel String 会员等级代码(PLUS / PRO / ULTRA)
membershipLevelName String 会员等级名称
licenseStatus String 营业执照审核状态:NOT_SUBMITTED / PENDING / APPROVED / REJECTED
inviterId String 邀请人用户ID(未绑定邀请码则为 null)
inviterNickname String 邀请人昵称
hasBoundInviteCode Boolean 是否已绑定邀请码
createTime String 注册时间

5.4 响应示例

{
  "code": 200,
  "message": "success",
  "data": {
    "userId": "1234567890123456789",
    "accessToken": "eyJhbGciOiJIUzI1NiJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiJ9...",
    "expiresIn": 7200,
    "newUser": false,
    "needBindPhone": false,
    "role": "USER",
    "userInfo": {
      "id": "1234567890123456789",
      "phone": "138****8000",
      "nickname": "药汇购001",
      "userName": "yaohuigou001",
      "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"
    }
  }
}

6. 错误码

错误码 说明 处理建议
200 成功
401 API Key 无效或缺失 检查X-Api-Key请求头
400 参数校验失败 检查phone是否为空
500 服务器内部错误 重试,持续失败联系我方

7. Token 使用说明

7.1 携带 Token

后续所有业务接口请求需在请求头中携带 accessToken:

Authorization: Bearer {accessToken}

7.2 Token 刷新

accessToken 过期后,使用 refreshToken 换取新 Token:

POST /api/auth/refresh
Content-Type: application/json

{ "refreshToken": "{refreshToken}" }

刷新后旧 refreshToken 立即失效(Token 旋转机制)。

7.3 心跳保活

建议客户端定时调用心跳接口,保持登录状态:

POST /api/auth/heartbeat
Authorization: Bearer {currentAccessToken}

建议间隔 = accessToken有效期 × 80%,约5760秒。


8. 注意事项

# 说明
1 PC端严格单设备,小程序与PC互不干扰,同一账号可同时登录
2 手机号未注册时自动创建账号并初始化 PLUS 会员(普通会员)
3 inviteCode仅新用户注册时生效
4 API Key请在后端调用,勿暴露到客户端
5 accessToken默认2小时,refreshToken默认7天
6 每次刷新后旧refreshToken立即作废,请保存最新的
7 用户信息字段(username、药店信息等)每次调用都会同步更新,建议传入最新数据
8 username 会保存到我方的 nickname 字段,不需要额外维护用户名表
9 传入入驻信息字段后自动创建/更新入驻记录,无需再调用 /api/business-license/upload
10 status=1 时入驻状态直接审核通过,首次通过赠送 30 天 PRO 高级会员
11 已审核通过的入驻记录(APPROVED)不会因后续调用而降级
12 传入 openId/unionId 后,小程序支付无需再次 wx.login 授权,后端直接从 t_user.wechat_mini_open_id 读取

9. 调用示例

cURL

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",
    "username": "yaohuigou001",
    "pharmacyName": "北京同仁堂大药房",
    "province": "北京市",
    "city": "北京市",
    "district": "朝阳区",
    "pharmacyAddress": "建国路88号",
    "contactPerson": "张三",
    "contactPhone": "13800138000",
    "businessLicenseNo": "91110105MA12345678",
    "openId": "oJx1x5xxxxxxxxxxxxxxxxx",
    "unionId": "oABCD1234xxxxxxxxxxxxxx",
    "storeName": "北京同仁堂大药房(朝阳分店)",
    "terminalType": "1",
    "businessLicenseUrl": "https://oss.example.com/yyzz.jpg",
    "drugLicenseUrl": "https://oss.example.com/ypjy.jpg",
    "status": 1
  }'

Java (OkHttp)

OkHttpClient client = new OkHttpClient();

String json = "{"
    + "\"phone\": \"13800138000\","
    + "\"username\": \"yaohuigou001\","
    + "\"pharmacyName\": \"北京同仁堂大药房\","
    + "\"province\": \"北京市\","
    + "\"city\": \"北京市\","
    + "\"district\": \"朝阳区\","
    + "\"pharmacyAddress\": \"建国路88号\","
    + "\"contactPerson\": \"张三\","
    + "\"contactPhone\": \"13800138000\","
    + "\"businessLicenseNo\": \"91110105MA12345678\","
    + "\"openId\": \"oJx1x5xxxxxxxxxxxxxxxxx\","
    + "\"unionId\": \"oABCD1234xxxxxxxxxxxxxx\","
    + "\"storeName\": \"北京同仁堂大药房(朝阳分店)\","
    + "\"terminalType\": \"1\","
    + "\"businessLicenseUrl\": \"https://oss.example.com/yyzz.jpg\","
    + "\"drugLicenseUrl\": \"https://oss.example.com/ypjy.jpg\","
    + "\"status\": 1"
    + "}";

RequestBody body = RequestBody.create(json, MediaType.parse("application/json"));
Request request = new Request.Builder()
    .url("https://your-domain.com/api/auth/external/miniapp-token")
    .post(body)
    .addHeader("X-Api-Key", "your-api-key-here")
    .addHeader("Content-Type", "application/json")
    .build();

try (Response response = client.newCall(request).execute()) {
    System.out.println(response.body().string());
}

Python (requests)

import requests

url = "https://your-domain.com/api/auth/external/miniapp-token"
headers = {
    "X-Api-Key": "your-api-key-here",
    "Content-Type": "application/json"
}
data = {
    "phone": "13800138000",
    "username": "yaohuigou001",
    "pharmacyName": "北京同仁堂大药房",
    "province": "北京市",
    "city": "北京市",
    "district": "朝阳区",
    "pharmacyAddress": "建国路88号",
    "contactPerson": "张三",
    "contactPhone": "13800138000",
    "businessLicenseNo": "91110105MA12345678",
    "openId": "oJx1x5xxxxxxxxxxxxxxxxx",
    "unionId": "oABCD1234xxxxxxxxxxxxxx",
    "storeName": "北京同仁堂大药房(朝阳分店)",
    "terminalType": "1",
    "businessLicenseUrl": "https://oss.example.com/yyzz.jpg",
    "drugLicenseUrl": "https://oss.example.com/ypjy.jpg",
    "status": 1
}

response = requests.post(url, json=data, headers=headers)
print(response.json())

10. 字段映射表

第三方传入字段与我方数据库字段的对应关系:

10.1 用户信息(写入 t_users)

第三方传入字段 我方数据库字段 我方实体字段 说明
phone phone phone 手机号(必填)
username nickname nickname 用户名(同时写入 nickname 和 userName)
nickname nickname nickname 昵称(优先级低于 username)
pharmacyName pharmacy_name pharmacyName 药店名称(也作为 storeName 兜底)
province province province
city city city
district district district
pharmacyAddress pharmacy_address pharmacyAddress 药店详细地址
contactPerson contact_person contactPerson 联系人
contactPhone contact_phone contactPhone 联系电话
businessLicenseNo business_license_no businessLicenseNo 营业执照号
openId wechat_mini_open_id wechatMiniOpenId 微信小程序 openId
unionId wechat_union_id wechatUnionId 微信开放平台 unionId

10.2 入驻信息(写入 t_business_license,全部可选)

第三方传入字段 我方数据库字段 我方实体字段 说明
storeName store_name storeName 店铺名称(不传则用 pharmacyName)
terminalType terminal_type terminalType 终端类型
businessLicenseUrl license_image_url licenseImageUrl 营业执照图片URL
drugLicenseUrl drug_license_url drugLicenseUrl 药品经营许可证图片URL
medicalDeviceClass2Url medical_device_class2_url medicalDeviceClass2Url 二类医疗器械备案图片URL
medicalDeviceClass3Url medical_device_class3_url medicalDeviceClass3Url 三类医疗器械备案图片URL
status review_status reviewStatus 审核状态:0→PENDING / 1→APPROVED / 2→REJECTED

💡 入驻记录保存时,以下字段由系统自动填入(无需第三方传入):

  • store_address ← 从 pharmacyAddress 提取
  • credit_code ← 从 businessLicenseNo 提取
  • sync_source ← 固定为 MINIAPP_SYNC
  • review_status ← 取决于 status1APPROVED2REJECTED0/不传 → PENDING
  • show_verified_badge ← 首次审核通过时设为 true

11. 联系方式

如有问题,请联系我方技术支持。


12. 变更记录

版本 日期 变更内容
V1.0 2026-07-07 初始版本:基础登录接口
V1.1 2026-07-07 扩展请求参数:支持传入用户名、药店信息等,自动保存到用户表
V1.2 2026-07-09 新增入驻信息字段(storeName / terminalType / businessLicenseUrl / drugLicenseUrl / medicalDeviceClass2Url / medicalDeviceClass3Url),传入后同步写入 t_business_license,无需额外调用入驻接口
V1.3 2026-07-09 新增 licenseApproved 参数支持第三方已审核入驻状态直通;审核通过自动赠送 30 天 PRO 高级会员;已 APPROVED 状态不降级;响应新增 licenseStatus 字段
V1.4 2026-07-09 响应 userInfo 补全完整返回字段(userName、pharmacyName、province、city、district、inviterId、inviterNickname、hasBoundInviteCode);修正 nickname 优先级描述(nickname > username)
V1.5 2026-07-09 新增 openIdunionId 参数,第三方传入后保存到 wechat_mini_open_id / wechat_union_id;后续小程序支付等场景直接从用户表读取,无需重复 wx.login 授权
V1.6 2026-07-10 licenseApproved(Boolean)改为 status(Integer:0-审核中 / 1-审核成功 / 2-审核失败),支持审核失败状态;已 APPROVED 不降级,PENDING/REJECTED 可随 status 互转或升级;调用第三方同步接口同步传 status