Skip to content

访问统计 API

提供访问者数据的统计和分析功能,支持访问量统计、地理位置分析和用户行为追踪。

📊 接口信息

  • 接口路径: /stats
  • 请求方法: GET
  • 响应格式: JSON
  • 数据源: EdgeOne KV Storage (访问者数据)

🚀 快速开始

基础调用

bash
curl https://www.160621.xyz/api/stats

JavaScript 调用

javascript
// 获取统计数据
fetch('https://www.160621.xyz/api/stats')
  .then(response => response.json())
  .then(result => {
    if (result.status === 'success') {
      console.log('访问统计:', result.data)
      console.log('总访问次数:', result.data.global.total_visits)
      console.log('独立访客数:', result.data.global.unique_visitors)
      console.log('今日访问:', result.data.global.today_visits)
    } else {
      console.error('获取失败:', result.message)
    }
  })

// 使用 async/await
async function getStats() {
  try {
    const response = await fetch('https://www.160621.xyz/api/stats')
    const result = await response.json()

    if (result.status === 'success') {
      return result.data
    } else {
      throw new Error(result.message)
    }
  } catch (error) {
    console.error('获取统计数据失败:', error)
    throw error
  }
}

📋 响应字段

字段名类型说明示例
codenumber业务状态码200
statusstring状态标识"success"
messagestring提示信息"获取统计数据成功"
dataobject统计数据对象{}
timestampnumber服务器响应时间戳1736123456789

data 字段说明

字段名类型说明示例
typestring统计类型"overview"
datestring数据时间"2025-01-19T12:00:00Z"
globalobject全局统计{ total_visits, unique_visitors, ... }
distributionobject地理分布{ countries, cities, isps }

📝 响应示例

完整统计响应

json
{
  "code": 200,
  "status": "success",
  "message": "获取统计数据成功",
  "data": {
    "type": "overview",
    "date": "2025-01-19T12:00:00Z",
    "global": {
      "total_visits": 1250,
      "unique_visitors": 856,
      "today_visits": 45
    },
    "distribution": {
      "top_countries": [
        {
          "country": "中国",
          "count": 800,
          "percentage": 64.0
        },
        {
          "country": "美国",
          "count": 200,
          "percentage": 16.0
        },
        {
          "country": "日本",
          "count": 150,
          "percentage": 12.0
        }
      ],
      "top_cities": [
        {
          "city": "深圳",
          "count": 156,
          "country": "中国",
          "percentage": 12.5
        },
        {
          "city": "北京",
          "count": 98,
          "country": "中国",
          "percentage": 7.8
        },
        {
          "city": "上海",
          "count": 87,
          "country": "中国",
          "percentage": 7.0
        }
      ],
      "top_isps": [
        {
          "isp": "腾讯云",
          "count": 320,
          "percentage": 25.6
        },
        {
          "isp": "阿里云",
          "count": 280,
          "percentage": 22.4
        },
        {
          "isp": "中国电信",
          "count": 200,
          "percentage": 16.0
        }
      ]
    }
  },
  "timestamp": 1736123456789
}

错误响应 - 参数错误

json
{
  "code": 400,
  "status": "error",
  "message": "缺少UUID参数",
  "data": {
    "code": "MISSING_UUID",
    "validTypes": ["overview", "user", "distribution", "endpoints"]
  },
  "timestamp": 1736123456789
}

错误响应 - 无效类型

json
{
  "code": 400,
  "status": "error",
  "message": "无效的类型参数",
  "data": {
    "code": "INVALID_TYPE",
    "validTypes": ["overview", "user", "distribution", "endpoints"]
  },
  "timestamp": 1736123456789
}

错误响应 - 服务器错误

json
{
  "code": 500,
  "status": "error",
  "message": "获取统计数据失败",
  "data": {
    "code": "STATS_ERROR",
    "details": "Internal server error"
  },
  "timestamp": 1736123456789
}

🎯 使用场景

1. 统计仪表板

javascript
class StatsDashboard {
  constructor(containerId) {
    this.container = document.getElementById(containerId)
    this.stats = null
  }
  
