Files
rose_cat707 0d84b07f68
CI / docker (push) Successful in 1m58s
feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台:Agent 隧道、域名反代、IP 安全、访客验证与监控大盘。
Gitea Actions CI 多架构镜像;纯 Go SQLite、无 CGO Docker 构建。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 17:52:43 +08:00

205 lines
5.0 KiB
Go

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}
}