init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
132
webapp-back/holders/routers.go
Normal file
132
webapp-back/holders/routers.go
Normal file
@@ -0,0 +1,132 @@
|
||||
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(),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user