  async init() {
    await this.loadStats()
    this.renderDashboard()
    this.startAutoRefresh()
  }
  
  async loadStats() {
    try {
      const response = await fetch('https://www.160621.xyz/api/stats')
      const result = await response.json()

      if (result.status === 'success') {
        this.stats = result.data
      }
    } catch (error) {
      console.error('加载统计数据失败:', error)
    }
  }
  
  renderDashboard() {
    if (!this.stats) return
    
    this.container.innerHTML = `
      <div class="stats-dashboard">
        <div class="stats-overview">
          ${this.renderOverviewCards()}
        </div>
        <div class="stats-charts">
          ${this.renderCharts()}
        </div>
        <div class="stats-tables">
          ${this.renderTables()}
        </div>
      </div>
    `
    
    this.renderCharts()
  }
  
  renderOverviewCards() {
    const cards = [
      { title: '总访问次数', value: this.stats.global.total_visits, icon: '👥' },
      { title: '独立访客', value: this.stats.global.unique_visitors, icon: '👤' },
      { title: '今日访问', value: this.stats.global.today_visits, icon: '📅' },
      { title: '最后更新', value: this.formatTime(this.stats.date), icon: '🕒' }
    ]
    
    return cards.map(card => `
      <div class="stat-card">
        <div class="stat-icon">${card.icon}</div>
        <div class="stat-content">
          <h3 class="stat-value">${this.formatNumber(card.value)}</h3>
          <p class="stat-title">${card.title}</p>
        </div>
      </div>
    `).join('')
  }
  
  renderCharts() {
    return `
      <div class="chart-grid">
        <div class="chart-container">
          <h3>24小时访问分布</h3>
          <canvas id="hourly-chart"></canvas>
        </div>
        <div class="chart-container">
          <h3>7天访问趋势</h3>
          <canvas id="daily-chart"></canvas>
        </div>
        <div class="chart-container">
          <h3>设备分布</h3>
          <canvas id="device-chart"></canvas>
        </div>
        <div class="chart-container">
          <h3>浏览器统计</h3>
          <canvas id="browser-chart"></canvas>
        </div>
      </div>
    `
  }
  
  renderTables() {
    return `
      <div class="tables-grid">
        <div class="table-container">
          <h3>访问地区 TOP 5</h3>
          ${this.renderCountryTable()}
        </div>
        <div class="table-container">
          <h3>访问城市 TOP 5</h3>
          ${this.renderCityTable()}
        </div>
        <div class="table-container">
          <h3>ISP 提供商 TOP 5</h3>
          ${this.renderISPTable()}
        </div>
      </div>
    `
  }
  
  renderCountryTable() {
    return this.renderTable(
      ['排名', '国家', '访问次数', '占比'],
      this.stats.top_countries.slice(0, 5).map((item, index) => [
        index + 1,
        item.country,
        item.count,
        `${item.percentage}%`
      ])
    )
  }
  
  renderCityTable() {
    return this.renderTable(
      ['排名', '城市', '国家', '访问次数', '占比'],
      this.stats.top_cities.slice(0, 5).map((item, index) => [
        index + 1,
        item.city,
        item.country,
        item.count,
        `${item.percentage}%`
      ])
    )
  }
  
  renderISPTable() {
    return this.renderTable(
      ['排名', 'ISP', '访问次数', '占比'],
      this.stats.top_isps.slice(0, 5).map((item, index) => [
        index + 1,
        item.isp,
        item.count,
        `${item.percentage}%`
      ])
    )
  }
  
  renderTable(headers, rows) {
    return `
      <table class="stats-table">
        <thead>
          <tr>
            ${headers.map(header => `<th>${header}</th>`).join('')}
          </tr>
        </thead>
        <tbody>
          ${rows.map(row => `
            <tr>
              ${row.map(cell => `<td>${cell}</td>`).join('')}
            </tr>
          `).join('')}
        </tbody>
      </table>
    `
  }
  
  renderCharts() {
    // 这里可以集成 Chart.js 或其他图表库
    this.renderHourlyChart()
    this.renderDailyChart()
    this.renderDeviceChart()
    this.renderBrowserChart()
  }
  
