打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

查看Xray中各用户流量使用情况的脚本

来自md5.pw
Koud Wind留言 | 贡献2026年2月18日 (三) 07:27的版本
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

预览



要求

Xray中需要配置, 分别开启 统计 / API / 本地策略, 且用户设置了emaillevel

配置参考:

{
  "stats": {},

  "api": {
    "tag": "API",
    "listen": "127.0.0.1:8080",
    "services": [
      "LoggerService",
      "StatsService"
    ]
  },

  "policy": {
    "levels": {
      "0": {
        "handshake": 5,
        "connIdle": 600,
        "statsUserUplink": true,
        "statsUserDownlink": true,
        "statsUserOnline": true
      }
    },
    "system": {
      "statsInboundUplink": true,
      "statsInboundDownlink": true
    }
  }
}

用户配置参考:

{
    "id": "UserId123456",
    "email": "易于标识的名称",
    "level": 0
}

内容

#!/usr/bin/env bash
set -euo pipefail

XRAY_BIN="${XRAY_BIN:-xray}"
# 自行修改 Xray API 地址
SERVER="${1:-127.0.0.1:8080}"

declare -A IN_UP IN_DOWN IN_TOTAL
declare -A U_UP U_DOWN U_TOTAL
declare -A USERSET

bytes_to_gb() {
  local b="${1-}"
  if [[ -z "${b}" ]]; then
    echo "null"
    return 0
  fi
  awk -v b="${b}" 'BEGIN{printf "%.3f", b/1024/1024/1024}'
}

normalize_stats_objects() {
  tr -d '\n\r\t ' \
  | grep -oE '\{"name":"[^"]+","value":[0-9]+\}'
}

extract_field() {
  local obj="$1"
  local key="$2"
  echo "$obj" | sed -nE "s/.*\"${key}\":\"?([^\",}]*)\"?.*/\1/p"
}

accumulate_stats() {
  local stats_json="$1"
  local line name value kind tag dir email

  while IFS= read -r line; do
    [[ -z "$line" ]] && continue

    name="$(extract_field "$line" "name")"
    [[ -z "$name" ]] && continue

    value="$(echo "$line" | sed -nE 's/.*"value":([0-9]+).*/\1/p')"
    value="${value:-0}"

    kind="$(awk -F'>>>' '{print $1}' <<<"$name")"

    if [[ "$kind" == "inbound" ]]; then
      tag="$(awk -F'>>>' '{print $2}' <<<"$name")"
      dir="$(awk -F'>>>' '{print $4}' <<<"$name")"

      if [[ "$dir" == "uplink" ]]; then
        IN_UP["$tag"]=$(( ${IN_UP["$tag"]:-0} + value ))
      elif [[ "$dir" == "downlink" ]]; then
        IN_DOWN["$tag"]=$(( ${IN_DOWN["$tag"]:-0} + value ))
      fi
      IN_TOTAL["$tag"]=$(( ${IN_TOTAL["$tag"]:-0} + value ))

    elif [[ "$kind" == "user" ]]; then
      email="$(awk -F'>>>' '{print $2}' <<<"$name")"
      dir="$(awk -F'>>>' '{print $4}' <<<"$name")"

      USERSET["$email"]=1

      if [[ "$dir" == "uplink" ]]; then
        U_UP["$email"]=$(( ${U_UP["$email"]:-0} + value ))
      elif [[ "$dir" == "downlink" ]]; then
        U_DOWN["$email"]=$(( ${U_DOWN["$email"]:-0} + value ))
      fi
      U_TOTAL["$email"]=$(( ${U_TOTAL["$email"]:-0} + value ))
    fi
  done < <(echo "$stats_json" | normalize_stats_objects)
}

