编写搜索 Controller 用于搜索种子信息

2026-02-16 64 浏览 0 评论

在一个资源类站点或下载站中, 搜索功能几乎是核心入口 。无论是种子、文章还是商品,用户访问站点的第一诉求往往是「快速找到想要的内容」。本文将结合 Egg.js 框架,介绍一个 搜索 Controller 的完整实现思路 ,包括:

  • 查询参数的标准化处理
  • 调用搜索 Service 获取匹配结果
  • 二次查询业务数据(种子信息)
  • 搜索关键词高亮与 XSS 安全处理
  • 分页导航与页面渲染

下面我们直接从代码入手。


搜索 Controller 代码实现

该 Controller 位于 app/controller/search.js ,核心职责是 协调搜索逻辑与页面渲染 ,而不是直接处理复杂业务。

/* eslint-disable prefer-const */
'use strict';

const Controller = require('egg').Controller;

module.exports = class extends Controller {
  async index() {
    const query = this.ctx.query;
    query.page = Number(query.page || 1) || 1;
    query.rows = Number(query.rows || 10) || 10;
    query.word = this.ctx.service.search.filterWord(query.word) || '';
    if (Number(query.type) === 0) query.type = '';

    let result = { matches: [], words: [], total: 0, time: 0 };
    if (query.word) result = await this.ctx.service.search.get(query);

    let torrents = [];
    if (result.matches.length) {
      const ids = [];
      for (let i = 0; i < result.matches.length; i++) ids.push(result.matches[i].id);
      torrents = await this.ctx.service.torrent.get({ ids, rows: query.rows });
      // 关键词替换
      for (let i = 0; i < torrents.length; i++) {
        // 1. 先替换内容里面的 尖括号
        torrents[i].name = torrents[i].name.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        for (let f = 0; f < torrents[i].files.length; f++) {
          torrents[i].files[f].name = torrents[i].files[f].name.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        }
        // 2. 再替换关键词
        for (let j = 0; j < result.words.length; j++) {
          const word = result.words[j];
          const reg = new RegExp(word, 'gi');
          torrents[i].name = torrents[i].name.replace(reg, '<em>' + word + '</em>');
          for (let f = 0; f < torrents[i].files.length; f++) {
            torrents[i].files[f].name = torrents[i].files[f].name.replace(reg, '<em>' + word + '</em>');
          }
        }
      }
    }

    const maxPage = Math.ceil(result.total / query.rows);
    const pageArray = this.ctx.helper.pageNavi(query.page, 5, maxPage);
    const pagePre = '/search';

    let title = '站内搜索';
    if (query.page) title = '第' + query.page + '页 - ' + title;
    if (query.word) title = query.word + ' 的搜索结果 - ' + title;

    await this.ctx.helper.renderView('search.ejs', {
      torrents,
      count: result.total,
      page: query.page,
      pageArray,
      pagePre,
      maxPage,
      title,
      type: this.app.config.site.type,
    });
  }
};

查询参数的预处理与安全过滤

query.page = Number(query.page || 1) || 1;
query.rows = Number(query.rows || 10) || 10;

这里对分页参数进行了 显式数值转换 ,可以避免字符串参与计算导致的问题,同时也防止非法参数直接影响逻辑。

query.word = this.ctx.service.search.filterWord(query.word) || '';

搜索词在进入核心搜索逻辑之前,先通过 filterWord 进行过滤,这一步通常用于:

  • 去除多余空格
  • 过滤非法字符
  • 防止搜索引擎异常或注入问题

这是一个 非常推荐放在 Service 层的通用处理逻辑


搜索与业务数据的解耦设计

result = await this.ctx.service.search.get(query);

搜索 Service 返回的数据结构并不是完整的种子数据,而是类似:

  • matches :匹配到的 ID 列表
  • words :实际参与搜索的关键词
  • total :匹配总数
  • time :搜索耗时

这种设计非常合理,因为:

  • 搜索引擎(如 Sphinx / ES / Meilisearch)只负责“查 ID”
  • 业务数据仍然以数据库为准

随后通过 ID 再去查询真正的种子信息:

torrents = await this.ctx.service.torrent.get({ ids, rows: query.rows });

这是一种 典型的搜索系统分层设计 ,也方便后期替换搜索引擎实现。


XSS 防护与关键词高亮

搜索结果页最容易出现安全问题,尤其是 关键词高亮功能

torrents[i].name = torrents[i].name.replace(/</g, '&lt;').replace(/>/g, '&gt;');

在插入 <em> 标签之前, 先转义尖括号 ,可以有效防止恶意 HTML 或脚本注入。

随后再进行关键词替换:

const reg = new RegExp(word, 'gi');
torrents[i].name = torrents[i].name.replace(reg, '<em>' + word + '</em>');

这种处理顺序非常关键:

先转义内容 → 再插入安全的高亮标签

同样的逻辑也应用到了种子内部的文件名上,保证展示层的一致性与安全性。


分页导航与页面信息构建

const maxPage = Math.ceil(result.total / query.rows);
const pageArray = this.ctx.helper.pageNavi(query.page, 5, maxPage);

分页逻辑被抽离到 helper 中,Controller 只负责传参与结果使用,保持了代码的清晰与可维护性。

页面标题也根据当前状态动态生成:

if (query.word) title = query.word + ' 的搜索结果 - ' + title;

这不仅对用户友好,也有利于 SEO。


页面渲染与 Controller 的职责边界

最后通过:

await this.ctx.helper.renderView('search.ejs', { ... });

将所有数据交给视图层,Controller 本身不关心页面结构,只关心 数据是否完整、是否安全

这正是 Egg.js 推荐的设计方式:

  • Controller:流程调度
  • Service:业务逻辑
  • Helper:通用工具
  • View:页面展示

总结

这个搜索 Controller 虽然代码不算复杂,但涵盖了一个真实项目中搜索功能的多个关键点:

  • 搜索参数规范化
  • 搜索引擎与业务数据解耦
  • XSS 安全处理
  • 关键词高亮
  • 分页与 SEO 友好设计

在实际项目中,你可以在此基础上继续扩展,例如:

  • 搜索历史与热门关键词
  • 多字段权重搜索
  • 搜索结果缓存
  • 搜索耗时统计与监控

如果你正在使用 Egg.js 构建中大型应用,这种 Controller + Service 的搜索实现方式,值得作为一个稳定、可扩展的参考方案。


发布评论

发布评论前请先 登录
取消
0 评论
点赞
收藏

评论列表 0

暂无评论