  renderHourlyChart() {
    const canvas = document.getElementById('hourly-chart')
    if (!canvas) return
    
    // 简单的柱状图实现
    const ctx = canvas.getContext('2d')
    const data = this.stats.hourly_stats
    
    // 绘制逻辑...
  }
  
  formatNumber(num) {
    if (num >= 1000000) {
      return (num / 1000000).toFixed(1) + 'M'
    } else if (num >= 1000) {
      return (num / 1000).toFixed(1) + 'K'
    }
    return num.toString()
  }
  
  formatTime(timestamp) {
    return new Date(timestamp).toLocaleString('zh-CN')
  }
  
  startAutoRefresh() {
    // 每5分钟自动刷新
    setInterval(async () => {
      await this.loadStats()
      this.renderDashboard()
    }, 5 * 60 * 1000)
  }
}

// 使用示例
const dashboard = new StatsDashboard('stats-dashboard')
dashboard.init()

2. 访问趋势分析

javascript
class TrendAnalyzer {
  constructor() {
    this.historicalData = []
  }
  
  async loadTrendData(days = 30) {
    try {
      const response = await fetch(`https://www.160621.xyz/api/stats`)
      const result = await response.json()

      if (result.status === 'success') {
        this.historicalData = result.data.daily_stats
        return this.analyzeTrends()
      }
    } catch (error) {
      console.error('加载趋势数据失败:', error)
    }
  }
  
  analyzeTrends() {
    const trends = {
      growth: this.calculateGrowth(),
      peakHours: this.findPeakHours(),
      seasonality: this.detectSeasonality(),
      forecast: this.simpleForecast()
    }
    
    return trends
  }
  
  calculateGrowth() {
    if (this.historicalData.length < 2) return 0
    
    const recent = this.historicalData.slice(-7).reduce((sum, day) => sum + day.count, 0)
    const previous = this.historicalData.slice(-14, -7).reduce((sum, day) => sum + day.count, 0)
    
    return previous > 0 ? ((recent - previous) / previous * 100).toFixed(2) : 0
  }
  
  findPeakHours() {
    // 从 hourly_stats 中找到访问高峰
    // 这里需要额外的数据或API支持
    return ['20:00', '21:00', '22:00']
  }
  
  detectSeasonality() {
    // 检测访问的周期性模式
    const weekdays = []
    const weekends = []
    
    this.historicalData.forEach(day => {
      const date = new Date(day.date)
      const isWeekend = date.getDay() === 0 || date.getDay() === 6
      
      if (isWeekend) {
        weekends.push(day.count)
      } else {
        weekdays.push(day.count)
      }
    })
    
    const avgWeekday = weekdays.reduce((a, b) => a + b, 0) / weekdays.length
    const avgWeekend = weekends.reduce((a, b) => a + b, 0) / weekends.length
    
    return {
      weekday: avgWeekday.toFixed(2),
      weekend: avgWeekend.toFixed(2),
      difference: ((avgWeekday - avgWeekend) / avgWeekend * 100).toFixed(2)
    }
  }
  
  simpleForecast() {
    if (this.historicalData.length < 7) return null
    
    // 简单的线性回归预测
    const recent7Days = this.historicalData.slice(-7)
    const average = recent7Days.reduce((sum, day) => sum + day.count, 0) / 7
    
    return {
      tomorrow: Math.round(average),
      nextWeek: Math.round(average * 7),
      nextMonth: Math.round(average * 30)
    }
  }
  
  generateReport() {
    const trends = this.analyzeTrends()
    
    return `
# 访问趋势分析报告

## 📈 增长趋势
- 7天增长率: ${trends.growth}%
- 预测明日访问: ${trends.forecast?.tomorrow || 'N/A'}

## ⏰ 高峰时段
- 主要访问时段: ${trends.peakHours.join(', ')}

## 📅 周期性分析
- 工作日平均: ${trends.seasonality.weekday} 访问
- 周末平均: ${trends.seasonality.weekend} 访问
- 差异: ${trends.seasonality.difference}%

## 🔮 预测
- 下周预计: ${trends.forecast?.nextWeek || 'N/A'} 访问
- 下月预计: ${trends.forecast?.nextMonth || 'N/A'} 访问
    `
  }
}

