Files
assetx/webapp-back/holders/routers.go

133 lines
3.8 KiB
Go
Raw Permalink Normal View History

package holders
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gothinkster/golang-gin-realworld-example-app/common"
"github.com/gothinkster/golang-gin-realworld-example-app/models"
)
// RegisterRoutes registers holders API routes
func RegisterRoutes(router *gin.RouterGroup) {
router.GET("/stats", GetStats)
router.GET("/:tokenType", GetHoldersByType)
router.POST("/update", UpdateHolders)
}
// GetStats returns aggregated statistics for all token types
func GetStats(c *gin.Context) {
db := common.GetDB()
log.Printf("📊 [API] 收到获取统计数据请求 - IP: %s", c.ClientIP())
var stats []models.HolderStats
err := db.Raw(`
SELECT
token_type,
COUNT(DISTINCT holder_address) as holder_count
FROM holder_snapshots
WHERE balance != '0' AND balance != '' AND LENGTH(balance) > 0
GROUP BY token_type
`).Scan(&stats).Error
if err != nil {
log.Printf("❌ [API] 查询统计数据失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": "Failed to fetch stats",
})
return
}
log.Printf("✅ [API] 成功返回统计数据 - %d 个token类型", len(stats))
for _, stat := range stats {
log.Printf(" - %s: %d 个持有者", stat.TokenType, stat.HolderCount)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": stats,
})
}
// GetHoldersByType returns holders for a specific token type
func GetHoldersByType(c *gin.Context) {
tokenType := c.Param("tokenType")
db := common.GetDB()
// 记录请求日志
log.Printf("🔍 [API] 收到获取持有者请求 - Token类型: %s, IP: %s", tokenType, c.ClientIP())
var holders []models.HolderSnapshot
err := db.Where("token_type = ? AND balance != ? AND balance != '' AND LENGTH(balance) > 0", tokenType, "0").
Order("LENGTH(balance) DESC, balance DESC").
Find(&holders).Error
if err != nil {
log.Printf("❌ [API] 查询持有者失败 - Token类型: %s, 错误: %v", tokenType, err)
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"error": "Failed to fetch holders",
})
return
}
log.Printf("✅ [API] 成功返回 %d 个持有者 - Token类型: %s", len(holders), tokenType)
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": holders,
})
}
// Global scanner instance
var globalScanner *Scanner
// StartAllScanners 为所有支持的链启动 holder 扫描器(后台常驻)
func StartAllScanners() {
supportedChains := []int64{97, 421614}
for _, chainID := range supportedChains {
config, err := LoadConfigFromDB(chainID)
if err != nil {
log.Printf("[HolderScanner] Chain %d 配置加载失败: %v", chainID, err)
continue
}
if len(config.YTVaults) == 0 && config.YTLPAddress == "" {
log.Printf("[HolderScanner] Chain %d 无活跃资产,跳过", chainID)
continue
}
go func(cfg Config) {
log.Printf("[HolderScanner] Chain %d 启动YTVaults=%d ytLP=%s",
cfg.ChainID, len(cfg.YTVaults), cfg.YTLPAddress)
if err := Start(cfg); err != nil {
log.Printf("[HolderScanner] Chain %d 异常退出: %v", cfg.ChainID, err)
}
}(config)
}
}
// UpdateHolders triggers blockchain data update (admin only)
func UpdateHolders(c *gin.Context) {
if globalScanner == nil {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Scanner is running in background. It will process new blocks on next tick.",
"timestamp": time.Now().Unix(),
})
return
}
go func() {
if err := globalScanner.incrementalScan(c.Request.Context()); err != nil {
log.Printf("[HolderScanner] Manual update error: %v", err)
}
}()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Manual scan triggered.",
"timestamp": time.Now().Unix(),
})
}