QAExchange 前端 API 集成指南

📋 User-Account 架构说明

核心概念

User (用户) 1 ──────→ N Account (账户)
  │                      │
  ├─ user_id (UUID)      ├─ account_id (ACC_xxx)
  ├─ username            ├─ account_name
  ├─ email               ├─ balance
  └─ password            └─ portfolio_cookie = user_id

关键理解:

  • 1 个 User 可以有 多个 Account
  • User 用于登录认证
  • Account 用于交易操作
  • 通过 portfolio_cookie 字段关联 User ↔ Account

🔐 1. 用户认证流程

1.1 注册

接口: POST /api/auth/register

请求:

{
  "username": "zhangsan",
  "email": "zhangsan@example.com",
  "password": "password123",
  "phone": "13800138000"
}

响应:

{
  "success": true,
  "data": {
    "user_id": "8d482456-9fab-4d1b-9c2c-bf80cb3ff509",
    "username": "zhangsan",
    "email": "zhangsan@example.com",
    "message": "Registration successful"
  },
  "error": null
}

前端处理:

async function register(username, email, password, phone) {
  const response = await fetch('http://192.168.2.115:8097/api/auth/register', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, email, password, phone })
  });

  const result = await response.json();
  if (result.success) {
    // 保存 user_id 到 localStorage
    localStorage.setItem('user_id', result.data.user_id);
    localStorage.setItem('username', result.data.username);
    return result.data;
  } else {
    throw new Error(result.error);
  }
}

1.2 登录

接口: POST /api/auth/login

请求:

{
  "username": "zhangsan",
  "password": "password123"
}

响应:

{
  "success": true,
  "data": {
    "user_id": "8d482456-9fab-4d1b-9c2c-bf80cb3ff509",
    "username": "zhangsan",
    "email": "zhangsan@example.com",
    "phone": "13800138000",
    "token": "mock_token_xxx",
    "message": "Login successful"
  },
  "error": null
}

前端处理:

async function login(username, password) {
  const response = await fetch('http://192.168.2.115:8097/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });

  const result = await response.json();
  if (result.success) {
    // 保存登录态
    localStorage.setItem('user_id', result.data.user_id);
    localStorage.setItem('username', result.data.username);
    localStorage.setItem('token', result.data.token);
    return result.data;
  } else {
    throw new Error(result.error);
  }
}

1.3 获取当前用户信息

接口: GET /api/auth/user/{user_id}

示例:

curl http://192.168.2.115:8097/api/auth/user/8d482456-9fab-4d1b-9c2c-bf80cb3ff509

响应:

{
  "success": true,
  "data": {
    "user_id": "8d482456-9fab-4d1b-9c2c-bf80cb3ff509",
    "username": "zhangsan",
    "email": "zhangsan@example.com",
    "phone": "13800138000",
    "is_admin": false,
    "created_at": "2025-10-05 12:00:00"
  },
  "error": null
}

💰 2. 账户管理 (核心功能)

2.1 查询用户的所有账户 ⭐ 重点

接口: GET /api/user/{user_id}/accounts

前端页面: http://192.168.2.115:8097/#/accounts

正确调用方式:

// ✅ 正确: 使用 user_id 查询该用户的所有账户
async function getUserAccounts() {
  const user_id = localStorage.getItem('user_id');  // 从登录态获取

  const response = await fetch(
    `http://192.168.2.115:8097/api/user/${user_id}/accounts`
  );

  const result = await response.json();
  if (result.success) {
    return result.data.accounts;  // 返回账户列表
  }
}

错误调用方式:

// ❌ 错误: 直接用 user_id 查单个账户(这不存在)
fetch(`http://192.168.2.115:8097/api/account/${user_id}`)  // 404 错误!

响应示例:

{
  "success": true,
  "data": {
    "accounts": [
      {
        "account_id": "ACC_9bc0b5268d4741cb8e03d766565f3fc8",
        "account_name": "tea1",
        "account_type": "Individual",
        "balance": 10000000.0,
        "available": 10000000.0,
        "margin": 0.0,
        "risk_ratio": 0.0,
        "created_at": 1759680011
      },
      {
        "account_id": "ACC_a1b2c3d4e5f6...",
        "account_name": "tea2",
        "account_type": "Corporate",
        "balance": 5000000.0,
        "available": 4800000.0,
        "margin": 200000.0,
        "risk_ratio": 0.04,
        "created_at": 1759680999
      }
    ],
    "total": 2
  },
  "error": null
}

前端展示:

<!-- Vue 组件示例 -->
<template>
  <div class="accounts-page">
    <h2>我的账户</h2>
    <div v-for="account in accounts" :key="account.account_id" class="account-card">
      <h3>{{ account.account_name }}</h3>
      <p>账户ID: {{ account.account_id }}</p>
      <p>类型: {{ account.account_type }}</p>
      <p>总权益: ¥{{ account.balance.toLocaleString() }}</p>
      <p>可用资金: ¥{{ account.available.toLocaleString() }}</p>
      <p>保证金: ¥{{ account.margin.toLocaleString() }}</p>
      <p>风险度: {{ (account.risk_ratio * 100).toFixed(2) }}%</p>
      <button @click="selectAccount(account.account_id)">选择此账户</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      accounts: [],
      currentUserId: ''
    }
  },

  async mounted() {
    this.currentUserId = localStorage.getItem('user_id');
    await this.loadAccounts();
  },

  methods: {
    async loadAccounts() {
      try {
        const response = await fetch(
          `http://192.168.2.115:8097/api/user/${this.currentUserId}/accounts`
        );
        const result = await response.json();

        if (result.success) {
          this.accounts = result.data.accounts;
        } else {
          console.error('加载账户失败:', result.error);
        }
      } catch (error) {
        console.error('请求失败:', error);
      }
    },

    selectAccount(accountId) {
      // 保存当前选中的账户ID,用于后续交易
      localStorage.setItem('current_account_id', accountId);
      this.$router.push('/trading');
    }
  }
}
</script>