// 使用示例
const analyzer = new TrendAnalyzer()
analyzer.loadTrendData().then(trends => {
  console.log('趋势分析:', trends)
})

3. React Hook 封装

jsx
import { useState, useEffect, useCallback } from 'react'

export function useStats(autoRefresh = false) {
  const [stats, setStats] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)
  
  const fetchStats = useCallback(async () => {
    setLoading(true)
    setError(null)

    try {
      const response = await fetch('https://www.160621.xyz/api/stats')
      const result = await response.json()

      if (result.status === 'success') {
        setStats(result.data)
      } else {
        throw new Error(result.message || '获取统计数据失败')
      }
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }, [])
  
  useEffect(() => {
    fetchStats()
    
    if (autoRefresh) {
      const interval = setInterval(fetchStats, 5 * 60 * 1000) // 5分钟刷新
      return () => clearInterval(interval)
    }
  }, [fetchStats, autoRefresh])
  
  return {
    stats,
    loading,
    error,
    refetch: fetchStats
  }
}

// 统计卡片组件
function StatsCard({ title, value, icon, trend }) {
  return (
    <div className="stats-card">
      <div className="stats-icon">{icon}</div>
      <div className="stats-content">
        <h3 className="stats-value">{value}</h3>
        <p className="stats-title">{title}</p>
        {trend && (
          <span className={`stats-trend ${trend > 0 ? 'positive' : 'negative'}`}>
            {trend > 0 ? '↗' : '↘'} {Math.abs(trend)}%
          </span>
        )}
      </div>
    </div>
  )
}

// 仪表板组件
function StatsDashboard() {
  const { stats, loading, error } = useStats(true)
  
  if (loading) return <div>正在加载统计数据...</div>
  if (error) return <div>加载失败: {error}</div>
  if (!stats) return null
  
  return (
    <div className="dashboard">
      <div className="overview-grid">
        <StatsCard
          title="总访问次数"
          value={stats.total_visits.toLocaleString()}
          icon="👥"
        />
        <StatsCard
          title="独立访客"
          value={stats.unique_visitors.toLocaleString()}
          icon="👤"
        />
        <StatsCard
          title="今日访问"
          value={stats.today_visits.toLocaleString()}
          icon="📅"
        />
        <StatsCard
          title="最后更新"
          value={new Date(stats.last_updated).toLocaleTimeString()}
          icon="🕒"
        />
      </div>
      
      <div className="charts-section">
        <div className="chart-container">
          <h3>访问地区分布</h3>
          <CountryChart data={stats.top_countries} />
        </div>
        
        <div className="chart-container">
          <h3>设备分布</h3>
          <DeviceChart data={stats.device_stats} />
        </div>
      </div>
      
      <div className="tables-section">
        <TopCountriesTable data={stats.top_countries} />
        <TopCitiesTable data={stats.top_cities} />
        <TopISPsTable data={stats.top_isps} />
      </div>
    </div>
  )
}

4. 导出功能

javascript
class StatsExporter {
  constructor() {
    this.stats = null
  }
  
  async loadStats() {
    try {
      const response = await fetch('https://www.160621.xyz/api/stats')
      const result = await response.json()

      if (result.status === 'success') {
        this.stats = result.data
        return result.data
      }
    } catch (error) {
      console.error('加载统计数据失败:', error)
    }
  }
  
