<template>
  <div class="help-content">
    <div class="content-header">
      <h2>统一认证平台 - 简易认证 (Simple Auth) 集成指南</h2>
      <el-button type="primary" size="small" plain @click="downloadDoc('/docs/simple_auth.md', 'Simple_Auth_Guide.md')">
        <el-icon style="margin-right: 5px"><Download /></el-icon>
        下载 开发文档
      </el-button>
    </div>
    <p class="intro">本指南适用于需要使用自定义登录页面（而非跳转到认证中心标准页面），并通过后端 API 直接进行用户认证的场景。</p>

    <div class="section">
      <h3>1. 核心流程图</h3>
      <div class="flow-chart">
        <div class="actor-row">
          <div class="actor">用户 (User)</div>
          <div class="actor">客户端 (Client)</div>
          <div class="actor">统一认证平台 (UAP)</div>
        </div>
        
        <div class="step">
          <div class="arrow user-to-client">1. 输入账号/密码</div>
        </div>
        <div class="step">
          <div class="self-action">2. 生成签名 (Sign)</div>
        </div>
        <div class="step">
          <div class="arrow client-to-uap">3. POST /login (账号+密码+签名)</div>
        </div>
        <div class="step">
          <div class="arrow uap-to-client dashed">4. 返回 Ticket (票据)</div>
        </div>
        <div class="step">
          <div class="self-action">5. 内部逻辑处理</div>
        </div>
        <div class="step">
          <div class="arrow client-to-uap">6. POST /validate (Ticket+签名)</div>
        </div>
        <div class="step">
          <div class="arrow uap-to-client dashed">7. 返回 用户信息 (ID, Mobile...)</div>
        </div>
        <div class="step">
          <div class="arrow client-to-user dashed">8. 登录成功</div>
        </div>
      </div>
    </div>

    <div class="section">
      <h3>2. 前置准备</h3>
      <p>在调用 API 之前，请确保您已在平台注册应用并获取以下信息：</p>
      <ul>
        <li><strong>App ID (<code>app_id</code>)</strong>: 应用唯一标识。</li>
        <li><strong>App Secret (<code>app_secret</code>)</strong>: 应用密钥（<strong class="danger">严禁泄露给前端</strong>）。</li>
      </ul>
      <el-alert title="安全警告" type="error" :closable="false" show-icon class="alert-box">
        <div>由于生成签名需要使用 <code>App Secret</code>，建议登录请求由您的<strong>应用后端</strong>发起，或者使用后端代理（BFF模式）。如果在前端（浏览器 JS）直接存储 Secret 并计算签名，极易导致密钥泄露。</div>
      </el-alert>
    </div>

    <div class="section">
      <h3>3. 签名算法 (Signature)</h3>
      <p>所有涉及安全的接口都需要校验签名。</p>
      <p><strong>签名生成步骤：</strong></p>
      <ol>
        <li><strong>准备参数</strong>：收集所有请求参数（不包括 <code>sign</code> 本身）。</li>
        <li><strong>排序</strong>：按照参数名（key）的 ASCII 码从小到大排序。</li>
        <li><strong>拼接</strong>：将排序后的参数拼接成 <code>key1=value1&key2=value2...</code> 格式的字符串。</li>
        <li><strong>计算 HMAC</strong>：使用 <code>App Secret</code> 作为密钥，对拼接字符串进行 <strong>HMAC-SHA256</strong> 计算。</li>
        <li><strong>转十六进制</strong>：将计算结果转换为 Hex 字符串即为签名。</li>
      </ol>

      <p><strong>Python 示例代码：</strong></p>
      <div class="code-block">
        <pre>
import hmac
import hashlib
import time