2.2 查询单个账户详情

接口: GET /api/account/{account_id}

使用场景: 点击某个账户后,查看该账户的详细信息

示例:

async function getAccountDetail(accountId) {
  const response = await fetch(
    `http://192.168.2.115:8097/api/account/${accountId}`
  );

  const result = await response.json();
  if (result.success) {
    return result.data;
  }
}

// 使用示例
const detail = await getAccountDetail('ACC_9bc0b5268d4741cb8e03d766565f3fc8');
console.log(detail);

响应:

{
  "success": true,
  "data": {
    "user_id": "ACC_9bc0b5268d4741cb8e03d766565f3fc8",
    "user_name": "tea1",
    "balance": 10000000.0,
    "available": 10000000.0,
    "frozen": 0.0,
    "margin": 0.0,
    "profit": 0.0,
    "risk_ratio": 0.0,
    "account_type": "individual",
    "created_at": 1759680011
  },
  "error": null
}

2.3 创建新账户

接口: POST /api/user/{user_id}/account/create

请求:

{
  "account_id": "ACC_custom_123",  // 可选,不填则自动生成
  "account_name": "My Trading Account",
  "account_type": "Individual",
  "init_balance": 1000000.0
}

前端示例:

async function createAccount(accountName, accountType, initBalance) {
  const user_id = localStorage.getItem('user_id');

  const response = await fetch(
    `http://192.168.2.115:8097/api/user/${user_id}/account/create`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        account_name: accountName,
        account_type: accountType,
        init_balance: initBalance
      })
    }
  );

  const result = await response.json();
  if (result.success) {
    console.log('账户创建成功:', result.data.account_id);
    return result.data;
  }
}

响应:

{
  "success": true,
  "data": {
    "account_id": "ACC_9bc0b5268d4741cb8e03d766565f3fc8",
    "message": "Account created successfully"
  },
  "error": null
}

📊 3. 交易流程

3.1 下单

接口: POST /api/order/submit

前提: 用户已选择当前交易账户

请求:

