编写 Torrent Service 操作方法文件 - Egg.js 实战

2026-02-15 46 浏览 0 评论

在基于 Egg.js 构建的后端应用中, Service 层 主要负责处理业务逻辑,是 Controller 与数据层之间的重要桥梁。 本文将以一个 Torrent(种子)管理模块 为例,详细讲解如何编写一个结构清晰、功能完整的 torrent service 操作文件。

该 Service 主要围绕以下功能展开:

  • 种子列表查询(分页 / 条件 / 排序)
  • 根据 hash 获取单个种子信息
  • 新增、更新、删除种子
  • 判断种子是否已存在
  • 随机获取种子数据(常用于推荐或发现页)

一、Service 文件结构说明

在 Egg.js 中,Service 通常位于 app/service 目录下,每一个文件对应一个业务模块:

// app/service/torrent.js

Service 通过继承 egg.Service ,可以方便地访问:

  • this.app (应用实例)
  • this.ctx (请求上下文)
  • 插件能力(如 egg-mysql

二、Torrent Service 完整实现

下面是完整的 torrent Service 实现代码, 保持原样,不做结构性改动

// app/service/torrent.js

'use strict';

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

module.exports = class extends Service {
  // 获取列表
  async get(params) {
    const defaultParams = {
      page: 1,
      rows: 20,
      count: false,
      orderby: 'id',
      order: 'desc',
      hash: '',
      id: '',
      ids: [],
      single: false,
    };
    params = Object.assign(defaultParams, params);
    params.page = params.page || 1;
    params.rows = params.rows || 10;

    let sql = 'select t1.* from torrent t1';
    if (params.count) sql = 'select count(*) as count from torrent t1';

    sql += ' where 1';
    if (params.id) sql += ` and t1.id = ${params.id}`;
    if (params.ids.length) sql += ` and t1.id in (${params.ids.join()})`;
    if (params.hash) sql += ` and t1.hash = '${params.hash}'`;

    if (!params.count) {
      sql += ` order by t1.${params.orderby} ${params.order}`;
      sql += ` limit ${(params.page - 1) * params.rows}, ${params.rows}`;
    }

    const val = await this.app.mysql.query(sql);
    if (!params.count) {
      for (let i = 0; i < val.length; i++) {
        val[i].files = JSON.parse(val[i].files);
        if (!params.single) {
          val[i].files.splice(3, 100);
        }
      }
    }
    return params.count ? val[0].count : val;
  }

  // 根据 hash 获取种子信息
  async getByHash(hash) {
    const val = await this.get({ hash, single: true });
    return val.length ? val[0] : {};
  }

  // 插入种子信息,用于手动上传种子
  async insert(params) {
    params.add_date = new Date();
    const val = await this.app.mysql.insert('torrent', params);
    return val.insertId;
  }

  // 更新种子信息
  async update(params) {
    const val = await this.app.mysql.update('torrent', params);
    return val.affectedRows;
  }

  // 删除种子
  async delete(id) {
    const val = await this.app.mysql.query('delete from torrent where id = ' + id);
    return val.affectedRows;
  }

  // 检查是否由对应 hash 的种子
  async hashExists(hash) {
    const sql = `select * from torrent where hash = '${hash}'`;
    const val = await this.app.mysql.query(sql);
    return val.length ? val[0] : null;
  }

  // 随机种子信息
  async random(number = 10) {
    const sql = `SELECT * FROM torrent WHERE id >= (
    (SELECT MAX(id) FROM torrent) - 
    (SELECT MIN(id) FROM torrent)
    ) * RAND() + (SELECT MIN(id) FROM torrent) LIMIT ${number}`;
    return this.app.mysql.query(sql);
  }
};

三、核心方法设计思路解析

1️⃣ get :通用列表查询方法

这是整个 Service 中 最核心、复用性最高 的方法,支持:

  • 分页( page / rows
  • 排序( orderby / order
  • 条件过滤( id / ids / hash
  • 统计数量( count
  • 单条 / 列表返回( single

关键设计点:

  • 默认参数合并 使用 Object.assign ,避免调用方漏传参数导致逻辑异常。
  • count 查询复用同一套条件 只切换 select 字段,不重复写 SQL。
  • 文件列表裁剪 非详情页场景下,只保留前几个文件,减少接口数据体积,提高性能。

这种写法在后台管理系统和内容平台中非常常见。


2️⃣ getByHash :业务语义封装

async getByHash(hash) {
  const val = await this.get({ hash, single: true });
  return val.length ? val[0] : {};
}

这里并没有重新写 SQL,而是 复用 get 方法 ,体现了:

  • 逻辑集中
  • 代码可维护性高
  • 业务语义清晰(通过方法名表达用途)

3️⃣ insert / update :数据写入操作

  • insert :用于手动上传种子
  • update :用于修改种子信息(如分类、描述、状态等)

insert 中自动补充 add_date ,可以避免 Controller 层关心数据库字段细节。


4️⃣ delete :直接删除记录

async delete(id) {
  const val = await this.app.mysql.query('delete from torrent where id = ' + id);
  return val.affectedRows;
}

适用于后台管理场景。 如果是对数据安全要求更高的系统,也可以改成 软删除 (增加 deleted 字段)。


5️⃣ hashExists :防重复校验

Torrent 的 hash 天然唯一 ,这是一个非常典型的业务校验方法,常用于:

  • 上传前校验
  • 自动抓取时去重
  • 同步任务防止重复写入

6️⃣ random :随机推荐数据

该方法通过 ID 区间 + RAND() 的方式实现随机查询,性能优于直接 ORDER BY RAND() ,适合数据量较大的表。

常见使用场景包括:

  • 首页推荐
  • 你可能感兴趣
  • 发现页内容填充

四、实践建议与注意事项

  • SQL 注入风险 当前代码使用字符串拼接,生产环境建议使用:
  • this.app.mysql.escape
  • 或参数化查询
  • 📦 字段结构设计 files 使用 JSON 字符串存储,适合文件列表这类结构化但不常更新的数据。
  • 🔁 Service 层只做业务,不做参数校验 参数校验更适合放在 Controller 或 Middleware 中。

五、总结

本文通过一个完整的 torrent service 示例,展示了在 Egg.js 中如何:

  • 设计通用查询方法
  • 封装清晰的业务语义
  • 合理组织 CRUD 操作
  • 平衡灵活性与可维护性

这种 Service 写法不仅适用于 Torrent 系统,也非常适合 内容管理、资源库、媒体系统 等场景,具备很强的可复用价值。

如果你后续想把这套逻辑拆成 微服务 、接入 消息队列 或做 自动化抓取 ,这个 Service 结构也能很好地支撑演进。


发布评论

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

评论列表 0

暂无评论