Skip to content

Feathers.js 服务详解 - 从内存到数据库的完整实现

发布时间:2024-05-20
作者:一介布衣
标签:Feathers.js, Services, 数据库适配器, 服务架构

前言

上一篇文章我们通过待办事项项目体验了 Feathers.js 的强大功能,今天咱们来深入学习 Services(服务)的各种实现方式。说实话,Service 是 Feathers.js 的核心,理解了 Service 就理解了 Feathers.js 的精髓。

我记得刚开始学 Feathers.js 的时候,总觉得 Service 就是简单的 CRUD 操作。后来深入使用才发现,Feathers.js 的 Service 设计非常巧妙,它不仅统一了不同数据源的操作接口,还提供了强大的扩展能力。无论数据存储在内存、文件、数据库,还是来自第三方 API,都可以用同样的方式操作。

今天我就带大家从最简单的内存服务开始,一步步学习各种 Service 的实现方式。

Service 的本质

什么是 Service?

Service 在 Feathers.js 中是一个抽象概念,它代表一个数据资源。每个 Service 都实现了标准的 CRUD 接口:

javascript
// Service 的标准接口
interface Service {
  find(params)     // 查询多条记录
  get(id, params)  // 获取单条记录
  create(data, params)  // 创建记录
  update(id, data, params)  // 完全更新记录
  patch(id, data, params)   // 部分更新记录
  remove(id, params)        // 删除记录
}

Service 的统一性

这种统一的接口设计带来了巨大的好处:

javascript
// 无论底层是什么存储,客户端代码都一样
const users = app.service('users');

// 这些操作在任何 Service 中都是一样的
await users.find();
await users.get(1);
await users.create({ name: 'John' });
await users.patch(1, { name: 'Jane' });
await users.remove(1);

内存服务(Memory Service)

1. 基础内存服务

最简单的 Service 就是内存服务,数据存储在内存中:

javascript
// src/services/memory-demo/memory-demo.class.js
class MemoryService {
  constructor() {
    this.data = [];
    this.currentId = 0;
  }

  async find(params) {
    const { query = {} } = params;
    let result = [...this.data];

    // 简单过滤
    Object.keys(query).forEach(key => {
      if (key.startsWith('$')) return; // 跳过特殊查询参数
      
      result = result.filter(item => {
        if (typeof query[key] === 'object') {
          // 处理复杂查询(如范围查询)
          return this.matchComplexQuery(item[key], query[key]);
        }
        return item[key] === query[key];
      });
    });

    // 排序
    if (query.$sort) {
      result = this.sortData(result, query.$sort);
    }

    // 分页
    const total = result.length;
    const limit = parseInt(query.$limit) || total;
    const skip = parseInt(query.$skip) || 0;
    
    result = result.slice(skip, skip + limit);

    return {
      total,
      limit,
      skip,
      data: result
    };
  }

  async get(id, params) {
    const item = this.data.find(d => d.id === parseInt(id));
    if (!item) {
      throw new Error(`记录 ${id} 不存在`);
    }
    return item;
  }

  async create(data, params) {
    // 支持批量创建
    if (Array.isArray(data)) {
      return Promise.all(data.map(item => this.create(item, params)));
    }

    const item = {
      id: ++this.currentId,
      ...data,
      createdAt: new Date(),
      updatedAt: new Date()
    };

    this.data.push(item);
    return item;
  }

  async update(id, data, params) {
    const index = this.data.findIndex(d => d.id === parseInt(id));
    if (index === -1) {
      throw new Error(`记录 ${id} 不存在`);
    }

    // 完全替换
    const item = {
      id: parseInt(id),
      ...data,
      updatedAt: new Date()
    };

    this.data[index] = item;
    return item;
  }

  async patch(id, data, params) {
    const item = await this.get(id, params);
    
    // 部分更新
    Object.assign(item, data, {
      updatedAt: new Date()
    });

    return item;
  }

  async remove(id, params) {
    const index = this.data.findIndex(d => d.id === parseInt(id));
    if (index === -1) {
      throw new Error(`记录 ${id} 不存在`);
    }

    const item = this.data[index];
    this.data.splice(index, 1);
    return item;
  }