{
  "user_id": "ACC_9bc0b5268d4741cb8e03d766565f3fc8",  // 注意: 这里是 account_id
  "instrument_id": "SHFE.cu2501",
  "direction": "Buy",
  "offset": "Open",
  "volume": 1,
  "price": 75000.0,
  "order_type": "Limit"
}

前端示例:

async function submitOrder(instrumentId, direction, volume, price) {
  // 使用当前选中的 account_id
  const account_id = localStorage.getItem('current_account_id');

  const response = await fetch(
    'http://192.168.2.115:8097/api/order/submit',
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        user_id: account_id,  // 后端参数名叫 user_id,但实际是 account_id
        instrument_id: instrumentId,
        direction: direction,
        offset: 'Open',
        volume: volume,
        price: price,
        order_type: 'Limit'
      })
    }
  );

  const result = await response.json();
  return result;
}

3.2 查询订单

按用户查询: GET /api/order/user/{account_id}

async function getUserOrders() {
  const account_id = localStorage.getItem('current_account_id');

  const response = await fetch(
    `http://192.168.2.115:8097/api/order/user/${account_id}`
  );

  const result = await response.json();
  return result.data;
}

3.3 查询持仓

接口: GET /api/position/{account_id}

async function getPositions() {
  const account_id = localStorage.getItem('current_account_id');

  const response = await fetch(
    `http://192.168.2.115:8097/api/position/${account_id}`
  );

  const result = await response.json();
  return result.data.positions;
}

3.4 查询成交记录

接口: GET /api/trades/user/{account_id}

async function getTrades() {
  const account_id = localStorage.getItem('current_account_id');

  const response = await fetch(
    `http://192.168.2.115:8097/api/trades/user/${account_id}`
  );

  const result = await response.json();
  return result.data.trades;
}

📈 4. 市场数据

4.1 获取合约列表

接口: GET /api/market/instruments

async function getInstruments() {
  const response = await fetch(
    'http://192.168.2.115:8097/api/market/instruments'
  );

  const result = await response.json();
  return result.data.instruments;
}

4.2 获取行情快照

接口: GET /api/market/tick/{instrument_id}

async function getTick(instrumentId) {
  const response = await fetch(
    `http://192.168.2.115:8097/api/market/tick/${instrumentId}`
  );

  const result = await response.json();
  return result.data;
}

// 使用示例
const tick = await getTick('SHFE.cu2501');
console.log('最新价:', tick.last_price);

4.3 获取盘口数据

接口: GET /api/market/orderbook/{instrument_id}

async function getOrderbook(instrumentId) {
  const response = await fetch(
    `http://192.168.2.115:8097/api/market/orderbook/${instrumentId}`
  );

  const result = await response.json();
  return result.data;
}

// 使用示例
const orderbook = await getOrderbook('SHFE.cu2501');
console.log('买一价:', orderbook.bids[0].price);
console.log('卖一价:', orderbook.asks[0].price);

🔌 5. WebSocket 实时推送 (DIFF 协议)

5.1 连接 WebSocket

const ws = new WebSocket('ws://192.168.2.115:8097/ws');

