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

发布时间 - 2026-02-02 00:00:00    点击率:

本文详解如何在基于 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,坚守参数化查询原则。

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


# word  # html  # 前端  # go  # 处理器  # 编码  # app  # ai  # 路由  # 表单提交  # sql  # echo  # 字符串  # table  # 数据库  # 重构  # 关键词  # 分页  # 表单  # 的是  # 中统  # 两种  # 可选  # 而非  # 只做  # 到第 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 如何在万网ECS上快速搭建专属网站?  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  jQuery validate插件功能与用法详解  如何在IIS7上新建站点并设置安全权限?  Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】  Laravel如何升级到最新的版本_Laravel版本升级流程与兼容性处理  Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】  详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  如何用美橙互联一键搭建多站合一网站?  Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】  🚀拖拽式CMS建站能否实现高效与个性化并存?  如何用ChatGPT准备面试 模拟面试问答与职场话术练习教程  详解MySQL数据库的安装与密码配置  打开php文件提示内存不足_怎么调整php内存限制【解决方案】  Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制  jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】  Laravel如何构建RESTful API_Laravel标准化API接口开发指南  Android 常见的图片加载框架详细介绍  利用python获取某年中每个月的第一天和最后一天  Laravel怎么写单元测试_PHPUnit在Laravel项目中的基础测试入门  php结合redis实现高并发下的抢购、秒杀功能的实例  Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册  如何确认建站备案号应放置的具体位置?  Laravel如何生成和使用数据填充?(Seeder和Factory示例)  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  个人网站制作流程图片大全,个人网站如何注销?  Laravel如何处理跨站请求伪造(CSRF)保护_Laravel表单安全机制与令牌校验  如何制作公司的网站链接,公司想做一个网站,一般需要花多少钱?  Win11怎么关闭透明效果_Windows11辅助功能视觉效果设置  网站建设要注意的标准 促进网站用户好感度!  Swift中switch语句区间和元组模式匹配  谷歌Google入口永久地址_Google搜索引擎官网首页永久入口  Swift开发中switch语句值绑定模式  zabbix利用python脚本发送报警邮件的方法  在Oracle关闭情况下如何修改spfile的参数  UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】  百度浏览器ai对话怎么关 百度浏览器ai聊天窗口隐藏  JavaScript如何实现音频处理_Web Audio API如何工作?  专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?  JavaScript如何实现错误处理_try...catch如何捕获异常?  Laravel如何使用Service Provider服务提供者_Laravel依赖注入与容器绑定【深度】  JavaScript实现Fly Bird小游戏  JS碰撞运动实现方法详解  香港服务器租用每月最低只需15元?  如何快速生成橙子建站落地页链接?  Laravel distinct去重查询_Laravel Eloquent去重方法  ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集  Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】  如何在Windows服务器上快速搭建网站?