  // 辅助方法
  matchComplexQuery(value, query) {
    if (query.$gt !== undefined) return value > query.$gt;
    if (query.$gte !== undefined) return value >= query.$gte;
    if (query.$lt !== undefined) return value < query.$lt;
    if (query.$lte !== undefined) return value <= query.$lte;
    if (query.$ne !== undefined) return value !== query.$ne;
    if (query.$in !== undefined) return query.$in.includes(value);
    if (query.$nin !== undefined) return !query.$nin.includes(value);
    return true;
  }

  sortData(data, sortQuery) {
    return data.sort((a, b) => {
      for (const [field, direction] of Object.entries(sortQuery)) {
        const aVal = a[field];
        const bVal = b[field];
        
        if (aVal < bVal) return direction === 1 ? -1 : 1;
        if (aVal > bVal) return direction === 1 ? 1 : -1;
      }
      return 0;
    });
  }
}

module.exports = MemoryService;

2. 使用官方内存适配器

Feathers.js 提供了官方的内存适配器:

javascript
// 安装内存适配器
npm install @feathersjs/memory

// 使用内存适配器
const { MemoryService } = require('@feathersjs/memory');

// 注册服务
app.use('todos', new MemoryService({
  multi: true,  // 允许批量操作
  id: 'id',     // 主键字段
  startId: 1,   // 起始ID
  store: {},    // 自定义存储对象
  paginate: {
    default: 10,
    max: 50
  }
}));

3. 高级内存服务

javascript
// 带缓存和持久化的内存服务
class AdvancedMemoryService extends MemoryService {
  constructor(options = {}) {
    super();
    this.options = options;
    this.cacheTimeout = options.cacheTimeout || 60000; // 1分钟
    this.persistFile = options.persistFile;
    
    // 从文件加载数据
    this.loadFromFile();
    
    // 定期保存到文件
    if (this.persistFile) {
      setInterval(() => this.saveToFile(), 30000); // 30秒保存一次
    }
  }

  async create(data, params) {
    const result = await super.create(data, params);
    
    // 触发保存
    if (this.persistFile) {
      this.saveToFile();
    }
    
    return result;
  }

  async patch(id, data, params) {
    const result = await super.patch(id, data, params);
    
    // 触发保存
    if (this.persistFile) {
      this.saveToFile();
    }
    
    return result;
  }

  async remove(id, params) {
    const result = await super.remove(id, params);
    
    // 触发保存
    if (this.persistFile) {
      this.saveToFile();
    }
    
    return result;
  }

  loadFromFile() {
    if (!this.persistFile) return;
    
    try {
      const fs = require('fs');
      if (fs.existsSync(this.persistFile)) {
        const fileData = fs.readFileSync(this.persistFile, 'utf8');
        const parsed = JSON.parse(fileData);
        this.data = parsed.data || [];
        this.currentId = parsed.currentId || 0;
      }
    } catch (error) {
      console.error('加载数据文件失败:', error);
    }
  }

  saveToFile() {
    if (!this.persistFile) return;
    
    try {
      const fs = require('fs');
      const dataToSave = {
        data: this.data,
        currentId: this.currentId,
        savedAt: new Date()
      };
      fs.writeFileSync(this.persistFile, JSON.stringify(dataToSave, null, 2));
    } catch (error) {
      console.error('保存数据文件失败:', error);
    }
  }

  // 数据统计
  getStats() {
    return {
      totalRecords: this.data.length,
      memoryUsage: JSON.stringify(this.data).length,
      lastModified: Math.max(...this.data.map(d => new Date(d.updatedAt).getTime()))
    };
  }

  // 批量导入
  async bulkImport(records) {
    const results = [];
    for (const record of records) {
      try {
        const created = await this.create(record);
        results.push({ success: true, data: created });
      } catch (error) {
        results.push({ success: false, error: error.message, data: record });
      }
    }
    return results;
  }

  // 清空数据
  async clear() {
    this.data = [];
    this.currentId = 0;
    if (this.persistFile) {
      this.saveToFile();
    }
    return { message: '数据已清空' };
  }
}

文件系统服务

1. 基于文件的服务

javascript
// src/services/file-service/file-service.class.js
const fs = require('fs').promises;
const path = require('path');

