# 第三方登录接口文档 > 版本:V1.1 > 更新日期:2026-07-07 > 变更说明:扩展请求参数,支持传入用户名、药店名称、地址、联系人等用户信息 --- ## 1. 概述 本接口用于第三方系统通过用户手机号换取登录 Token,实现免密登录。系统会自动查找或注册用户,返回访问令牌和刷新令牌。同时支持第三方系统传入用户详细信息(用户名、药店名称、地址、联系人等),由我方负责存储,第三方无需我方再次维护用户信息。 **核心特性:** - 按手机号自动查找或注册用户 - 支持第三方传入用户名、药店信息等,自动保存到用户表 - 不递增 `tokenVersion`,不踢出 PC 端已有会话 - 新用户自动初始化 PLUS 会员 --- ## 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 | 否 | 用户名(保存到 nickname 字段,优先级高于 nickname) | `nickname` | | `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` | > 💡 **字段更新规则**: > - 新用户会写入所有传入字段 > - 已存在用户仅更新非空字段(不会清空已有数据) > > 💡 **nickname 优先级**:`username` > `nickname` > 手机号生成(如 `用户8000`) > > 💡 **建议**:第三方每次调用时传入最新的用户信息,我方会自动同步更新,无需额外维护。 ### 4.3 请求示例 #### 最简请求(仅必填字段) ```json { "phone": "13800138000" } ``` #### 完整请求(含用户信息) ```json { "phone": "13800138000", "inviteCode": "ABC123", "username": "yaohuigou001", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "pharmacyAddress": "建国路88号", "contactPerson": "张三", "contactPhone": "13800138000", "businessLicenseNo": "91110105MA12345678" } ``` --- ## 5. 响应参数 ### 5.1 通用响应结构 ```json { "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 | 昵称(即第三方传入的 username 或 nickname) | | `avatar` | String | 头像 URL | | `wechatBound` | Boolean | 是否已绑定微信 | | `loginSource` | String | 登录来源,`"MINIAPP"` 表示小程序 | | `membershipLevel` | String | 会员等级代码 | | `membershipLevelName` | String | 会员等级名称 | | `createTime` | String | 注册时间 | ### 5.4 响应示例 ```json { "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": "yaohuigou001", "avatar": null, "wechatBound": false, "loginSource": "MINIAPP", "membershipLevel": "PLUS", "membershipLevelName": "PLUS会员", "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. 调用示例 ### 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", "username": "yaohuigou001", "pharmacyName": "北京同仁堂大药房", "province": "北京市", "city": "北京市", "district": "朝阳区", "pharmacyAddress": "建国路88号", "contactPerson": "张三", "contactPhone": "13800138000", "businessLicenseNo": "91110105MA12345678" }' ``` ### Java (OkHttp) ```java OkHttpClient client = new OkHttpClient(); String json = "{" + "\"phone\": \"13800138000\"," + "\"username\": \"yaohuigou001\"," + "\"pharmacyName\": \"北京同仁堂大药房\"" + "}"; 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) ```python 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": "朝阳区" } response = requests.post(url, json=data, headers=headers) print(response.json()) ``` --- ## 10. 字段映射表 第三方传入字段与我方数据库字段的对应关系: | 第三方传入字段 | 我方数据库字段 | 我方实体字段 | 说明 | |----------------|----------------|--------------|------| | `phone` | `phone` | `phone` | 手机号(必填) | | `username` | `nickname` | `nickname` | 用户名(保存到 nickname) | | `nickname` | `nickname` | `nickname` | 昵称(优先级低于 username) | | `pharmacyName` | `pharmacy_name` | `pharmacyName` | 药店名称 | | `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` | 营业执照号 | --- ## 11. 联系方式 如有问题,请联系我方技术支持。 --- ## 12. 变更记录 | 版本 | 日期 | 变更内容 | |------|------|----------| | V1.0 | 2026-07-07 | 初始版本:基础登录接口 | | V1.1 | 2026-07-07 | 扩展请求参数:支持传入用户名、药店信息等,自动保存到用户表 |