ws.onopen = () => {
  console.log('WebSocket 连接成功');

  // 发送 peek_message 请求数据更新
  ws.send(JSON.stringify({ aid: 'peek_message' }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.aid === 'rtn_data') {
    // 处理数据更新 (DIFF 协议)
    handleDataUpdate(message.data);
  }
};

5.2 订阅行情

function subscribeQuotes(instruments) {
  const message = {
    aid: 'subscribe_quote',
    ins_list: instruments.join(',')  // 'SHFE.cu2501,CFFEX.IF2501'
  };

  ws.send(JSON.stringify(message));
}

// 使用示例
subscribeQuotes(['SHFE.cu2501', 'CFFEX.IF2501']);

5.3 处理 DIFF 数据更新

let businessSnapshot = {
  accounts: {},
  orders: {},
  positions: {},
  quotes: {}
};

function handleDataUpdate(patches) {
  // 应用所有 JSON Merge Patch
  patches.forEach(patch => {
    applyMergePatch(businessSnapshot, patch);
  });

  // 更新 UI
  updateUI(businessSnapshot);

  // 发送下一个 peek_message
  ws.send(JSON.stringify({ aid: 'peek_message' }));
}

// JSON Merge Patch 算法 (RFC 7386)
function applyMergePatch(target, patch) {
  for (const key in patch) {
    if (patch[key] === null) {
      delete target[key];
    } else if (typeof patch[key] === 'object' && !Array.isArray(patch[key])) {
      if (!target[key]) target[key] = {};
      applyMergePatch(target[key], patch[key]);
    } else {
      target[key] = patch[key];
    }
  }
}

📝 6. 完整示例:账户管理页面

<template>
  <div class="accounts-management">
    <!-- 用户信息 -->
    <div class="user-info">
      <h2>欢迎, {{ username }}</h2>
      <p>用户ID: {{ userId }}</p>
    </div>

    <!-- 账户列表 -->
    <div class="accounts-section">
      <h3>我的账户 ({{ accounts.length }})</h3>

      <button @click="showCreateDialog = true">+ 创建新账户</button>

      <div class="accounts-grid">
        <div
          v-for="account in accounts"
          :key="account.account_id"
          class="account-card"
          :class="{ active: account.account_id === currentAccountId }"
          @click="selectAccount(account.account_id)"
        >
          <h4>{{ account.account_name }}</h4>
          <div class="account-id">{{ account.account_id }}</div>
          <div class="account-stats">
            <div class="stat">
              <span>总权益</span>
              <strong>¥{{ account.balance.toLocaleString() }}</strong>
            </div>
            <div class="stat">
              <span>可用资金</span>
              <strong>¥{{ account.available.toLocaleString() }}</strong>
            </div>
            <div class="stat">
              <span>保证金</span>
              <strong>¥{{ account.margin.toLocaleString() }}</strong>
            </div>
            <div class="stat">
              <span>风险度</span>
              <strong :class="getRiskClass(account.risk_ratio)">
                {{ (account.risk_ratio * 100).toFixed(2) }}%
              </strong>
            </div>
          </div>
          <div class="account-actions">
            <button @click.stop="viewDetail(account.account_id)">详情</button>
            <button @click.stop="deposit(account.account_id)">入金</button>
          </div>
        </div>
      </div>
    </div>

    <!-- 创建账户对话框 -->
    <div v-if="showCreateDialog" class="dialog">
      <h3>创建新账户</h3>
      <form @submit.prevent="createAccount">
        <input v-model="newAccount.name" placeholder="账户名称" required />
        <select v-model="newAccount.type" required>
          <option value="Individual">个人账户</option>
          <option value="Corporate">企业账户</option>
        </select>
        <input
          v-model.number="newAccount.balance"
          type="number"
          placeholder="初始资金"
          required
        />
        <button type="submit">创建</button>
        <button type="button" @click="showCreateDialog = false">取消</button>
      </form>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userId: '',
      username: '',
      accounts: [],
      currentAccountId: '',
      showCreateDialog: false,
      newAccount: {
        name: '',
        type: 'Individual',
        balance: 1000000
      }
    }
  },

  async mounted() {
    // 从 localStorage 获取登录态
    this.userId = localStorage.getItem('user_id');
    this.username = localStorage.getItem('username');
    this.currentAccountId = localStorage.getItem('current_account_id') || '';

    if (!this.userId) {
      this.$router.push('/login');
      return;
    }

    await this.loadAccounts();
  },

  methods: {
    async loadAccounts() {
      try {
        const response = await fetch(
          `http://192.168.2.115:8097/api/user/${this.userId}/accounts`
        );
        const result = await response.json();

        if (result.success) {
          this.accounts = result.data.accounts;
        } else {
          this.$message.error('加载账户失败: ' + result.error);
        }
      } catch (error) {
        console.error('请求失败:', error);
        this.$message.error('网络错误');
      }
    },

    async createAccount() {
      try {
        const response = await fetch(
          `http://192.168.2.115:8097/api/user/${this.userId}/account/create`,
          {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              account_name: this.newAccount.name,
              account_type: this.newAccount.type,
              init_balance: this.newAccount.balance
            })
          }
        );

        const result = await response.json();

        if (result.success) {
          this.$message.success('账户创建成功');
          this.showCreateDialog = false;
          await this.loadAccounts();
        } else {
          this.$message.error('创建失败: ' + result.error);
        }
      } catch (error) {
        console.error('创建失败:', error);
        this.$message.error('网络错误');
      }
    },

    selectAccount(accountId) {
      this.currentAccountId = accountId;
      localStorage.setItem('current_account_id', accountId);
      this.$message.success('已切换到账户: ' + accountId);
    },

    async viewDetail(accountId) {
      this.$router.push(`/account/${accountId}`);
    },

    async deposit(accountId) {
      // 跳转到入金页面
      this.$router.push(`/deposit?account=${accountId}`);
    },

    getRiskClass(ratio) {
      if (ratio > 0.8) return 'danger';
      if (ratio > 0.5) return 'warning';
      return 'safe';
    }
  }
}
</script>