class FileService {
  constructor(options = {}) {
    this.dataDir = options.dataDir || './data';
    this.extension = options.extension || '.json';
    this.ensureDataDir();
  }

  async ensureDataDir() {
    try {
      await fs.mkdir(this.dataDir, { recursive: true });
    } catch (error) {
      console.error('创建数据目录失败:', error);
    }
  }

  getFilePath(id) {
    return path.join(this.dataDir, `${id}${this.extension}`);
  }

  async find(params) {
    const { query = {} } = params;
    
    try {
      const files = await fs.readdir(this.dataDir);
      const jsonFiles = files.filter(f => f.endsWith(this.extension));
      
      const data = [];
      for (const file of jsonFiles) {
        try {
          const filePath = path.join(this.dataDir, file);
          const content = await fs.readFile(filePath, 'utf8');
          const record = JSON.parse(content);
          
          // 简单过滤
          if (this.matchesQuery(record, query)) {
            data.push(record);
          }
        } catch (error) {
          console.error(`读取文件 ${file} 失败:`, error);
        }
      }

      // 排序
      if (query.$sort) {
        data.sort((a, b) => {
          for (const [field, direction] of Object.entries(query.$sort)) {
            if (a[field] < b[field]) return direction === 1 ? -1 : 1;
            if (a[field] > b[field]) return direction === 1 ? 1 : -1;
          }
          return 0;
        });
      }

      // 分页
      const total = data.length;
      const limit = parseInt(query.$limit) || total;
      const skip = parseInt(query.$skip) || 0;
      
      return {
        total,
        limit,
        skip,
        data: data.slice(skip, skip + limit)
      };
    } catch (error) {
      throw new Error(`查询失败: ${error.message}`);
    }
  }

  async get(id, params) {
    const filePath = this.getFilePath(id);
    
    try {
      const content = await fs.readFile(filePath, 'utf8');
      return JSON.parse(content);
    } catch (error) {
      if (error.code === 'ENOENT') {
        throw new Error(`记录 ${id} 不存在`);
      }
      throw new Error(`读取记录失败: ${error.message}`);
    }
  }

  async create(data, params) {
    const id = data.id || this.generateId();
    const record = {
      ...data,
      id,
      createdAt: new Date(),
      updatedAt: new Date()
    };

    const filePath = this.getFilePath(id);
    
    try {
      await fs.writeFile(filePath, JSON.stringify(record, null, 2));
      return record;
    } catch (error) {
      throw new Error(`创建记录失败: ${error.message}`);
    }
  }

  async update(id, data, params) {
    const record = {
      ...data,
      id,
      updatedAt: new Date()
    };

    const filePath = this.getFilePath(id);
    
    try {
      await fs.writeFile(filePath, JSON.stringify(record, null, 2));
      return record;
    } catch (error) {
      throw new Error(`更新记录失败: ${error.message}`);
    }
  }

  async patch(id, data, params) {
    const existing = await this.get(id, params);
    const updated = {
      ...existing,
      ...data,
      id,
      updatedAt: new Date()
    };

    const filePath = this.getFilePath(id);
    
    try {
      await fs.writeFile(filePath, JSON.stringify(updated, null, 2));
      return updated;
    } catch (error) {
      throw new Error(`更新记录失败: ${error.message}`);
    }
  }

  async remove(id, params) {
    const record = await this.get(id, params);
    const filePath = this.getFilePath(id);
    
    try {
      await fs.unlink(filePath);
      return record;
    } catch (error) {
      throw new Error(`删除记录失败: ${error.message}`);
    }
  }

  generateId() {
    return Date.now().toString() + Math.random().toString(36).substr(2, 9);
  }

  matchesQuery(record, query) {
    for (const [key, value] of Object.entries(query)) {
      if (key.startsWith('$')) continue;
      
      if (record[key] !== value) {
        return false;
      }
    }
    return true;
  }

  // 备份数据
  async backup(backupPath) {
    const data = await this.find({ query: {} });
    await fs.writeFile(backupPath, JSON.stringify(data, null, 2));
    return { message: `备份完成,共 ${data.total} 条记录` };
  }

