package store import ( "fmt" "strconv" "gorm.io/gorm" ) type PageParams struct { Page int PageSize int Q string } func NormalizePage(p PageParams) (offset, limit int) { if p.Page < 1 { p.Page = 1 } if p.PageSize < 1 { p.PageSize = 20 } if p.PageSize > 200 { p.PageSize = 200 } return (p.Page - 1) * p.PageSize, p.PageSize } func tunnelOwnerScope(db *gorm.DB, clientID, ownerID uint, isAdmin bool) *gorm.DB { q := db.Model(&Tunnel{}) if clientID > 0 { return q.Where("client_id = ?", clientID) } if !isAdmin { var ids []uint db.Model(&Client{}).Where("owner_user_id = ?", ownerID).Pluck("id", &ids) if len(ids) == 0 { return q.Where("1 = 0") } return q.Where("client_id IN ?", ids) } return q } func hostOwnerScope(db *gorm.DB, clientID, ownerID uint, isAdmin bool) *gorm.DB { q := db.Model(&Host{}) if clientID > 0 { return q.Where("client_id = ?", clientID) } if !isAdmin { var ids []uint db.Model(&Client{}).Where("owner_user_id = ?", ownerID).Pluck("id", &ids) if len(ids) == 0 { return q.Where("1 = 0") } return q.Where("client_id IN ?", ids) } return q } func applyTunnelSearch(q *gorm.DB, keyword string) *gorm.DB { if keyword == "" { return q } like := "%" + keyword + "%" portLike := "%" + keyword + "%" return q.Where( "name LIKE ? OR target_addr LIKE ? OR CAST(listen_port AS TEXT) LIKE ?", like, like, portLike, ) } func applyHostSearch(q *gorm.DB, keyword string) *gorm.DB { if keyword == "" { return q } like := "%" + keyword + "%" return q.Where("host LIKE ? OR target_addr LIKE ? OR location LIKE ?", like, like, like) } func (s *Store) ListTunnelsPaged(clientID, ownerID uint, isAdmin bool, p PageParams) ([]Tunnel, int64, error) { var tunnels []Tunnel var total int64 q := tunnelOwnerScope(s.db, clientID, ownerID, isAdmin) q = applyTunnelSearch(q, p.Q) if err := q.Count(&total).Error; err != nil { return nil, 0, err } offset, limit := NormalizePage(p) err := q.Order("id asc").Offset(offset).Limit(limit).Find(&tunnels).Error return tunnels, total, err } func (s *Store) HostRouteConflict(host, location string, excludeHostID uint) error { if location == "" { location = "/" } inUse, err := s.HostRouteInUse(host, location, excludeHostID) if err != nil { return err } if inUse { return fmt.Errorf("域名与路径前缀已被穿透域名占用") } return nil } func (s *Store) ListHostsPaged(clientID, ownerID uint, isAdmin bool, p PageParams) ([]Host, int64, error) { var hosts []Host var total int64 q := hostOwnerScope(s.db, clientID, ownerID, isAdmin) q = applyHostSearch(q, p.Q) if err := q.Count(&total).Error; err != nil { return nil, 0, err } offset, limit := NormalizePage(p) err := q.Order("id asc").Offset(offset).Limit(limit).Find(&hosts).Error return hosts, total, err } func (s *Store) TunnelPortInUse(port int, excludeID uint) (bool, error) { if port <= 0 { return false, fmt.Errorf("invalid port") } q := s.db.Model(&Tunnel{}).Where("listen_port = ?", port) if excludeID > 0 { q = q.Where("id <> ?", excludeID) } var count int64 if err := q.Count(&count).Error; err != nil { return false, err } return count > 0, nil } func (s *Store) HostRouteInUse(host, location string, excludeID uint) (bool, error) { if host == "" { return false, fmt.Errorf("host required") } if location == "" { location = "/" } q := s.db.Model(&Host{}).Where("host = ? AND location = ?", host, location) if excludeID > 0 { q = q.Where("id <> ?", excludeID) } var count int64 if err := q.Count(&count).Error; err != nil { return false, err } return count > 0, nil } func (s *Store) ListTunnelsByFlow(limit, offset int) ([]Tunnel, int64, error) { var tunnels []Tunnel var total int64 q := s.db.Model(&Tunnel{}) if err := q.Count(&total).Error; err != nil { return nil, 0, err } if limit <= 0 { limit = 10 } err := q.Order("(inlet_flow + export_flow) desc").Offset(offset).Limit(limit).Find(&tunnels).Error return tunnels, total, err } func (s *Store) ListHostsByFlow(limit, offset int) ([]Host, int64, error) { var hosts []Host var total int64 q := s.db.Model(&Host{}) if err := q.Count(&total).Error; err != nil { return nil, 0, err } if limit <= 0 { limit = 10 } err := q.Order("(inlet_flow + export_flow) desc").Offset(offset).Limit(limit).Find(&hosts).Error return hosts, total, err } func (s *Store) ListAllRequestIPs(limit, offset int, qstr string) ([]RequestIP, int64, error) { var items []RequestIP var total int64 q := s.db.Model(&RequestIP{}) if qstr != "" { like := "%" + qstr + "%" q = q.Where("ip LIKE ? OR direct_ip LIKE ?", like, like) } if err := q.Count(&total).Error; err != nil { return nil, 0, err } if limit <= 0 { limit = 50 } err := q.Order("hit_count desc").Offset(offset).Limit(limit).Find(&items).Error return items, total, err } func ParsePageQuery(pageStr, pageSizeStr, q string) PageParams { page, _ := strconv.Atoi(pageStr) pageSize, _ := strconv.Atoi(pageSizeStr) return PageParams{Page: page, PageSize: pageSize, Q: q} }