  exportToCSV() {
    if (!this.stats) return

    const csvRows = []

    // 添加标题
    csvRows.push(['统计项目', '数值'].join(','))

    // 添加基础统计
    csvRows.push(`总访问次数,${this.stats.global.total_visits}`)
    csvRows.push(`独立访客,${this.stats.global.unique_visitors}`)
    csvRows.push(`今日访问,${this.stats.global.today_visits}`)
    csvRows.push('')

    // 添加地区统计
    csvRows.push('国家统计')
    csvRows.push('国家,访问次数,占比')
    this.stats.distribution.top_countries.forEach(country => {
      csvRows.push(`${country.country},${country.count},${country.percentage}%`)
    })
    csvRows.push('')

    // 添加城市统计
    csvRows.push('城市统计')
    csvRows.push('城市,国家,访问次数,占比')
    this.stats.distribution.top_cities.forEach(city => {
      csvRows.push(`${city.city},${city.country},${city.count},${city.percentage}%`)
    })
    csvRows.push('')

    // 添加每日统计
    csvRows.push('每日统计')
    csvRows.push('日期,访问次数')
    this.stats.global.daily_stats.forEach(day => {
      csvRows.push(`${day.date},${day.count}`)
    })

    // 创建下载链接
    const csvContent = csvRows.join('\n')
    const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8' })
    const url = URL.createObjectURL(blob)

    const a = document.createElement('a')
    a.href = url
    a.download = `访问统计_${new Date().toISOString().split('T')[0]}.csv`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }
  
  exportToJSON() {
    if (!this.stats) return
    
    const jsonContent = JSON.stringify(this.stats, null, 2)
    const blob = new Blob([jsonContent], { type: 'application/json' })
    const url = URL.createObjectURL(blob)
    
    const a = document.createElement('a')
    a.href = url
    a.download = `访问统计_${new Date().toISOString().split('T')[0]}.json`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }
  
  generateReport() {
    if (!this.stats) return

    const report = `
# EdgeOne API 访问统计报告

生成时间: ${new Date().toLocaleString('zh-CN')}

## 📊 基础统计
- 总访问次数: ${this.stats.global.total_visits.toLocaleString()}
- 独立访客数: ${this.stats.global.unique_visitors.toLocaleString()}
- 今日访问: ${this.stats.global.today_visits.toLocaleString()}

## 🌍 地区分布
### TOP 5 国家
${this.stats.distribution.top_countries.slice(0, 5).map((item, index) =>
  `${index + 1}. ${item.country}: ${item.count} (${item.percentage}%)`
).join('\n')}

### TOP 5 城市
${this.stats.distribution.top_cities.slice(0, 5).map((item, index) =>
  `${index + 1}. ${item.city} (${item.country}): ${item.count} (${item.percentage}%)`
).join('\n')}

## 🌐 ISP 分布
${this.stats.distribution.top_isps.slice(0, 5).map((item, index) =>
  `${index + 1}. ${item.isp}: ${item.count} (${item.percentage}%)`
).join('\n')}

---
数据来源: EdgeOne API 统计服务
    `

    const blob = new Blob([report], { type: 'text/markdown' })
    const url = URL.createObjectURL(blob)

    const a = document.createElement('a')
    a.href = url
    a.download = `访问统计报告_${new Date().toISOString().split('T')[0]}.md`
    document.body.appendChild(a)
    a.click()
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }
}

// 使用示例
const exporter = new StatsExporter()

// 加载数据并导出
exporter.loadStats().then(() => {
  // 导出为 CSV
  exporter.exportToCSV()
  
  // 导出为 JSON
  exporter.exportToJSON()
  
  // 生成报告
  exporter.generateReport()
})

⚠️ 注意事项

1. 数据隐私

javascript
// 统计数据应该是匿名的,不包含个人身份信息
const sanitizeVisitorData = (data) => {
  return {
    ...data,
    ip: data.ip ? hashIP(data.ip) : null, // IP 哈希化
    userAgent: data.userAgent ? parseUA(data.userAgent) : null // 只保留必要信息
  }
}

2. 缓存策略

javascript
// 统计数据可以适当缓存,提高性能
const statsCache = new Map()

async function getCachedStats(ttl = 300000) { // 5分钟
  const cacheKey = 'api_stats'
  const cached = statsCache.get(cacheKey)
  
  if (cached && Date.now() - cached.timestamp < ttl) {
    return cached.data
  }
  
  const data = await fetch('https://www.160621.xyz/api/stats').then(res => res.json())
  
  statsCache.set(cacheKey, {
    data,
    timestamp: Date.now()
  })
  
  return data
}

3. 性能优化

  • 大数据量时考虑分页加载
  • 使用图表时注意性能,避免频繁重绘
  • 移动端适当简化图表显示

基于 EdgeOne 边缘计算平台构建