  // 恢复数据
  async restore(backupPath) {
    try {
      const content = await fs.readFile(backupPath, 'utf8');
      const backup = JSON.parse(content);
      
      let restored = 0;
      for (const record of backup.data) {
        try {
          await this.create(record);
          restored++;
        } catch (error) {
          console.error(`恢复记录 ${record.id} 失败:`, error);
        }
      }
      
      return { message: `恢复完成,共恢复 ${restored} 条记录` };
    } catch (error) {
      throw new Error(`恢复失败: ${error.message}`);
    }
  }
}

module.exports = FileService;

数据库服务基础

1. 抽象数据库服务

javascript
// src/services/abstract-db/abstract-db.class.js
class AbstractDatabaseService {
  constructor(options = {}) {
    this.Model = options.Model;
    this.id = options.id || 'id';
    this.paginate = options.paginate;
  }

  async find(params) {
    throw new Error('find 方法必须被子类实现');
  }

  async get(id, params) {
    throw new Error('get 方法必须被子类实现');
  }

  async create(data, params) {
    throw new Error('create 方法必须被子类实现');
  }

  async update(id, data, params) {
    throw new Error('update 方法必须被子类实现');
  }

  async patch(id, data, params) {
    throw new Error('patch 方法必须被子类实现');
  }

  async remove(id, params) {
    throw new Error('remove 方法必须被子类实现');
  }

  // 通用的查询构建方法
  buildQuery(query) {
    const where = {};
    const options = {};

    Object.keys(query).forEach(key => {
      if (key.startsWith('$')) {
        // 处理特殊查询参数
        switch (key) {
          case '$limit':
            options.limit = parseInt(query[key]);
            break;
          case '$skip':
            options.offset = parseInt(query[key]);
            break;
          case '$sort':
            options.order = this.buildSort(query[key]);
            break;
          case '$select':
            options.attributes = query[key];
            break;
        }
      } else {
        // 处理普通字段查询
        where[key] = this.buildFieldQuery(query[key]);
      }
    });

    return { where, options };
  }

  buildFieldQuery(value) {
    if (typeof value === 'object' && value !== null) {
      // 处理复杂查询
      const conditions = {};
      
      Object.keys(value).forEach(operator => {
        switch (operator) {
          case '$gt':
            conditions[this.getOperator('gt')] = value[operator];
            break;
          case '$gte':
            conditions[this.getOperator('gte')] = value[operator];
            break;
          case '$lt':
            conditions[this.getOperator('lt')] = value[operator];
            break;
          case '$lte':
            conditions[this.getOperator('lte')] = value[operator];
            break;
          case '$ne':
            conditions[this.getOperator('ne')] = value[operator];
            break;
          case '$in':
            conditions[this.getOperator('in')] = value[operator];
            break;
          case '$nin':
            conditions[this.getOperator('nin')] = value[operator];
            break;
        }
      });
      
      return conditions;
    }
    
    return value;
  }

  buildSort(sortQuery) {
    // 子类实现具体的排序逻辑
    return sortQuery;
  }

  getOperator(op) {
    // 子类实现具体的操作符映射
    return op;
  }

  // 分页处理
  async paginate(query, countQuery) {
    const { where, options } = this.buildQuery(query);
    
    const [data, total] = await Promise.all([
      this.executeQuery(where, options),
      this.executeCount(countQuery || where)
    ]);

    return {
      total,
      limit: options.limit || total,
      skip: options.offset || 0,
      data
    };
  }

  async executeQuery(where, options) {
    throw new Error('executeQuery 方法必须被子类实现');
  }

  async executeCount(where) {
    throw new Error('executeCount 方法必须被子类实现');
  }

  // 事务支持
  async transaction(callback) {
    throw new Error('transaction 方法必须被子类实现');
  }

  // 批量操作
  async bulkCreate(data, params) {
    const results = [];
    for (const item of data) {
      try {
        const created = await this.create(item, params);
        results.push({ success: true, data: created });
      } catch (error) {
        results.push({ success: false, error: error.message, data: item });
      }
    }
    return results;
  }

  async bulkUpdate(updates, params) {
    const results = [];
    for (const { id, data } of updates) {
      try {
        const updated = await this.patch(id, data, params);
        results.push({ success: true, data: updated });
      } catch (error) {
        results.push({ success: false, error: error.message, id });
      }
    }
    return results;
  }

