跳到主要内容

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

· 阅读需 16 分钟
一介布衣
全栈开发者 / 技术写作者

发布时间: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 接口:

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

Service 的统一性

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

// 无论底层是什么存储,客户端代码都一样
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 就是内存服务,数据存储在内存中:

// 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 提供了官方的内存适配器:

// 安装内存适配器
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. 高级内存服务

// 带缓存和持久化的内存服务
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. 基于文件的服务

// 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. 抽象数据库服务

// 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 服务

// 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. 缓存服务

// 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. 服务注册

// 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. 服务配置示例

// 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 的钩子系统,看看如何优雅地组织业务逻辑。


相关文章推荐:

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