def generate_signature(secret: str, params: dict) -> str:
    # 1. 过滤掉空值和 sign 字段 (如果有)
    data = {k: v for k, v in params.items() if k != "sign" and v is not None}
    
    # 2. 排序 key
    sorted_keys = sorted(data.keys())
    
    # 3. 拼接字符串
    query_string = "&".join([f"{k}={data[k]}" for k in sorted_keys])
    
    # 4. 计算 HMAC-SHA256
    signature = hmac.new(
        secret.encode('utf-8'),
        query_string.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    return signature
        </pre>
      </div>
    </div>

    <div class="section">
      <h3>4. 接口开发详解</h3>

      <h4>4.1 第一步：密码登录获取票据 (Login)</h4>
      <p>用户在界面输入账号密码后，调用此接口获取临时票据（Ticket）。</p>
      <ul>
        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/login</code></li>
        <li><strong>Content-Type</strong>: <code>application/json</code></li>
      </ul>

      <p><strong>请求参数 (JSON Body):</strong></p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
          <tr><td><code>identifier</code></td><td>string</td><td>是</td><td>用户标识（手机号、用户名或邮箱）</td></tr>
          <tr><td><code>password</code></td><td>string</td><td>是</td><td>用户明文密码</td></tr>
          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳（秒），有效期 300秒</td></tr>
          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名字符串</td></tr>
        </tbody>
      </table>

      <p><strong>请求示例:</strong></p>
      <div class="code-block">
        <pre>
{
  "app_id": "your_app_id",
  "identifier": "13800138000",
  "password": "user_password_123",
  "timestamp": 1709876543,
  "sign": "a1b2c3d4e5..." 
}
        </pre>
      </div>

      <p><strong>响应成功 (200 OK):</strong></p>
      <div class="code-block">
        <pre>
{
  "ticket": "TICKET-7f8e9d0a-..." 
}
        </pre>
      </div>

      <el-divider />

      <h4>4.2 (可选) 替代方案：验证码登录获取票据 (SMS Login)</h4>
      <p>除了密码登录，您还可以使用手机验证码进行登录获取 Ticket。</p>
      <ul>
        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/sms-login</code></li>
        <li><strong>Content-Type</strong>: <code>application/json</code></li>
      </ul>

      <p><strong>请求参数 (JSON Body):</strong></p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
          <tr><td><code>mobile</code></td><td>string</td><td>是</td><td>用户手机号</td></tr>
          <tr><td><code>code</code></td><td>string</td><td>是</td><td>短信验证码</td></tr>
          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳</td></tr>
          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名字符串</td></tr>
        </tbody>
      </table>
      
      <p><strong>注意：</strong> 在调用此接口前，需先调用 <code>/api/v1/auth/sms/send-code</code> 发送验证码。</p>

      <p><strong>请求示例:</strong></p>
      <div class="code-block">
        <pre>
{
  "app_id": "your_app_id",
  "mobile": "13800138000",
  "code": "123456",
  "timestamp": 1709876543,
  "sign": "a1b2c3d4e5..." 
}
        </pre>
      </div>

      <p><strong>响应成功 (200 OK):</strong></p>
      <div class="code-block">
        <pre>
{
  "ticket": "TICKET-7f8e9d0a-..." 
}
        </pre>
      </div>

      <el-divider />

      <h4>4.3 第二步：验证票据并获取用户信息 (Validate)</h4>
      <p>拿到 <code>ticket</code> 后，立即调用此接口解析出用户身份。</p>
      <ul>
        <li><strong>接口地址</strong>: <code>POST /api/v1/simple/validate</code></li>
        <li><strong>Content-Type</strong>: <code>application/json</code></li>
      </ul>

      <p><strong>请求参数 (JSON Body):</strong></p>
      <table class="param-table">
        <thead>
          <tr><th>字段</th><th>类型</th><th>必填</th><th>说明</th></tr>
        </thead>
        <tbody>
          <tr><td><code>app_id</code></td><td>string</td><td>是</td><td>您的应用 ID</td></tr>
          <tr><td><code>ticket</code></td><td>string</td><td>是</td><td>上一步获取到的票据</td></tr>
          <tr><td><code>timestamp</code></td><td>int</td><td>是</td><td>当前时间戳</td></tr>
          <tr><td><code>sign</code></td><td>string</td><td>是</td><td>签名（注意参数变化，需重新计算）</td></tr>
        </tbody>
      </table>

      <p><strong>请求示例:</strong></p>
      <div class="code-block">
        <pre>
{
  "app_id": "your_app_id",
  "ticket": "TICKET-7f8e9d0a-...",
  "timestamp": 1709876545,
  "sign": "f9e8d7c6b5..."
}
        </pre>
      </div>

      <p><strong>响应成功 (200 OK):</strong></p>
      <p>此接口返回 <code>valid: true</code> 表示票据有效，并附带用户数据。</p>
      <div class="code-block">
        <pre>
{
  "valid": true,
  "user_id": 1001,
  "mobile": "13800138000",
  "mapped_key": "user_zhangsan",  // 第三方映射ID（如果有）
  "mapped_email": "zhangsan@example.com" // 映射邮箱（如果有）
}
        </pre>
      </div>

      <p><strong>响应失败 (票据无效或过期):</strong></p>
      <div class="code-block">
        <pre>
{
  "valid": false
}
        </pre>
      </div>
    </div>

    <div class="section">
      <h3>5. 多语言调用示例</h3>
      <p>以下提供了多种编程语言计算签名并发起请求的示例代码。</p>
      
      <el-tabs type="border-card" class="code-tabs">
        <el-tab-pane label="Python">
          <div class="code-block">
            <pre>
import requests
import time
import hmac
import hashlib
import json

# 配置信息
API_BASE = "http://localhost:8000/api/v1/simple"
APP_ID = "test_app_001"
APP_SECRET = "secret_key_abc123" # 务必保密

def get_sign(params):
    # 排除 sign 字段
    data = {k: v for k, v in params.items() if k != "sign"}
    # 排序并拼接
    query_string = "&".join([f"{k}={data[k]}" for k in sorted(data.keys())])
    # HMAC-SHA256
    return hmac.new(APP_SECRET.encode(), query_string.encode(), hashlib.sha256).hexdigest()

def main():
    # === 步骤 1: 登录获取 Ticket ===
    login_ts = int(time.time())
    login_payload = {
        "app_id": APP_ID,
        "identifier": "13800000001",
        "password": "password123",
        "timestamp": login_ts
    }
    # 计算签名
    login_payload["sign"] = get_sign(login_payload)

    print(f"1. 正在尝试登录: {login_payload['identifier']} ...")
    resp = requests.post(f"{API_BASE}/login", json=login_payload)
    
    if resp.status_code != 200:
        print(f"登录失败: {resp.text}")
        return

    ticket = resp.json().get("ticket")
    print(f"登录成功! 获取到 Ticket: {ticket}")

    # === 步骤 2: 使用 Ticket 换取用户信息 ===
    validate_ts = int(time.time())
    validate_payload = {
        "app_id": APP_ID,
        "ticket": ticket,
        "timestamp": validate_ts
    }
    # 重新计算签名（参数变了，签名必须重算）
    validate_payload["sign"] = get_sign(validate_payload)

    print(f"\n2. 正在验证 Ticket...")
    v_resp = requests.post(f"{API_BASE}/validate", json=validate_payload)
    
    user_info = v_resp.json()
    if user_info.get("valid"):
        print("验证成功! 用户信息如下:")
        print(json.dumps(user_info, indent=2, ensure_ascii=False))
    else:
        print("Ticket 无效或已过期")

if __name__ == "__main__":
    main()
            </pre>
          </div>
        </el-tab-pane>
        
        <el-tab-pane label="Java">
          <div class="code-block">
            <pre>
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
import java.nio.charset.StandardCharsets;

public class AuthExample {
    private static final String SECRET = "secret_key_abc123";

    public static String generateSign(Map&lt;String, String&gt; params) {
        try {
            // 1. 排序
            List&lt;String&gt; sortedKeys = new ArrayList&lt;&gt;(params.keySet());
            Collections.sort(sortedKeys);
            
            // 2. 拼接
            StringBuilder sb = new StringBuilder();
            for (String key : sortedKeys) {
                if (!key.equals("sign") && params.get(key) != null) {
                    if (sb.length() > 0) sb.append("&");
                    sb.append(key).append("=").append(params.get(key));
                }
            }
            
            // 3. HMAC-SHA256
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            
            byte[] bytes = sha256_HMAC.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
            
            // 4. Hex
            StringBuilder hex = new StringBuilder();
            for (byte b : bytes) {
                hex.append(String.format("%02x", b));
            }
            return hex.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
    
    public static void main(String[] args) {
        Map&lt;String, String&gt; params = new HashMap&lt;&gt;();
        params.put("app_id", "test_app_001");
        params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
        
        System.out.println("Sign: " + generateSign(params));
    }
}
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="Android (Kotlin)">
          <div class="code-block">
            <pre>
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

object AuthUtils {
    private const val APP_SECRET = "secret_key_abc123"

    fun generateSign(params: Map&lt;String, String&gt;): String {
        // 1. 过滤 & 排序
        val sortedKeys = params.keys.filter { it != "sign" }.sorted()

        // 2. 拼接
        val queryString = sortedKeys.joinToString("&") { key ->
            "$key=${params[key]}"
        }

        // 3. HMAC-SHA256
        val hmacSha256 = "HmacSHA256"
        val secretKeySpec = SecretKeySpec(APP_SECRET.toByteArray(Charsets.UTF_8), hmacSha256)
        val mac = Mac.getInstance(hmacSha256)
        mac.init(secretKeySpec)
        
        val bytes = mac.doFinal(queryString.toByteArray(Charsets.UTF_8))
        
        // 4. Hex
        return bytes.joinToString("") { "%02x".format(it) }
    }
}

// Usage Example
fun main() {
    val params = mapOf(
        "app_id" to "test_app_001",
        "identifier" to "13800000001",
        "timestamp" to (System.currentTimeMillis() / 1000).toString()
    )
    val sign = AuthUtils.generateSign(params)
    println("Signature: $sign")
}
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="JavaScript (Node.js)">
          <div class="code-block">
            <pre>
const crypto = require('crypto');
const axios = require('axios'); // npm install axios

const APP_ID = 'test_app_001';
const APP_SECRET = 'secret_key_abc123';
const BASE_URL = 'http://localhost:8000/api/v1/simple';

function getSign(params) {
  // 1. 过滤 & 排序
  const keys = Object.keys(params)
    .filter(k => k !== 'sign' && params[k] !== undefined)
    .sort();
  
  // 2. 拼接 Query String
  const queryString = keys.map(k => `${k}=${params[k]}`).join('&');
  
  // 3. HMAC-SHA256
  return crypto.createHmac('sha256', APP_SECRET)
    .update(queryString)
    .digest('hex');
}

async function login() {
  const timestamp = Math.floor(Date.now() / 1000);
  const payload = {
    app_id: APP_ID,
    identifier: '13800000001',
    password: 'password123',
    timestamp: timestamp
  };
  
  payload.sign = getSign(payload);
  
  try {
    console.log('Sending login request...');
    const res = await axios.post(`${BASE_URL}/login`, payload);
    console.log('Ticket:', res.data.ticket);
  } catch (error) {
    console.error('Login Failed:', error.response?.data || error.message);
  }
}

login();
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="Go">
          <div class="code-block">
            <pre>
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"sort"
	"strings"
	"time"
)

func GetSign(secret string, params map[string]interface{}) string {
	// 1. 提取 Key
	var keys []string
	for k := range params {
		if k != "sign" {
			keys = append(keys, k)
		}
	}
	// 2. 排序
	sort.Strings(keys)

	// 3. 拼接
	var parts []string
	for _, k := range keys {
		val := fmt.Sprintf("%v", params[k])
		parts = append(parts, fmt.Sprintf("%s=%s", k, val))
	}
	query := strings.Join(parts, "&")

	// 4. HMAC-SHA256
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(query))
	return hex.EncodeToString(h.Sum(nil))
}

func main() {
    params := map[string]interface{}{
        "app_id":    "test_app_001",
        "identifier": "13800000001",
        "password":   "123456",
        "timestamp":  time.Now().Unix(),
    }
    
    secret := "secret_key_abc123"
    sign := GetSign(secret, params)
    fmt.Printf("Signature: %s\n", sign)
}
            </pre>
          </div>
        </el-tab-pane>

        <el-tab-pane label="Swift">
          <div class="code-block">
            <pre>
import Foundation
import CommonCrypto

// 注意：需要添加 Bridging Header 引入 CommonCrypto 或直接在 Linux 环境使用

func hmac(string: String, key: String) -> String {
    var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), key, key.count, string, string.count, &digest)
    let data = Data(digest)
    return data.map { String(format: "%02hhx", $0) }.joined()
}

func generateSign(secret: String, params: [String: Any]) -> String {
    // 1. 过滤 & 排序
    let sortedKeys = params.keys.filter { $0 != "sign" }.sorted()
    
    // 2. 拼接
    let queryParts = sortedKeys.map { key in
        return "\(key)=\(params[key]!)"
    }
    let queryString = queryParts.joined(separator: "&")
    
    // 3. HMAC
    return hmac(string: queryString, key: secret)
}

// Usage
let params: [String: Any] = [
    "app_id": "test_app_001",
    "timestamp": Int(Date().timeIntervalSince1970)
]
let sign = generateSign(secret: "secret_123", params: params)
print(sign)
            </pre>
          </div>
        </el-tab-pane>
      </el-tabs>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Download } from '@element-plus/icons-vue'
import { useHelpDocs } from '../../composables/useHelpDocs'

const { downloadDoc } = useHelpDocs()
</script>

<style scoped>
@import './help.css';
</style>