  async bulkRemove(ids, params) {
    const results = [];
    for (const id of ids) {
      try {
        const removed = await this.remove(id, params);
        results.push({ success: true, data: removed });
      } catch (error) {
        results.push({ success: false, error: error.message, id });
      }
    }
    return results;
  }
}

module.exports = AbstractDatabaseService;

自定义服务示例

1. 第三方 API 服务

javascript
// src/services/github-api/github-api.class.js
const axios = require('axios');

class GitHubApiService {
  constructor(options = {}) {
    this.token = options.token;
    this.baseURL = 'https://api.github.com';
    
    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': this.token ? `token ${this.token}` : undefined,
        'Accept': 'application/vnd.github.v3+json'
      }
    });
  }

  async find(params) {
    const { query = {} } = params;
    const { q, sort = 'updated', order = 'desc' } = query;
    
    if (!q) {
      throw new Error('搜索查询参数 q 是必需的');
    }

    try {
      const response = await this.client.get('/search/repositories', {
        params: {
          q,
          sort,
          order,
          per_page: query.$limit || 30,
          page: Math.floor((query.$skip || 0) / (query.$limit || 30)) + 1
        }
      });

      return {
        total: response.data.total_count,
        limit: query.$limit || 30,
        skip: query.$skip || 0,
        data: response.data.items.map(this.transformRepository)
      };
    } catch (error) {
      throw new Error(`GitHub API 请求失败: ${error.message}`);
    }
  }

  async get(id, params) {
    // id 格式: owner/repo
    try {
      const response = await this.client.get(`/repos/${id}`);
      return this.transformRepository(response.data);
    } catch (error) {
      if (error.response?.status === 404) {
        throw new Error(`仓库 ${id} 不存在`);
      }
      throw new Error(`获取仓库信息失败: ${error.message}`);
    }
  }

  async create(data, params) {
    // 创建仓库
    try {
      const response = await this.client.post('/user/repos', {
        name: data.name,
        description: data.description,
        private: data.private || false,
        auto_init: data.auto_init || true
      });
      
      return this.transformRepository(response.data);
    } catch (error) {
      throw new Error(`创建仓库失败: ${error.message}`);
    }
  }

  async patch(id, data, params) {
    // 更新仓库信息
    try {
      const response = await this.client.patch(`/repos/${id}`, {
        name: data.name,
        description: data.description,
        private: data.private
      });
      
      return this.transformRepository(response.data);
    } catch (error) {
      throw new Error(`更新仓库失败: ${error.message}`);
    }
  }

  async remove(id, params) {
    // 删除仓库
    try {
      const repo = await this.get(id, params);
      await this.client.delete(`/repos/${id}`);
      return repo;
    } catch (error) {
      throw new Error(`删除仓库失败: ${error.message}`);
    }
  }

  // 不支持完全更新
  async update(id, data, params) {
    throw new Error('GitHub API 不支持完全更新操作,请使用 patch');
  }

  transformRepository(repo) {
    return {
      id: repo.full_name,
      name: repo.name,
      fullName: repo.full_name,
      description: repo.description,
      url: repo.html_url,
      cloneUrl: repo.clone_url,
      language: repo.language,
      stars: repo.stargazers_count,
      forks: repo.forks_count,
      issues: repo.open_issues_count,
      private: repo.private,
      createdAt: repo.created_at,
      updatedAt: repo.updated_at,
      owner: {
        login: repo.owner.login,
        avatar: repo.owner.avatar_url,
        url: repo.owner.html_url
      }
    };
  }

  // 获取仓库的提交历史
  async getCommits(repoId, params = {}) {
    try {
      const response = await this.client.get(`/repos/${repoId}/commits`, {
        params: {
          per_page: params.limit || 30,
          page: params.page || 1,
          since: params.since,
          until: params.until
        }
      });

      return response.data.map(commit => ({
        sha: commit.sha,
        message: commit.commit.message,
        author: {
          name: commit.commit.author.name,
          email: commit.commit.author.email,
          date: commit.commit.author.date
        },
        url: commit.html_url
      }));
    } catch (error) {
      throw new Error(`获取提交历史失败: ${error.message}`);
    }
  }

  // 获取仓库的问题列表
  async getIssues(repoId, params = {}) {
    try {
      const response = await this.client.get(`/repos/${repoId}/issues`, {
        params: {
          state: params.state || 'open',
          per_page: params.limit || 30,
          page: params.page || 1
        }
      });

      return response.data.map(issue => ({
        id: issue.id,
        number: issue.number,
        title: issue.title,
        body: issue.body,
        state: issue.state,
        labels: issue.labels.map(label => label.name),
        createdAt: issue.created_at,
        updatedAt: issue.updated_at,
        url: issue.html_url
      }));
    } catch (error) {
      throw new Error(`获取问题列表失败: ${error.message}`);
    }
  }
}

