
本文详解如何在 Go Echo Web 框架中,为现有分页产品列表页(`/product/:page`)无缝集成搜索框功能,通过 URL 查询参数传递关键词,并安全地实现 `LIKE` 模糊查询,避免 SQL 注入且保持分页逻辑完整。
要在基于 Echo 的分页产品页中添加搜索功能,核心在于将搜索条件从路径参数(/product/:page)解耦为可选的查询参数(如 ?q=keyword),并改造 SQL 查询逻辑,使其支持动态 WHERE 条件与安全的 LIKE 模糊匹配。以下是完整实现步骤:
✅ 1. 修改路由:支持可选搜索参数
保持原有分页路径,但允许附加查询参数(无需修改路由定义):
e.GET("/product/:page", handlers.Product) // ✅ 原路由不变,Echo 自动解析 ?q=xxx用户访问示例:
/product/1?q=golang 或 /product/2?q=api
✅ 2. 更新 Handler:解析搜索关键词并构建动态查询
在 handlers.Product 中,优先从 c.QueryParam("q") 获取搜索词,并在服务端拼接通配符(⚠️切勿在 SQL 中直接写 %!):
// 获取搜索关键词(默认为空字符串)
searchQuery := strings.TrimSpace(c.QueryParam("q"))
// 构建 LIKE 匹配值(安全:由 Go 拼接,非 SQL 拼接)
var prefixPattern, usagePattern string
if searchQuery != "" {
prefixPattern = "%" + searchQuery + "%"
usagePattern = "%" + searchQuery + "%"
} else {
prefixPattern = "%" // 匹配所有(或设为 NULL 逻辑,见下文优化建议)
usagePattern = "%"
}
// 计算分页偏移量(逻辑不变)
pageNumber := c.Param("page")
page, err := strconv.Atoi(pageNumber)
if err != nil || page < 1 {
page = 1
}
pageSize := 10
offset := (page - 1) * pageSize
// 执行带搜索条件的分页查询(使用参数化占位符 $3, $4)
rows, err := database.WrapQuery(
dbconnections.DBPool,
ctx,
"GetFromProductPaginatedByOffset",
pageSize, offset,
prefixPattern, // ← 绑定到 SQL 的 $3
usagePattern, // ← 绑定到 SQL 的 $4
)
if err != nil {
loggerWithTrace.Error().Err(err).Caller().Msg("paginated query failed")
return c.Render(http.StatusInternalServerError, "error", nil)
}
defer rows.Close()✅ 3. 修正 SQL 查询语句(关键!)
原 SQL 存在严重安全隐患(字符串拼接)且语法错误('%s' 是字面量,非参数占位符)。必须改用参数化查询:
-- ✅ 正确:PostgreSQL 参数化 LIKE 查询($3 和 $4 由 Go 传入已含 %) SELECT * FROM product WHERE (prefix ILIKE $3 OR usage ILIKE $4) LIMIT $1 OFFSET $2; -- ? 提示:使用 ILIKE(PostgreSQL)或 LIKE(MySQL/SQLite)实现大小写不敏感匹配
⚠️ 重要安全原则: 绝对禁止在 SQL 字符串中用 fmt.Sprintf 拼接用户输入; 通配符 % 必须由 Go 层("%" + q + "%")生成,再作为独立参数传入数据库驱动; 数据库驱动会自动转义参数值,彻底防止 SQL 注入。
✅ 4. 更新总数统计 SQL(保持分页准确性)
搜索时总数需同步过滤,否则 TotalPages 将错误:
-- ✅ 对应的总数查询(同样使用 $3, $4 参数) SELECT COUNT(*) FROM product WHERE (prefix ILIKE $3 OR usage ILIKE $4);
并在 Go 中调用:
var totalItems int
err := database.WrapQueryRow(
dbconnections.DBPool,
ctx,
"GetTotalSizeFromProduct",
prefixPattern, usagePattern, // ← 传入相同 pattern
).Scan(&totalItems)✅ 5. 前端搜索表单(HTML 模板示例)
在 product.html 模板中添加搜索框,保持当前页码并提交到同一路由:
? 提示:为提升体验,可在模板中将 {{.SearchQuery}} 从 c.QueryParam("q") 传入 templateDataMap。
✅ 总结与最佳实践
- 路由无侵入:复用 /product/:page,通过 ?q= 扩展功能;
- 安全第一:通配符由 Go 拼接,SQL 使用参数化查询($1, $2...);
- 分页一致性:搜索时 LIMIT/OFFSET 与 COUNT(*) 必须使用完全相同的 WHERE 条件;
- 用户体验:搜索框应保留当前页码,支持清空;
- 进阶建议:对高频搜索词添加数据库索引(如 CREATE INDEX idx_product_prefix_usage ON product USING gin(prefix gin_trgm_ops, usage gin_trgm_ops);)。
通过以上改造,你的 Echo 分页页面即可获得健壮、安全且用户友好的搜索能力。










