# 思云聚合登录接入文档

接口地址：https://login.siyun223.com，所有接口仅支持 HTTPS。

## 接入流程

1. 在商户控制台创建应用，填写回调域名并勾选要启用的登录渠道，拿到 AppID 和 AppSecret。
2. 用户在你的网站点击「微信登录」等按钮时，你的服务端调用「获取登录地址」接口，把用户浏览器跳转到返回的 url。
3. 平台按用户所在环境选择登录方式：电脑浏览器扫码、微信内授权、手机浏览器拉起小程序。用户第一次登录你的应用时需要在确认页同意提供信息。
4. 登录完成后，平台把用户浏览器跳回你的 redirect_uri，并带上 code 和你传入的 state。
5. 你的服务端用 code 调用「获取用户信息」接口，拿到该用户在你应用下的 openid、昵称和头像。code 只能使用一次，60 秒内有效。

## 签名规则

1. 取出除 sign 以外的全部请求参数，去掉值为空的参数。
2. 按参数名的 ASCII 码从小到大排序。
3. 拼接成 key1=value1&key2=value2 形式的字符串，值使用原始内容，不做 URL 编码。
4. 用 AppSecret 作为密钥，对该字符串做 HMAC-SHA256，结果转为小写十六进制，即为 sign。
5. timestamp 为当前 Unix 时间戳（秒），与平台时间相差不能超过 300 秒；nonce 为 8 到 64 位字母或数字的随机串，同一个 nonce 不能重复使用。

```php
$params = [
    'app_id'       => '你的 AppID',
    'type'         => 'wechat',
    'redirect_uri' => 'https://www.example.com/login/callback',
    'state'        => 'abc123',
    'timestamp'    => (string) time(),
    'nonce'        => bin2hex(random_bytes(8)),
];
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $key => $value) {
    if ($value !== '') {
        $pairs[] = $key . '=' . $value;
    }
}
$params['sign'] = hash_hmac('sha256', implode('&', $pairs), '你的 AppSecret');
```

## 获取登录地址

`POST https://login.siyun223.com/api/v1/login/url`

由你的服务端调用，返回的 url 有效期 10 分钟，只能在一个浏览器中打开。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| type | string | 是 | 登录渠道：wechat、qq、alipay、douyin |
| redirect_uri | string | 是 | 登录完成后跳回的地址，域名必须在应用的回调域名白名单内 |
| state | string | 否 | 原样带回，最长 128 个字符，建议用于防止 CSRF |
| app_id | string | 是 | 应用的 AppID |
| timestamp | string | 是 | 当前 Unix 时间戳（秒） |
| nonce | string | 是 | 8 到 64 位字母或数字的随机串 |
| sign | string | 是 | 签名，见「签名规则」 |

### 返回 data 字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| url | string | 把用户浏览器跳转到这个地址 |
| expires_in | int | 有效期（秒） |

### 返回示例

```json
{
    "code": 0,
    "msg": "成功",
    "data": {
        "url": "https://login.siyun223.com/connect/Xa9…（示意）",
        "expires_in": 600
    }
}
```

## 获取用户信息

`POST https://login.siyun223.com/api/v1/login/user`

用户被跳回 redirect_uri 后，用地址里的 code 换取用户信息。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| code | string | 是 | 跳回地址中携带的 code |
| app_id | string | 是 | 应用的 AppID |
| timestamp | string | 是 | 当前 Unix 时间戳（秒） |
| nonce | string | 是 | 8 到 64 位字母或数字的随机串 |
| sign | string | 是 | 签名，见「签名规则」 |

### 返回 data 字段

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| openid | string | 用户在你的应用下的唯一标识，同一用户在不同应用下不同 |
| type | string | 登录渠道，见「渠道取值」 |
| type_name | string | 登录渠道中文名称 |
| method | string | 实际登录方式，见「登录方式取值」 |
| method_name | string | 登录方式中文名称 |
| nickname | string | 昵称，可能为空（例如首次通过小程序登录的用户） |
| avatar | string | 头像地址，可能为空 |

### 返回示例

```json
{
    "code": 0,
    "msg": "成功",
    "data": {
        "openid": "o8Qd…（示意）",
        "type": "wechat",
        "type_name": "微信",
        "method": "wechat_web",
        "method_name": "微信扫码登录",
        "nickname": "示意昵称",
        "avatar": "https://…（示意）"
    }
}
```

## 回调参数

登录结束后，平台把用户浏览器跳转到 redirect_uri，并在地址后追加以下参数：

| 参数 | 说明 |
| --- | --- |
| code | 登录成功时携带，用于换取用户信息 |
| state | 你在获取登录地址时传入的 state |
| error | 用户取消授权时为 access_denied，此时没有 code |

## 渠道取值（type）

| 取值 | 说明 |
| --- | --- |
| wechat | 微信 |
| qq | QQ |
| alipay | 支付宝 |
| douyin | 抖音 |

## 登录方式取值（method）

| 取值 | 说明 |
| --- | --- |
| wechat_web | 微信扫码登录 |
| wechat_mp | 微信内授权登录 |
| wechat_mini | 微信小程序登录 |
| qq | QQ 授权登录 |
| alipay | 支付宝授权登录 |
| douyin | 抖音授权登录 |

## 错误码

接口返回格式：`{"code": 0, "msg": "成功", "data": {...}}`，code 不为 0 时 data 为 null。

| code | 说明 |
| --- | --- |
| 0 | 成功 |
| 40001 | 参数错误 |
| 40002 | 签名错误 |
| 40003 | 请求时间戳已过期，请校准服务器时间 |
| 40004 | 重复的请求，nonce 已使用 |
| 40101 | 应用不存在 |
| 40102 | 应用已被禁用 |
| 40301 | 应用未启用该登录渠道 |
| 40302 | 平台暂未开放该登录渠道 |
| 40303 | 回调地址的域名不在应用白名单内 |
| 40401 | code 无效、已使用或已过期 |
| 42900 | 请求过于频繁，请稍后再试 |
| 50000 | 系统繁忙，请稍后再试 |

## PHP SDK

下载地址：https://login.siyun223.com/docs/sdk/php（单文件，PHP 7.4 及以上，需要 curl 扩展）

## 注意事项

- 接口请求格式为 application/x-www-form-urlencoded 或 application/json，返回格式为 JSON，code 为 0 表示成功。
- AppSecret 只能保存在你的服务端，不要写进网页或 App 中；泄露后请立即在控制台重置。
- 同一用户通过微信的不同方式（扫码、微信内、小程序）登录，拿到的 openid 相同。
- 小程序登录拿不到微信头像昵称，如果该用户之前没有通过其他方式登录过，nickname 和 avatar 为空。
- 用户撤回对你的应用的授权后，下次登录需要重新确认，openid 不变。