module.exports = GitHubApiService;

2. 缓存服务

javascript
// src/services/cache/cache.class.js
const Redis = require('redis');

class CacheService {
  constructor(options = {}) {
    this.redis = Redis.createClient(options.redis || {});
    this.prefix = options.prefix || 'cache:';
    this.defaultTTL = options.defaultTTL || 3600; // 1小时
    
    this.redis.on('error', (err) => {
      console.error('Redis 连接错误:', err);
    });
  }

  async find(params) {
    const { query = {} } = params;
    const pattern = query.pattern || '*';
    const keys = await this.redis.keys(`${this.prefix}${pattern}`);
    
    const data = [];
    for (const key of keys) {
      const value = await this.redis.get(key);
      const ttl = await this.redis.ttl(key);
      
      data.push({
        id: key.replace(this.prefix, ''),
        key: key,
        value: this.parseValue(value),
        ttl: ttl,
        expiresAt: ttl > 0 ? new Date(Date.now() + ttl * 1000) : null
      });
    }

    return {
      total: data.length,
      limit: query.$limit || data.length,
      skip: query.$skip || 0,
      data: data.slice(query.$skip || 0, (query.$skip || 0) + (query.$limit || data.length))
    };
  }

  async get(id, params) {
    const key = `${this.prefix}${id}`;
    const value = await this.redis.get(key);
    
    if (value === null) {
      throw new Error(`缓存键 ${id} 不存在或已过期`);
    }

    const ttl = await this.redis.ttl(key);
    
    return {
      id,
      key,
      value: this.parseValue(value),
      ttl,
      expiresAt: ttl > 0 ? new Date(Date.now() + ttl * 1000) : null
    };
  }

  async create(data, params) {
    const { id, value, ttl = this.defaultTTL } = data;
    const key = `${this.prefix}${id}`;
    
    const serializedValue = this.serializeValue(value);
    
    if (ttl > 0) {
      await this.redis.setex(key, ttl, serializedValue);
    } else {
      await this.redis.set(key, serializedValue);
    }

    return {
      id,
      key,
      value,
      ttl,
      expiresAt: ttl > 0 ? new Date(Date.now() + ttl * 1000) : null,
      createdAt: new Date()
    };
  }

  async patch(id, data, params) {
    const existing = await this.get(id, params);
    const { value, ttl } = data;
    
    if (value !== undefined) {
      const key = `${this.prefix}${id}`;
      const serializedValue = this.serializeValue(value);
      
      if (ttl !== undefined) {
        if (ttl > 0) {
          await this.redis.setex(key, ttl, serializedValue);
        } else {
          await this.redis.set(key, serializedValue);
        }
      } else {
        // 保持原有的 TTL
        await this.redis.set(key, serializedValue);
        if (existing.ttl > 0) {
          await this.redis.expire(key, existing.ttl);
        }
      }
    } else if (ttl !== undefined) {
      // 只更新 TTL
      const key = `${this.prefix}${id}`;
      if (ttl > 0) {
        await this.redis.expire(key, ttl);
      } else {
        await this.redis.persist(key);
      }
    }

    return await this.get(id, params);
  }

  async remove(id, params) {
    const existing = await this.get(id, params);
    const key = `${this.prefix}${id}`;
    
    await this.redis.del(key);
    return existing;
  }

  // 不支持完全更新
  async update(id, data, params) {
    return await this.patch(id, data, params);
  }

  serializeValue(value) {
    if (typeof value === 'string') {
      return value;
    }
    return JSON.stringify(value);
  }

