diff --git a/handlers/handlers.go b/handlers/handlers.go
index 34d4bb2..5f72f2e 100644
--- a/handlers/handlers.go
+++ b/handlers/handlers.go
@@ -1,9 +1,18 @@
package handlers
import (
+ "encoding/json"
+ "fmt"
+ "log"
"net/http"
+ "net/url"
+ "random-api-go/monitoring"
"random-api-go/router"
+ "random-api-go/services"
"random-api-go/stats"
+ "random-api-go/utils"
+ "strings"
+ "time"
)
type Router interface {
@@ -15,24 +24,146 @@ type Handlers struct {
}
func (h *Handlers) HandleAPIRequest(w http.ResponseWriter, r *http.Request) {
- HandleAPIRequest(w, r)
+ start := time.Now()
+ realIP := utils.GetRealIP(r)
+
+ // 获取并处理 referer
+ sourceInfo := "direct"
+ if referer := r.Referer(); referer != "" {
+ if parsedURL, err := url.Parse(referer); err == nil {
+ sourceInfo = parsedURL.Host + parsedURL.Path
+ if parsedURL.RawQuery != "" {
+ sourceInfo += "?" + parsedURL.RawQuery
+ }
+ }
+ }
+
+ path := strings.TrimPrefix(r.URL.Path, "/")
+ pathSegments := strings.Split(path, "/")
+
+ if len(pathSegments) < 2 {
+ monitoring.LogRequest(monitoring.RequestLog{
+ Time: time.Now(),
+ Path: r.URL.Path,
+ Method: r.Method,
+ StatusCode: http.StatusNotFound,
+ Latency: float64(time.Since(start).Microseconds()) / 1000,
+ IP: realIP,
+ Referer: sourceInfo,
+ })
+ http.NotFound(w, r)
+ return
+ }
+
+ prefix := pathSegments[0]
+ suffix := pathSegments[1]
+
+ services.Mu.RLock()
+ csvPath, ok := services.CSVPathsCache[prefix][suffix]
+ services.Mu.RUnlock()
+
+ if !ok {
+ monitoring.LogRequest(monitoring.RequestLog{
+ Time: time.Now(),
+ Path: r.URL.Path,
+ Method: r.Method,
+ StatusCode: http.StatusNotFound,
+ Latency: float64(time.Since(start).Microseconds()) / 1000,
+ IP: realIP,
+ Referer: sourceInfo,
+ })
+ http.NotFound(w, r)
+ return
+ }
+
+ selector, err := services.GetCSVContent(csvPath)
+ if err != nil {
+ log.Printf("Error fetching CSV content: %v", err)
+ monitoring.LogRequest(monitoring.RequestLog{
+ Time: time.Now(),
+ Path: r.URL.Path,
+ Method: r.Method,
+ StatusCode: http.StatusInternalServerError,
+ Latency: float64(time.Since(start).Microseconds()) / 1000,
+ IP: realIP,
+ Referer: sourceInfo,
+ })
+ http.Error(w, "Failed to fetch content", http.StatusInternalServerError)
+ return
+ }
+
+ if len(selector.URLs) == 0 {
+ monitoring.LogRequest(monitoring.RequestLog{
+ Time: time.Now(),
+ Path: r.URL.Path,
+ Method: r.Method,
+ StatusCode: http.StatusNotFound,
+ Latency: float64(time.Since(start).Microseconds()) / 1000,
+ IP: realIP,
+ Referer: sourceInfo,
+ })
+ http.Error(w, "No content available", http.StatusNotFound)
+ return
+ }
+
+ randomURL := selector.GetRandomURL()
+ endpoint := fmt.Sprintf("%s/%s", prefix, suffix)
+ h.Stats.IncrementCalls(endpoint)
+
+ duration := time.Since(start)
+ monitoring.LogRequest(monitoring.RequestLog{
+ Time: time.Now(),
+ Path: r.URL.Path,
+ Method: r.Method,
+ StatusCode: http.StatusFound,
+ Latency: float64(duration.Microseconds()) / 1000,
+ IP: realIP,
+ Referer: sourceInfo,
+ })
+
+ log.Printf(" %-12s | %-15s | %-6s | %-20s | %-20s | %-50s",
+ duration,
+ realIP,
+ r.Method,
+ r.URL.Path,
+ sourceInfo,
+ randomURL,
+ )
+
+ http.Redirect(w, r, randomURL, http.StatusFound)
}
func (h *Handlers) HandleStats(w http.ResponseWriter, r *http.Request) {
- HandleStats(w, r)
+ w.Header().Set("Content-Type", "application/json")
+ stats := h.Stats.GetStats()
+ if err := json.NewEncoder(w).Encode(stats); err != nil {
+ http.Error(w, "Error encoding stats", http.StatusInternalServerError)
+ log.Printf("Error encoding stats: %v", err)
+ }
}
func (h *Handlers) HandleURLStats(w http.ResponseWriter, r *http.Request) {
- HandleURLStats(w, r)
+ w.Header().Set("Content-Type", "application/json")
+ services.Mu.RLock()
+ response := map[string]interface{}{
+ "paths": services.CSVPathsCache,
+ }
+ services.Mu.RUnlock()
+ json.NewEncoder(w).Encode(response)
}
func (h *Handlers) HandleMetrics(w http.ResponseWriter, r *http.Request) {
- HandleMetrics(w, r)
+ metrics := monitoring.CollectMetrics()
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(metrics)
}
func (h *Handlers) Setup(r *router.Router) {
+ // 动态路由处理
r.HandleFunc("/pic/", h.HandleAPIRequest)
r.HandleFunc("/video/", h.HandleAPIRequest)
+
+ // API 统计和监控
r.HandleFunc("/stats", h.HandleStats)
r.HandleFunc("/urlstats", h.HandleURLStats)
r.HandleFunc("/metrics", h.HandleMetrics)
diff --git a/monitoring/metrics.go b/monitoring/metrics.go
index 1b1a6c5..0aed431 100644
--- a/monitoring/metrics.go
+++ b/monitoring/metrics.go
@@ -2,6 +2,7 @@ package monitoring
import (
"runtime"
+ "strings"
"sync"
"time"
)
@@ -95,16 +96,19 @@ func LogRequest(log RequestLog) {
metrics.StatusCodes[log.StatusCode]++
metrics.TopReferers[log.Referer]++
- // 更新路径延迟
- if existing, ok := metrics.PathLatencies[log.Path]; ok {
- metrics.PathLatencies[log.Path] = (existing + log.Latency) / 2
- } else {
- metrics.PathLatencies[log.Path] = log.Latency
- }
+ // 只记录 API 请求
+ if strings.HasPrefix(log.Path, "/pic/") || strings.HasPrefix(log.Path, "/video/") {
+ // 更新路径延迟
+ if existing, ok := metrics.PathLatencies[log.Path]; ok {
+ metrics.PathLatencies[log.Path] = (existing + log.Latency) / 2
+ } else {
+ metrics.PathLatencies[log.Path] = log.Latency
+ }
- // 保存最近请求记录
- metrics.RecentRequests = append(metrics.RecentRequests, log)
- if len(metrics.RecentRequests) > 100 {
- metrics.RecentRequests = metrics.RecentRequests[1:]
+ // 保存最近请求记录
+ metrics.RecentRequests = append(metrics.RecentRequests, log)
+ if len(metrics.RecentRequests) > 100 {
+ metrics.RecentRequests = metrics.RecentRequests[1:]
+ }
}
}
diff --git a/public/config/endpoint.json b/public/config/endpoint.json
new file mode 100644
index 0000000..015571c
--- /dev/null
+++ b/public/config/endpoint.json
@@ -0,0 +1,10 @@
+{
+ "pic": {
+ "all": "随机图片",
+ "fj": "随机风景",
+ "loading": "随机加载图"
+ },
+ "video": {
+ "all": "随机视频"
+ }
+}
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 2bc02a7..23d0628 100644
--- a/public/index.html
+++ b/public/index.html
@@ -34,9 +34,9 @@
// 用于存储配置的全局变量
let cachedEndpointConfig = null;
+
// 加载配置的函数
async function loadEndpointConfig() {
- // 如果已经有缓存的配置,直接返回
if (cachedEndpointConfig) {
return cachedEndpointConfig;
}
@@ -46,54 +46,50 @@
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
- // 保存配置到缓存
cachedEndpointConfig = await response.json();
return cachedEndpointConfig;
} catch (error) {
console.error('加载endpoint配置失败:', error);
- return {}; // 返回空对象作为默认值
+ return {};
}
}
// 加载统计数据
async function loadStats() {
try {
- // 添加刷新动画
- const refreshIcon = document.querySelector('.refresh-icon');
- const summaryElement = document.getElementById('stats-summary');
- const detailElement = document.getElementById('stats-detail');
+ const [statsResponse, urlStatsResponse, endpointConfig] = await Promise.all([
+ fetch('/stats'),
+ fetch('/urlstats'),
+ loadEndpointConfig()
+ ]);
- if (refreshIcon) {
- refreshIcon.classList.add('spinning');
- }
- if (summaryElement) summaryElement.classList.add('fade');
- if (detailElement) detailElement.classList.add('fade');
+ const stats = await statsResponse.json();
+ const urlStats = await urlStatsResponse.json();
- // 获取数据
- const response = await fetch('/stats');
- const stats = await response.json();
-
- // 更新统计
- await updateStats(stats);
-
- // 移除动画
- setTimeout(() => {
- if (refreshIcon) {
- refreshIcon.classList.remove('spinning');
+ // 只显示 endpoint.json 中配置的路径
+ const filteredPaths = {};
+ for (const [category, types] of Object.entries(endpointConfig)) {
+ if (urlStats.paths[category]) {
+ filteredPaths[category] = {};
+ for (const [type, desc] of Object.entries(types)) {
+ if (urlStats.paths[category][type]) {
+ filteredPaths[category][type] = {
+ path: urlStats.paths[category][type],
+ description: desc
+ };
+ }
+ }
}
- if (summaryElement) summaryElement.classList.remove('fade');
- if (detailElement) detailElement.classList.remove('fade');
- }, 300);
+ }
+ await updateStats(stats, filteredPaths);
} catch (error) {
console.error('Error loading stats:', error);
}
}
- // 处理统计数据
- async function updateStats(stats) {
- const endpointConfig = await loadEndpointConfig();
-
+ // 更新统计显示
+ async function updateStats(stats, paths) {
const startDate = new Date('2024-11-1');
const today = new Date();
const daysSinceStart = Math.ceil((today - startDate) / (1000 * 60 * 60 * 24));
@@ -101,80 +97,82 @@
let totalCalls = 0;
let todayCalls = 0;
+ // 计算总调用次数
Object.entries(stats).forEach(([endpoint, stat]) => {
- if (endpointConfig[endpoint]) {
- totalCalls += stat.total_calls;
- todayCalls += stat.today_calls;
- }
+ totalCalls += stat.total_calls;
+ todayCalls += stat.today_calls;
});
const avgCallsPerDay = Math.round(totalCalls / daysSinceStart);
+ // 更新总览统计
const summaryHtml = `
-
-
-
-
今日总调用:${todayCalls} 次
-
平均每天调用:${avgCallsPerDay} 次
-
总调用次数:${totalCalls} 次
-
统计开始日期:2024-11-1
-
-
- `;
+
+
+
+
今日总调用:${todayCalls} 次
+
平均每天调用:${avgCallsPerDay} 次
+
总调用次数:${totalCalls} 次
+
统计开始日期:2024-11-1
+
+
+ `;
- const sortedEndpoints = Object.entries(stats)
- .filter(([endpoint]) => endpointConfig[endpoint])
- .sort(([endpointA], [endpointB]) => {
- const orderA = endpointConfig[endpointA].order;
- const orderB = endpointConfig[endpointB].order;
- return orderA - orderB;
- });
+ // 获取 endpoint 配置
+ const endpointConfig = await loadEndpointConfig();
+ // 生成详细统计表格
let detailHtml = `
-
-
-
- 接口 |
- 今日调用 |
- 总调用 |
- URL数量 |
- 查看 |
-
-
-
- `;
+
+
+
+ 接口名称 |
+ 今日调用 |
+ 总调用 |
+ URL数量 |
+ 操作 |
+
+
+
+ `;
- // 同时加载URL统计数据
- const urlStatsResponse = await fetch('/urlstats');
- const urlStats = await urlStatsResponse.json();
+ // 按 order 排序并生成表格行
+ const endpoints = Object.entries(endpointConfig)
+ .sort(([, a], [, b]) => a.order - b.order);
- sortedEndpoints.forEach(([endpoint, stat]) => {
+ for (const [endpoint, config] of endpoints) {
+ const stat = stats[endpoint] || { today_calls: 0, total_calls: 0 };
const urlCount = urlStats[endpoint]?.total_urls || 0;
+
detailHtml += `
- ${getDisplayName(endpoint, endpointConfig)}
+ onclick="copyToClipboard('${endpoint}')"
+ class="endpoint-link"
+ title="点击复制链接">
+ ${config.name}
|
${stat.today_calls} |
${stat.total_calls} |
${urlCount} |
- 👀 |
+
+ 👀
+ 📋
+ |
`;
- });
+ }
detailHtml += `
`;
+ // 更新 DOM
const summaryElement = document.getElementById('stats-summary');
const detailElement = document.getElementById('stats-detail');
@@ -182,46 +180,15 @@
if (detailElement) detailElement.innerHTML = detailHtml;
}
- // 获取显示名称的函数
- function getDisplayName(endpoint, endpointConfig) {
- return endpointConfig[endpoint]?.name || endpoint;
- }
-
- // 修改事件处理函数来处理异步更新
- async function refreshStats() {
- try {
- // 如果配置还没有加载,先加载配置
- if (!cachedEndpointConfig) {
- await loadEndpointConfig();
- }
-
- // 然后获取统计数据
- const response = await fetch('/stats');
- const stats = await response.json();
- await updateStats(stats);
- } catch (error) {
- console.error('Error:', error);
- }
- }
-
- // 初始加载
- document.addEventListener('DOMContentLoaded', refreshStats);
-
-
- // 添加复制功能
+ // 复制链接功能
function copyToClipboard(endpoint) {
- const url = `https://random-api.czl.net/${endpoint}`;
+ const url = `${window.location.protocol}//${window.location.host}/${endpoint}`;
navigator.clipboard.writeText(url).then(() => {
- // 显示提示
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = '链接已复制到剪贴板!';
document.body.appendChild(toast);
-
- // 2秒后移除提示
- setTimeout(() => {
- toast.remove();
- }, 2000);
+ setTimeout(() => toast.remove(), 2000);
}).catch(err => {
console.error('复制失败:', err);
});