跳到主要内容

Go SDK

XLADA 没有官方的 Go SDK。XLADA 的 API 与 Remnawave 的 API 完全一致, 因此客户端可以用标准库 net/http 搭建,不需要任何外部 依赖 —— 这样你就不依赖他人的发布周期。

兼容性

社区为 Remnawave 维护着由同一份 OpenAPI 规范生成的 Go SDK。 XLADA 对相同的路径作出响应,因此它在这里同样适用, 但我们没有用 XLADA 对它做过官方测试。下面的示例不依赖任何模块。

安装​

需要 Go 1.18+(用到了 any)以及项目模块:

mkdir xlada-bot && cd xlada-bot
go mod init example.com/xlada-bot

不需要外部包 —— 只需要 net/http、encoding/json 和 context。

客户端​

令牌可以是 POST /api/auth/login 返回的 JWT,也可以是 POST /api/tokens 返回的长期 API 令牌。两者都通过 Authorization: Bearer 请求头传递。直接访问面板时,请加上 X-Forwarded-For 和 X-Forwarded-Proto: https 请求头, 否则面板的 middleware 会断开连接。

package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

type Client struct {
baseURL string
token string
http *http.Client
}

func NewClient(baseURL, token string) *Client {
return &Client{
baseURL: baseURL,
token: token,
http: &http.Client{Timeout: 30 * time.Second},
}
}

func (c *Client) do(ctx context.Context, method, path string, body, out any) error {
var payload []byte
if body != nil {
var err error
if payload, err = json.Marshal(body); err != nil {
return err
}
}

req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("X-Forwarded-For", "127.0.0.1")
req.Header.Set("X-Forwarded-Proto", "https")

res, err := c.http.Do(req)
if err != nil {
return err
}
defer res.Body.Close()

if res.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("xlada: %s %s: %s", method, path, res.Status)
}

if out != nil {
return json.NewDecoder(res.Body).Decode(out)
}
return nil
}

用户列表​

分页输出由查询参数 start(偏移量)和 size (每页大小,1–1000)指定。

type user struct {
ID int `json:"id"`
Username string `json:"username"`
Status string `json:"status"`
ShortUUID string `json:"shortUuid"`
ExpireAt string `json:"expireAt"`
}

type usersResponse struct {
Response struct {
Users []user `json:"users"`
Total int `json:"total"`
} `json:"response"`
}

func listUsers(ctx context.Context, c *Client) error {
var out usersResponse
if err := c.do(ctx, http.MethodGet, "/api/users?start=0&size=25", nil, &out); err != nil {
return err
}

for _, u := range out.Response.Users {
fmt.Printf("%s\t%s\t%s\n", u.Username, u.Status, u.ExpireAt)
}
fmt.Printf("总计: %d\n", out.Response.Total)
return nil
}

创建用户​

POST /api/users 接受 username(3–36 个字符,仅限 a-z、A-Z、 0-9、_、-)以及必填的、ISO 8601 格式的 expireAt。

type createUserRequest struct {
Username string `json:"username"`
Status string `json:"status,omitempty"`
ExpireAt string `json:"expireAt"`
TrafficLimitBytes int64 `json:"trafficLimitBytes,omitempty"`
TrafficLimitStrategy string `json:"trafficLimitStrategy,omitempty"`
}

func createUser(ctx context.Context, c *Client) error {
var out struct {
Response struct {
ID int `json:"id"`
ShortUUID string `json:"shortUuid"`
} `json:"response"`
}

err := c.do(ctx, http.MethodPost, "/api/users", createUserRequest{
Username: "john_doe",
Status: "ACTIVE",
ExpireAt: "2026-01-17T15:38:45.065Z",
TrafficLimitStrategy: "NO_RESET",
}, &out)
if err != nil {
return err
}

fmt.Printf("已创建 id=%d, shortUuid=%s\n", out.Response.ID, out.Response.ShortUUID)
return nil
}

获取节点​

具体节点通过 uuid 查询,节点列表通过 GET /api/nodes 查询。

func getNode(ctx context.Context, c *Client, uuid string) error {
var out struct {
Response struct {
Name string `json:"name"`
Address string `json:"address"`
Port *int `json:"port"`
IsConnected bool `json:"isConnected"`
} `json:"response"`
}

if err := c.do(ctx, http.MethodGet, "/api/nodes/"+uuid, nil, &out); err != nil {
return err
}

fmt.Printf("%s (%s) connected=%v\n", out.Response.Name, out.Response.Address, out.Response.IsConnected)
return nil
}
提示

所有响应结构都在 API Specification 页面的规范中有说明。 json 标签直接取自那里 —— 面板的字段 使用 camelCase 命名。

接下来​