  parseValue(value) {
    try {
      return JSON.parse(value);
    } catch (error) {
      return value;
    }
  }

  // 批量设置
  async mset(data) {
    const pipeline = this.redis.pipeline();
    
    for (const { id, value, ttl = this.defaultTTL } of data) {
      const key = `${this.prefix}${id}`;
      const serializedValue = this.serializeValue(value);
      
      if (ttl > 0) {
        pipeline.setex(key, ttl, serializedValue);
      } else {
        pipeline.set(key, serializedValue);
      }
    }
    
    await pipeline.exec();
    return { message: `批量设置 ${data.length} 个缓存项` };
  }

  // 批量获取
  async mget(ids) {
    const keys = ids.map(id => `${this.prefix}${id}`);
    const values = await this.redis.mget(keys);
    
    return ids.map((id, index) => ({
      id,
      value: values[index] ? this.parseValue(values[index]) : null,
      exists: values[index] !== null
    }));
  }

  // 清空所有缓存
  async flush() {
    const keys = await this.redis.keys(`${this.prefix}*`);
    if (keys.length > 0) {
      await this.redis.del(keys);
    }
    return { message: `清空了 ${keys.length} 个缓存项` };
  }

  // 获取缓存统计
  async getStats() {
    const keys = await this.redis.keys(`${this.prefix}*`);
    const info = await this.redis.info('memory');
    
    return {
      totalKeys: keys.length,
      memoryUsage: this.parseRedisInfo(info),
      prefix: this.prefix
    };
  }

  parseRedisInfo(info) {
    const lines = info.split('\r\n');
    const memory = {};
    
    lines.forEach(line => {
      if (line.includes(':')) {
        const [key, value] = line.split(':');
        if (key.includes('memory')) {
          memory[key] = value;
        }
      }
    });
    
    return memory;
  }
}

module.exports = CacheService;

服务注册和配置

1. 服务注册

javascript
// src/services/index.js
const users = require('./users/users.service.js');
const memoryDemo = require('./memory-demo/memory-demo.service.js');
const fileService = require('./file-service/file-service.service.js');
const githubApi = require('./github-api/github-api.service.js');
const cache = require('./cache/cache.service.js');

module.exports = function (app) {
  // 注册所有服务
  app.configure(users);
  app.configure(memoryDemo);
  app.configure(fileService);
  app.configure(githubApi);
  app.configure(cache);
};

2. 服务配置示例

javascript
// src/services/memory-demo/memory-demo.service.js
const MemoryService = require('./memory-demo.class.js');
const hooks = require('./memory-demo.hooks.js');

module.exports = function (app) {
  const options = {
    paginate: app.get('paginate'),
    persistFile: './data/memory-demo.json'
  };

  // 注册服务
  app.use('/memory-demo', new MemoryService(options));

  // 获取服务实例
  const service = app.service('memory-demo');

  // 配置钩子
  service.hooks(hooks);

  // 添加自定义方法
  service.getStats = function() {
    return this.getStats();
  };

  service.bulkImport = function(data) {
    return this.bulkImport(data);
  };

  service.clear = function() {
    return this.clear();
  };
};

总结

通过这篇文章,我们深入学习了 Feathers.js 中各种 Service 的实现方式:

内存服务

  • 基础内存服务实现
  • 官方内存适配器使用
  • 高级内存服务(缓存、持久化)

文件系统服务

  • 基于文件的数据存储
  • 备份和恢复功能
  • 文件系统操作优化

抽象数据库服务

  • 通用数据库服务基类
  • 查询构建和分页处理
  • 事务和批量操作支持

自定义服务

  • 第三方 API 集成
  • 缓存服务实现
  • 复杂业务逻辑封装

Service 是 Feathers.js 的核心,掌握了各种 Service 的实现方式,你就能够:

  • 统一不同数据源的操作接口
  • 灵活扩展业务功能
  • 构建可维护的服务架构
  • 实现复杂的数据处理逻辑

下一篇文章,我们将深入学习 Feathers.js 的钩子系统,看看如何优雅地组织业务逻辑。


相关文章推荐:

有问题欢迎留言讨论,我会及时回复大家!