package handler import ( "net/http" "strconv" "time" "api-server/internal/auth" "api-server/internal/db" "github.com/gin-gonic/gin" ) // LogHandler 调用日志查询处理器 type LogHandler struct { database *db.DB } // NewLogHandler 创建 func NewLogHandler(database *db.DB) *LogHandler { return &LogHandler{database: database} } // ListMyLogs 开发者查询自己的调用日志 // GET /api/developer/logs?product_id=&since=&limit=&offset= func (h *LogHandler) ListMyLogs(c *gin.Context) { user := auth.GetUser(c) if user == nil { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "请先登录"}) return } productID, _ := strconv.ParseInt(c.Query("product_id"), 10, 64) sinceStr := c.Query("since") limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) if limit < 1 || limit > 200 { limit = 50 } var since time.Time if sinceStr != "" { since, _ = time.Parse(time.RFC3339, sinceStr) } logs, total, err := h.database.ListCallLogs(user.ID, productID, since, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "data": gin.H{ "total": total, "items": logs, }, }) } // ListAllLogs 管理员查询所有调用日志 // GET /api/admin/logs?user_id=&product_id=&since=&limit=&offset= func (h *LogHandler) ListAllLogs(c *gin.Context) { userID, _ := strconv.ParseInt(c.Query("user_id"), 10, 64) productID, _ := strconv.ParseInt(c.Query("product_id"), 10, 64) sinceStr := c.Query("since") limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) if limit < 1 || limit > 500 { limit = 50 } var since time.Time if sinceStr != "" { since, _ = time.Parse(time.RFC3339, sinceStr) } logs, total, err := h.database.ListCallLogs(userID, productID, since, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{ "code": 0, "data": gin.H{ "total": total, "items": logs, }, }) } // LogSummary 管理员查看 API 调用摘要(按产品聚合) // GET /api/admin/logs/summary func (h *LogHandler) LogSummary(c *gin.Context) { summary, err := h.database.GetCallLogSummaryByProduct() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "data": summary}) } // ProductStats 管理员查看产品订阅统计 // GET /api/admin/product-stats func (h *LogHandler) ProductStats(c *gin.Context) { products, err := h.database.ListAPIProducts("") if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } type ProdStat struct { ProductID int64 `json:"product_id"` Name string `json:"name"` Slug string `json:"slug"` Status string `json:"status"` Subscribers int64 `json:"subscribers"` } stats := make([]ProdStat, 0, len(products)) for _, p := range products { var subs int64 h.database.Conn().QueryRow( `SELECT COUNT(*) FROM api_subscriptions WHERE product_id = ? AND status = 'active'`, p.ID, ).Scan(&subs) stats = append(stats, ProdStat{ ProductID: p.ID, Name: p.Name, Slug: p.Slug, Status: p.Status, Subscribers: subs, }) } c.JSON(http.StatusOK, gin.H{"code": 0, "data": stats}) } // ListAllSubs 管理员查看所有订阅 // GET /api/admin/subscriptions func (h *LogHandler) ListAllSubs(c *gin.Context) { subs, err := h.database.ListAllSubscriptions() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } if subs == nil { subs = []db.APISubscription{} } c.JSON(http.StatusOK, gin.H{"code": 0, "data": subs}) } // SetSubQuota 管理员设置订阅配额 // PUT /api/admin/subscriptions/:id/quota func (h *LogHandler) SetSubQuota(c *gin.Context) { id, _ := strconv.ParseInt(c.Param("id"), 10, 64) var req struct { Quota int64 `json:"quota" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 1, "msg": "参数错误"}) return } if err := h.database.SetSubscriptionQuota(id, req.Quota); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 1, "msg": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"code": 0, "msg": "配额已更新"}) }