访问统计 API
提供访问者数据的统计和分析功能,支持访问量统计、地理位置分析和用户行为追踪。
📊 接口信息
- 接口路径:
/stats - 请求方法:
GET - 响应格式: JSON
- 数据源: EdgeOne KV Storage (访问者数据)
🚀 快速开始
基础调用
bash
curl https://www.160621.xyz/api/statsJavaScript 调用
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
}
}📋 响应字段
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
code | number | 业务状态码 | 200 |
status | string | 状态标识 | "success" |
message | string | 提示信息 | "获取统计数据成功" |
data | object | 统计数据对象 | {} |
timestamp | number | 服务器响应时间戳 | 1736123456789 |
data 字段说明
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
type | string | 统计类型 | "overview" |
date | string | 数据时间 | "2025-01-19T12:00:00Z" |
global | object | 全局统计 | { total_visits, unique_visitors, ... } |
distribution | object | 地理分布 | { 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. 性能优化
- 大数据量时考虑分页加载
- 使用图表时注意性能,避免频繁重绘
- 移动端适当简化图表显示