Golang中API搜索端点过滤的实现与问题求助

Golang中API搜索端点过滤的实现与问题求助 你好,我一直在开发一个资产管理系统,使用 Go 语言服务器作为 API 后端,后端再与 MariaDB SQL 数据库通信。资产的搜索/筛选是此类系统最重要的功能之一,而我在如何正确实现这个搜索端点上遇到了困难。

由于各种原因,数据库布局关系的核心大致如下:

           |--> vendors
  |--> products --> product_types
assets
  |--> locations --> addresses --> customers

采用这种布局意味着,在搜索位于特定地址、客户或具有特定产品类型的资产时,我必须连接很多表。

我面临的主要问题是,我们需要以多种不同的方式进行搜索。有时我们需要在所有连接的表(上述所有表或更多)的几乎所有列上进行全文搜索。下一次我们需要搜索位于给定位置的所有资产。之后是按产品类型和用户名搜索,等等。

到目前为止,我基本上每次都写一个新的 SQL 查询,但这不可维护,使用起来也不方便,而且如果用户希望以特定方式筛选,灵活性也不够。

我能想到的避免这种混乱的唯一方法是编写一个动态 SQL 查询生成器。这是我从未想过要说出或认为自己能在没有漏洞和错误的情况下实现的东西。在互联网上,唯一看起来有点像解决方案的是 GraphQL,但这里没有人喜欢它的语法,而且实现它似乎比动态生成 SQL 更麻烦。

非常感谢任何建议。


更多关于Golang中API搜索端点过滤的实现与问题求助的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang中API搜索端点过滤的实现与问题求助的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go中实现动态API搜索端点,推荐使用基于结构体标签的SQL构建器。以下是具体实现方案:

package main

import (
    "fmt"
    "strings"
)

// 搜索条件结构体
type AssetFilter struct {
    VendorName    string `db:"v.name" op:"like"`
    ProductType   string `db:"pt.type_name" op:"="`
    CustomerEmail string `db:"c.email" op:"like"`
    LocationCity  string `db:"a.city" op:"="`
    Limit         int    `db:"-" op:"-"`
    Offset        int    `db:"-" op:"-"`
}

// 查询构建器
type QueryBuilder struct {
    baseQuery string
    filters   []FilterCondition
    limit     int
    offset    int
}

type FilterCondition struct {
    Field     string
    Value     interface{}
    Operation string
}

func BuildSearchQuery(filter AssetFilter) (string, []interface{}) {
    baseQuery := `
        SELECT assets.* 
        FROM assets
        LEFT JOIN products p ON assets.product_id = p.id
        LEFT JOIN vendors v ON p.vendor_id = v.id
        LEFT JOIN product_types pt ON p.type_id = pt.id
        LEFT JOIN locations l ON assets.location_id = l.id
        LEFT JOIN addresses a ON l.address_id = a.id
        LEFT JOIN customers c ON a.customer_id = c.id
        WHERE 1=1
    `
    
    var conditions []string
    var args []interface{}
    argIndex := 1
    
    // 动态添加条件
    if filter.VendorName != "" {
        conditions = append(conditions, fmt.Sprintf("v.name LIKE $%d", argIndex))
        args = append(args, "%"+filter.VendorName+"%")
        argIndex++
    }
    
    if filter.ProductType != "" {
        conditions = append(conditions, fmt.Sprintf("pt.type_name = $%d", argIndex))
        args = append(args, filter.ProductType)
        argIndex++
    }
    
    if filter.CustomerEmail != "" {
        conditions = append(conditions, fmt.Sprintf("c.email LIKE $%d", argIndex))
        args = append(args, "%"+filter.CustomerEmail+"%")
        argIndex++
    }
    
    if filter.LocationCity != "" {
        conditions = append(conditions, fmt.Sprintf("a.city = $%d", argIndex))
        args = append(args, filter.LocationCity)
        argIndex++
    }
    
    // 组合查询
    if len(conditions) > 0 {
        baseQuery += " AND " + strings.Join(conditions, " AND ")
    }
    
    // 添加分页
    if filter.Limit > 0 {
        baseQuery += fmt.Sprintf(" LIMIT $%d", argIndex)
        args = append(args, filter.Limit)
        argIndex++
        
        if filter.Offset > 0 {
            baseQuery += fmt.Sprintf(" OFFSET $%d", argIndex)
            args = append(args, filter.Offset)
        }
    }
    
    return baseQuery, args
}

// 使用反射的通用版本
func BuildDynamicQuery(baseQuery string, filter interface{}) (string, []interface{}) {
    // 这里可以使用反射读取结构体标签
    // 实际实现需要根据db和op标签动态构建WHERE条件
    return baseQuery, nil
}

// 处理函数示例
func SearchAssetsHandler(filter AssetFilter) {
    query, args := BuildSearchQuery(filter)
    fmt.Printf("Query: %s\n", query)
    fmt.Printf("Args: %v\n", args)
    
    // 执行查询
    // db.Query(query, args...)
}

func main() {
    filter := AssetFilter{
        VendorName:    "Dell",
        ProductType:   "Server",
        CustomerEmail: "company.com",
        LocationCity:  "New York",
        Limit:         50,
        Offset:        0,
    }
    
    SearchAssetsHandler(filter)
}

对于全文搜索,可以单独处理:

func BuildFullTextSearch(searchTerm string, columns []string) string {
    var conditions []string
    for _, col := range columns {
        conditions = append(conditions, fmt.Sprintf("%s LIKE '%%%s%%'", col, searchTerm))
    }
    return "(" + strings.Join(conditions, " OR ") + ")"
}

// 使用sqlx库的命名查询简化参数绑定
import "github.com/jmoiron/sqlx"

func SearchWithSqlx(filter AssetFilter) error {
    query := `
        SELECT assets.* 
        FROM assets
        LEFT JOIN products p ON assets.product_id = p.id
        LEFT JOIN vendors v ON p.vendor_id = v.id
        WHERE 1=1
        {{if .VendorName}} AND v.name LIKE :vendor {{end}}
        {{if .ProductType}} AND p.type = :product_type {{end}}
    `
    
    // 使用sqlx.Named扩展
    query, args, _ := sqlx.Named(query, filter)
    query = sqlx.Rebind(sqlx.DOLLAR, query)
    
    // db.Query(query, args...)
    return nil
}

对于复杂查询,考虑使用Squirrel或goqu等查询构建器:

import sq "github.com/Masterminds/squirrel"

func BuildWithSquirrel(filter AssetFilter) sq.SelectBuilder {
    query := sq.Select("assets.*").
        From("assets").
        LeftJoin("products p ON assets.product_id = p.id").
        LeftJoin("vendors v ON p.vendor_id = v.id").
        Where(sq.Eq{"assets.active": true})
    
    if filter.VendorName != "" {
        query = query.Where(sq.Like{"v.name": "%" + filter.VendorName + "%"})
    }
    
    if filter.ProductType != "" {
        query = query.Where(sq.Eq{"p.type": filter.ProductType})
    }
    
    if filter.Limit > 0 {
        query = query.Limit(uint64(filter.Limit))
    }
    
    sql, args, _ := query.ToSql()
    fmt.Printf("SQL: %s\nArgs: %v\n", sql, args)
    
    return query
}

这个方案避免了为每个搜索场景编写独立查询,通过结构体定义搜索参数,动态构建SQL语句,同时保持类型安全和SQL注入防护。

回到顶部