如何在 Go Echo 框架中为分页页面添加搜索框功能

4次阅读

如何在 Go Echo 框架中为分页页面添加搜索框功能

本文详解如何在基于 echo 的分页产品列表页面中集成搜索功能,包括安全拼接 like 查询参数、改造 sql 语句、更新路由处理器逻辑,并避免 sql 注入风险。

在当前的分页实现中,你已通过路径参数 :page 实现了基础分页(如 /product/1),但缺少对用户搜索行为的支持——例如按 prefix 或 usage 模糊查找。直接在 SQL 中使用 ‘%s%’ 占位符并传入原始关键词会导致语法错误或注入漏洞,因此必须重构查询逻辑。

✅ 正确做法:将搜索参数作为独立查询参数传递

首先,修改路由,不再仅依赖路径参数,而是支持带查询参数的 GET 请求(更符合 REST 语义且便于前端表单提交):

// 支持两种访问方式: // - /product?page=1&search=abc (推荐) // - /product/1?search=xyz (兼容旧路由,可选) e.GET("/product", handlers.Product)                 // 主搜索+分页入口 e.GET("/product/:page", handlers.Product)          // 保留原有路径式分页(需解析 query)

然后,在 handlers.Product 中统一处理搜索与分页逻辑:

func Product(c echo.Context) error {     ctx := c.Request().Context()      // 1. 解析分页参数(优先从 query,fallback 到 path)     pageStr := c.QueryParam("page")     if pageStr == "" {         pageStr = c.Param("page") // 兼容 /product/1 形式     }     page, err := strconv.Atoi(pageStr)     if err != nil || page < 1 {         page = 1     }      pageSize := 10     offset := (page - 1) * pageSize      // 2. 解析搜索关键词(可为空)     search := strings.TrimSpace(c.QueryParam("search"))      // 3. 构建安全的 LIKE 模式:前后加 %,由 Go 层完成,而非 SQL 拼接     var searchPattern string     if search != "" {         searchPattern = "%" + search + "%"     }      // 4. 执行带搜索条件的分页查询(注意:SQL 中使用 $3、$4 占位符)     rows, err := database.WrapQuery(         dbconnections.DBPool,         ctx,         "GetFromProductPaginatedByOffset",         pageSize,         offset,         searchPattern, // ← 传入已加 % 的 pattern         searchPattern, // ← 同样用于 usage 字段     )     if err != nil {         loggerWithTrace.Error().Err(err).Caller().Msg("paginated query failed")         return echo.NewHTTPError(http.StatusInternalServerError, "failed to fetch products")     }     defer rows.Close()      var results []models.Product     for rows.Next() {         var p models.Product         if err := rows.Scan(&p.Prefix, &p.Suffix, &p.Usage); err != nil {             loggerWithTrace.Error().Err(err).Caller().Msg("scan product row failed")             continue         }         results = append(results, p)     }      // 5. 获取总数量(同样需应用搜索条件)     var totalItems int     err = database.WrapQueryRow(         dbconnections.DBPool,         ctx,         "GetTotalSizeFromProduct",         searchPattern,         searchPattern,     ).Scan(&totalItems)     if err != nil {         loggerWithTrace.Error().Err(err).Caller().Msg("count query failed")     }      totalPages := int(math.Ceil(float64(totalItems) / float64(pageSize)))     nextPage := page + 1     if nextPage > totalPages {         nextPage = totalPages     }     prevPage := page - 1     if prevPage < 1 {         prevPage = 1     }      // 6. 渲染模板,透传 search 值以保持搜索状态     templateDataMap := map[string]interface{}{         "Product":     results,         "Page":        page,         "PageSize":    pageSize,         "TotalItems":  totalItems,         "TotalPages":  totalPages,         "NextPage":    nextPage,         "PrevPage":    prevPage,         "Search":      search, // ← 关键:回填到 input value 中     }      return c.Render(http.StatusOK, "product", templateDataMap) }

? SQL 查询语句更新(关键!)

确保你的 SQL 查询模板(如 Q_GET_PAGINATION_FROM_PRODUCT)使用参数化占位符,禁止字符串拼接:

-- ✅ 正确:使用 $3 和 $4 接收 go 传入的 '%keyword%' 字符串 SELECT * FROM PRODUCT  WHERE (prefix LIKE $3 OR usage LIKE $4)  LIMIT $1 OFFSET $2;  -- ✅ 对应总数查询: SELECT COUNT(*) FROM PRODUCT  WHERE (prefix LIKE $1 OR usage LIKE $2);

⚠️ 注意:不要写成 LIKE '%' || $1 || '%' 或 LIKE '%'+$1+'%' —— 这不仅降低可读性,还可能因数据库方言差异出错;更严重的是,若前端传入恶意内容(如 %'; DROP table...),虽参数化本身防注入,但手动拼接 % 仍易出错。始终由 Go 层构建 pattern,SQL 只做纯匹配。

?️ 前端模板(html 示例)

在 product.html 中添加搜索表单,并保持当前搜索词与分页链接同步:

{{ if gt .PrevPage 0 }} ← Prev {{ end }} Page {{ .Page }} of {{ .TotalPages }} {{ if lt .NextPage .TotalPages }} Next → {{ end }}

✅ 总结要点

  • 搜索与分页应解耦:用 ?page=2&search=abc 替代硬编码路径;
  • LIKE 模式(%xxx%)必须在 Go 层生成,SQL 中仅使用标准 $n 占位符;
  • 所有数据库查询(含总数统计)都需一致应用搜索条件;
  • 模板中回显 .Search 值,保障用户体验连续性;
  • 避免任何字符串格式化拼接 SQL,坚守参数化查询原则。

这样改造后,你的产品页就拥有了健壮、安全且用户友好的搜索+分页能力。

text=ZqhQzanResources