| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- package handler
- import (
- "encoding/csv"
- "fmt"
- "net/http"
- "strconv"
- "spider/internal/model"
- "spider/internal/store"
- "github.com/gin-gonic/gin"
- )
- // MerchantHandler handles merchant queries.
- type MerchantHandler struct {
- store *store.Store
- }
- // Stats returns aggregate statistics for merchants.
- func (h *MerchantHandler) Stats(c *gin.Context) {
- var rawTotal int64
- h.store.DB.Model(&model.MerchantRaw{}).Count(&rawTotal)
- var cleanTotal int64
- h.store.DB.Model(&model.MerchantClean{}).Count(&cleanTotal)
- // Count by status
- var statusRows []struct {
- Status string
- Cnt int64
- }
- h.store.DB.Model(&model.MerchantClean{}).
- Select("status, count(*) as cnt").
- Group("status").
- Scan(&statusRows)
- byStatus := map[string]int64{}
- for _, r := range statusRows {
- byStatus[r.Status] = r.Cnt
- }
- // Count by level
- var levelRows []struct {
- Level string
- Cnt int64
- }
- h.store.DB.Model(&model.MerchantClean{}).
- Select("level, count(*) as cnt").
- Group("level").
- Scan(&levelRows)
- byLevel := map[string]int64{}
- for _, r := range levelRows {
- byLevel[r.Level] = r.Cnt
- }
- // Count by source_type
- var sourceRows []struct {
- SourceType string
- Cnt int64
- }
- h.store.DB.Model(&model.MerchantRaw{}).
- Select("source_type, count(*) as cnt").
- Group("source_type").
- Scan(&sourceRows)
- bySource := map[string]int64{}
- for _, r := range sourceRows {
- bySource[r.SourceType] = r.Cnt
- }
- OK(c, gin.H{
- "raw_total": rawTotal,
- "clean_total": cleanTotal,
- "by_status": byStatus,
- "by_level": byLevel,
- "by_source": bySource,
- })
- }
- // ListRaw returns raw merchants with filters and pagination.
- func (h *MerchantHandler) ListRaw(c *gin.Context) {
- page, pageSize, offset := parsePage(c)
- query := h.store.DB.Model(&model.MerchantRaw{})
- if status := c.Query("status"); status != "" {
- query = query.Where("status = ?", status)
- }
- if sourceType := c.Query("source_type"); sourceType != "" {
- query = query.Where("source_type = ?", sourceType)
- }
- if search := c.Query("search"); search != "" {
- like := "%" + search + "%"
- query = query.Where("tg_username LIKE ? OR merchant_name LIKE ?", like, like)
- }
- var total int64
- query.Count(&total)
- var items []model.MerchantRaw
- if err := query.Order("created_at DESC").Limit(pageSize).Offset(offset).Find(&items).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- PageOK(c, items, total, page, pageSize)
- }
- // ListClean returns clean merchants with filters and pagination.
- func (h *MerchantHandler) ListClean(c *gin.Context) {
- page, pageSize, offset := parsePage(c)
- query := h.store.DB.Model(&model.MerchantClean{})
- if status := c.Query("status"); status != "" {
- query = query.Where("status = ?", status)
- }
- if level := c.Query("level"); level != "" {
- query = query.Where("level = ?", level)
- }
- if industry := c.Query("industry_tag"); industry != "" {
- query = query.Where("industry_tag = ?", industry)
- }
- if search := c.Query("search"); search != "" {
- like := "%" + search + "%"
- query = query.Where("tg_username LIKE ? OR merchant_name LIKE ?", like, like)
- }
- sortField := c.DefaultQuery("sort", "created_at")
- allowedSort := map[string]bool{
- "created_at": true,
- "updated_at": true,
- "source_count": true,
- "level": true,
- }
- if !allowedSort[sortField] {
- sortField = "created_at"
- }
- order := c.DefaultQuery("order", "desc")
- if order != "asc" && order != "desc" {
- order = "desc"
- }
- var total int64
- query.Count(&total)
- var items []model.MerchantClean
- if err := query.Order(sortField + " " + order).Limit(pageSize).Offset(offset).Find(&items).Error; err != nil {
- Fail(c, 500, err.Error())
- return
- }
- PageOK(c, items, total, page, pageSize)
- }
- // ExportCSV exports clean merchants as CSV.
- func (h *MerchantHandler) ExportCSV(c *gin.Context) {
- query := h.store.DB.Model(&model.MerchantClean{}).Where("status = ?", "valid")
- if level := c.Query("level"); level != "" {
- query = query.Where("level = ?", level)
- }
- var merchants []model.MerchantClean
- query.Order("level ASC, created_at DESC").Find(&merchants)
- c.Header("Content-Type", "text/csv; charset=utf-8")
- c.Header("Content-Disposition", "attachment; filename=merchants.csv")
- // Write BOM for Excel compatibility
- c.Writer.Write([]byte{0xEF, 0xBB, 0xBF})
- w := csv.NewWriter(c.Writer)
- w.Write([]string{"商户名", "TG用户名", "TG链接", "网站", "邮箱", "电话", "行业", "等级", "来源数"})
- for _, m := range merchants {
- w.Write([]string{
- m.MerchantName,
- m.TgUsername,
- m.TgLink,
- m.Website,
- m.Email,
- m.Phone,
- m.IndustryTag,
- m.Level,
- fmt.Sprintf("%d", m.SourceCount),
- })
- }
- w.Flush()
- }
- // GetByID fetches a merchant by ID.
- func (h *MerchantHandler) GetByID(c *gin.Context) {
- id, err := strconv.ParseUint(c.Param("id"), 10, 64)
- if err != nil {
- Fail(c, http.StatusBadRequest, "invalid id")
- return
- }
- var clean model.MerchantClean
- if err := h.store.DB.First(&clean, id).Error; err == nil {
- OK(c, gin.H{"source": "clean", "data": clean})
- return
- }
- var raw model.MerchantRaw
- if err := h.store.DB.First(&raw, id).Error; err == nil {
- OK(c, gin.H{"source": "raw", "data": raw})
- return
- }
- Fail(c, 404, "merchant not found")
- }
|