<style scoped>
.accounts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 20px;
  margin-top: 20px;
}

.account-card {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  padding: 20px;
  cursor: pointer;
  transition: all 0.3s;
}

.account-card:hover {
  border-color: #1890ff;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.account-card.active {
  border-color: #52c41a;
  background-color: #f6ffed;
}

.account-id {
  font-size: 12px;
  color: #999;
  margin: 5px 0;
}

.account-stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 15px 0;
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat span {
  font-size: 12px;
  color: #666;
}

.stat strong {
  font-size: 16px;
  margin-top: 5px;
}

.danger { color: #f5222d; }
.warning { color: #faad14; }
.safe { color: #52c41a; }
</style>

🎯 7. API 路由总结

用户认证

  • POST /api/auth/register - 注册
  • POST /api/auth/login - 登录
  • GET /api/auth/user/{user_id} - 获取用户信息

账户管理

  • GET /api/user/{user_id}/accounts - 查询用户的所有账户
  • POST /api/user/{user_id}/account/create - 创建新账户
  • GET /api/account/{account_id} - 查询单个账户详情
  • POST /api/account/deposit - 入金
  • POST /api/account/withdraw - 出金

交易相关

  • POST /api/order/submit - 下单
  • POST /api/order/cancel - 撤单
  • GET /api/order/{order_id} - 查询订单
  • GET /api/order/user/{account_id} - 查询用户订单
  • GET /api/position/{account_id} - 查询持仓
  • GET /api/trades/user/{account_id} - 查询成交

市场数据

  • GET /api/market/instruments - 合约列表
  • GET /api/market/tick/{instrument_id} - 行情快照
  • GET /api/market/orderbook/{instrument_id} - 盘口数据

⚠️ 常见错误

错误 1: 用 user_id 查账户详情

// ❌ 错误
fetch(`/api/account/${user_id}`)  // 404: Account not found

// ✅ 正确
fetch(`/api/user/${user_id}/accounts`)  // 返回账户列表

错误 2: 下单时传错 ID

// ❌ 错误: 传了 user_id
{
  "user_id": "8d482456-9fab-4d1b-9c2c-bf80cb3ff509"  // UUID
}

// ✅ 正确: 传 account_id
{
  "user_id": "ACC_9bc0b5268d4741cb8e03d766565f3fc8"  // account_id
}

错误 3: 混淆 User 和 Account

// User (用户): 8d482456-9fab-4d1b-9c2c-bf80cb3ff509
// Account (账户): ACC_9bc0b5268d4741cb8e03d766565f3fc8

// 登录时保存 user_id
localStorage.setItem('user_id', user_id);

// 查询账户列表
GET /api/user/{user_id}/accounts

// 选择账户后保存 account_id
localStorage.setItem('current_account_id', account_id);

// 交易时使用 account_id
POST /api/order/submit { user_id: account_id }

📞 技术支持

如有问题,请查看:


最后更新: 2025-10-06 API 版本: 1.0