query_online_ips() {
  local email="$1"
  local out pairs status

  out="$("$XRAY_BIN" api statsonlineiplist -s="$SERVER" -email "$email" 2>/dev/null || true)"

  pairs="$(echo "$out" | tr -d '\n\r\t ' \
  | grep -oE '"([0-9]{1,3}\.){3}[0-9]{1,3}":[0-9]+' \
  | sed -E 's/"//g' \
  | awk -F: '{
      ip=$1
      ts=$2
      cmd="TZ=Asia/Shanghai date -d @"ts" \"+%Y-%m-%d %H:%M:%S\""
      cmd | getline t
      close(cmd)
      printf "%s(%s) ", ip, t
    }' \
  | sed -E 's/[[:space:]]+$//')"

  if [[ -n "$pairs" ]]; then
    status="ONLINE"
    echo "$status"
    echo "$pairs"
  else
    echo "OFFLINE"
    echo "-"
  fi
}

main() {
  local stats_json
  stats_json="$("$XRAY_BIN" api statsquery --server="$SERVER" 2>/dev/null || true)"
  #echo "$stats_json"

  if [[ -z "$stats_json" ]]; then
    echo "Server: $SERVER"
    echo "Unit: GB, bytes divided by 1024^3"
    echo
    echo "Inbound traffic"
    printf "%-22s %14s %14s %14s\n" "tag" "uplink_GB" "downlink_GB" "total_GB"
    echo
    echo "User traffic and online status"
    printf "%-24s %14s %14s %14s %8s %s\n" "email" "uplink_GB" "downlink_GB" "total_GB" "status" "ips"
    exit 0
  fi

  accumulate_stats "$stats_json"

  echo "Server: $SERVER"
  echo "Unit: GB, bytes divided by 1024^3"
  echo

  echo "Inbound traffic"
  printf "%-22s %14s %14s %14s\n" "tag" "uplink_GB" "downlink_GB" "total_GB"

  {
    for tag in "${!IN_TOTAL[@]}"; do
      echo "$tag"
    done
  } | sort | while IFS= read -r tag; do
    local up down total up_s down_s total_s

    if [[ -v IN_UP["$tag"] ]]; then up="${IN_UP["$tag"]}"; else up=""; fi
    if [[ -v IN_DOWN["$tag"] ]]; then down="${IN_DOWN["$tag"]}"; else down=""; fi
    if [[ -v IN_TOTAL["$tag"] ]]; then total="${IN_TOTAL["$tag"]}"; else total=""; fi

    up_s="$(bytes_to_gb "$up")"
    down_s="$(bytes_to_gb "$down")"
    total_s="$(bytes_to_gb "$total")"

    printf "%-22s %14s %14s %14s\n" "$tag" "$up_s" "$down_s" "$total_s"
  done

  echo
  echo "User traffic and online status"
  printf "%-24s %14s %14s %14s %8s %s\n" "email" "uplink_GB" "downlink_GB" "total_GB" "status" "ips"

  {
    for email in "${!USERSET[@]}"; do
      echo "$email"
    done
  } | sort | while IFS= read -r email; do
    local up down total up_s down_s total_s status ips
    local qout

    if [[ -v U_UP["$email"] ]]; then up="${U_UP["$email"]}"; else up=""; fi
    if [[ -v U_DOWN["$email"] ]]; then down="${U_DOWN["$email"]}"; else down=""; fi
    if [[ -v U_TOTAL["$email"] ]]; then total="${U_TOTAL["$email"]}"; else total=""; fi

    up_s="$(bytes_to_gb "$up")"
    down_s="$(bytes_to_gb "$down")"
    total_s="$(bytes_to_gb "$total")"

    qout="$(query_online_ips "$email")"
    status="$(sed -n '1p' <<<"$qout")"
    ips="$(sed -n '2p' <<<"$qout")"

    printf "%-24s %14s %14s %14s %8s %s\n" \
      "$email" "$up_s" "$down_s" "$total_s" "$status" "$ips"
  done
}

main "$@"

推荐配置定时任务:

TZ=Asia/Shanghai
# 每月8号早上7点重置统计信息
0 7 8 * * /usr/local/bin/xray api statsquery -s=127.0.0.1:8080 -reset=true >/dev/null 2>&1
# 每天早上7点重启日志记录器
0 7 * * * /usr/local/bin/xray api restartlogger -s=127.0.0.1:8080