- 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
173 lines
4.4 KiB
Go
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": "配额已更新"})
|
|
}
|