Files
91-api-server/internal/handler/log_handler.go
yuqianhe 3e27017e42 feat: add auth, db, gateway, email, ratelimit, geoblock modules and new handlers
- New CLI commands: key, user
- New internal modules: auth (middleware, password, user), db (db, logs, products), email, gateway (gateway, hooks, middlewares), handler (admin, auth, dev, log, product, settings), middleware/geoblock, ratelimit
- New demo frontend page
- Updated config, server, analytics, stats, and existing frontend pages
2026-06-26 21:32:51 +09:00

173 lines
4.4 KiB
Go

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": "配额已更新"})
}