编写插件 #

一、插件结构 #

1.1 基本结构 #

javascript
const myPlugin = {
    name: 'my-plugin',
    version: '1.0.0',
    register: async function (server, options) {
        
    }
};

module.exports = myPlugin;

1.2 完整结构 #

javascript
const myPlugin = {
    name: 'my-plugin',
    version: '1.0.0',
    once: true,
    dependencies: ['other-plugin'],
    requirements: {
        hapi: '>=20.0.0',
        node: '>=14.0.0'
    },
    register: async function (server, options) {
        
    }
};

module.exports = myPlugin;

二、添加路由 #

2.1 单个路由 #

javascript
const routesPlugin = {
    name: 'custom-routes',
    register: async (server, options) => {
        server.route({
            method: 'GET',
            path: '/hello',
            handler: (request, h) => {
                return 'Hello from plugin!';
            }
        });
    }
};

2.2 多个路由 #

javascript
const apiPlugin = {
    name: 'api-routes',
    register: async (server, options) => {
        server.route([
            {
                method: 'GET',
                path: '/users',
                handler: (request, h) => {
                    return { users: [] };
                }
            },
            {
                method: 'POST',
                path: '/users',
                handler: (request, h) => {
                    return { message: 'Created' };
                }
            }
        ]);
    }
};

2.3 带验证的路由 #

javascript
const Joi = require('joi');

const userPlugin = {
    name: 'users',
    register: async (server, options) => {
        server.route({
            method: 'POST',
            path: '/users',
            options: {
                validate: {
                    payload: Joi.object({
                        name: Joi.string().required(),
                        email: Joi.string().email().required()
                    })
                }
            },
            handler: (request, h) => {
                return request.payload;
            }
        });
    }
};

三、添加扩展点 #

3.1 请求日志 #

javascript
const loggingPlugin = {
    name: 'request-logger',
    register: async (server, options) => {
        server.ext('onRequest', (request, h) => {
            console.log(`[${new Date().toISOString()}] ${request.method.toUpperCase()} ${request.path}`);
            return h.continue;
        });
    }
};

3.2 响应处理 #

javascript
const responsePlugin = {
    name: 'response-wrapper',
    register: async (server, options) => {
        server.ext('onPreResponse', (request, h) => {
            const response = request.response;
            
            if (!response.isBoom && !response.source) {
                response.source = {
                    success: true,
                    data: response.source
                };
            }
            
            return h.continue;
        });
    }
};

3.3 认证检查 #

javascript
const authCheckPlugin = {
    name: 'auth-check',
    register: async (server, options) => {
        server.ext('onPostAuth', (request, h) => {
            if (request.route.settings.auth !== false) {
                if (!request.auth.isAuthenticated) {
                    throw Boom.unauthorized('Please login');
                }
            }
            return h.continue;
        });
    }
};

四、添加方法 #

4.1 服务器方法 #

javascript
const methodsPlugin = {
    name: 'methods',
    register: async (server, options) => {
        server.method('formatDate', (date) => {
            return new Date(date).toISOString();
        });
        
        server.method('generateId', () => {
            return Date.now().toString(36);
        });
    }
};

4.2 带缓存的方法 #

javascript
const cachePlugin = {
    name: 'cached-methods',
    register: async (server, options) => {
        server.method('getUser', async (id) => {
            return await User.findById(id);
        }, {
            cache: {
                expiresIn: 60 * 1000,
                generateTimeout: 100
            }
        });
    }
};

4.3 方法对象 #

javascript
const utilsPlugin = {
    name: 'utils',
    register: async (server, options) => {
        server.method({
            name: 'utils.formatDate',
            method: (date) => new Date(date).toISOString()
        });
        
        server.method({
            name: 'utils.generateId',
            method: () => Date.now().toString(36)
        });
    }
};

五、添加装饰 #

5.1 服务器装饰 #

javascript
const decoratorsPlugin = {
    name: 'server-decorators',
    register: async (server, options) => {
        server.decorate('server', 'success', function (data) {
            return {
                success: true,
                data: data
            };
        });
        
        server.decorate('server', 'error', function (message) {
            return {
                success: false,
                error: message
            };
        });
    }
};

5.2 请求装饰 #

javascript
const requestDecoratorsPlugin = {
    name: 'request-decorators',
    register: async (server, options) => {
        server.decorate('request', 'getClientIP', function () {
            return this.info.remoteAddress;
        });
        
        server.decorate('request', 'getUser', function () {
            return this.auth.credentials;
        });
    }
};

5.3 响应装饰 #

javascript
const responseDecoratorsPlugin = {
    name: 'response-decorators',
    register: async (server, options) => {
        server.decorate('response', 'setCustomHeader', function (value) {
            return this.header('X-Custom', value);
        });
    }
};

六、认证策略 #

6.1 自定义认证 #

javascript
const customAuthPlugin = {
    name: 'custom-auth',
    register: async (server, options) => {
        server.auth.scheme('custom', (server, options) => {
            return {
                authenticate: async (request, h) => {
                    const token = request.headers.authorization;
                    
                    if (!token) {
                        throw Boom.unauthorized('Missing token');
                    }
                    
                    try {
                        const decoded = verifyToken(token, options.secret);
                        return h.authenticated({ credentials: decoded });
                    } catch (error) {
                        throw Boom.unauthorized('Invalid token');
                    }
                }
            };
        });
        
        server.auth.strategy('default', 'custom', { secret: options.secret });
    }
};

6.2 JWT认证 #

javascript
const jwt = require('jsonwebtoken');

const jwtAuthPlugin = {
    name: 'jwt-auth',
    register: async (server, options) => {
        server.auth.scheme('jwt', (server, schemeOptions) => {
            return {
                authenticate: async (request, h) => {
                    const authHeader = request.headers.authorization;
                    
                    if (!authHeader || !authHeader.startsWith('Bearer ')) {
                        throw Boom.unauthorized('Missing or invalid token');
                    }
                    
                    const token = authHeader.substring(7);
                    
                    try {
                        const decoded = jwt.verify(token, schemeOptions.secret);
                        return h.authenticated({ credentials: decoded });
                    } catch (error) {
                        throw Boom.unauthorized('Invalid token');
                    }
                }
            };
        });
        
        server.auth.strategy('jwt', 'jwt', { secret: options.secret });
        server.auth.default('jwt');
    }
};

七、状态管理 #

7.1 使用server.app #

javascript
const statePlugin = {
    name: 'state',
    register: async (server, options) => {
        server.app.config = options;
        server.app.startTime = Date.now();
        server.app.connections = new Map();
    }
};

7.2 使用server.plugins #

javascript
const dataPlugin = {
    name: 'data',
    register: async (server, options) => {
        server.plugins.data = {
            users: new Map(),
            sessions: new Map()
        };
    }
};

八、事件处理 #

8.1 服务器事件 #

javascript
const eventsPlugin = {
    name: 'events',
    register: async (server, options) => {
        server.events.on('start', () => {
            console.log('Server started');
        });
        
        server.events.on('stop', () => {
            console.log('Server stopped');
        });
        
        server.events.on('response', (request) => {
            console.log(`Request completed: ${request.path}`);
        });
    }
};

8.2 自定义事件 #

javascript
const customEventsPlugin = {
    name: 'custom-events',
    register: async (server, options) => {
        server.event('userCreated');
        server.event('userDeleted');
        
        server.events.on('userCreated', (user) => {
            console.log('User created:', user.id);
        });
    }
};

九、完整插件示例 #

9.1 数据库插件 #

javascript
const { Pool } = require('pg');

const databasePlugin = {
    name: 'database',
    version: '1.0.0',
    register: async (server, options) => {
        const pool = new Pool({
            host: options.host || 'localhost',
            port: options.port || 5432,
            database: options.database,
            user: options.user,
            password: options.password
        });
        
        server.decorate('server', 'db', pool);
        
        server.method('db.query', async (sql, params) => {
            const result = await pool.query(sql, params);
            return result.rows;
        });
        
        server.events.on('stop', () => {
            pool.end();
        });
    }
};

module.exports = databasePlugin;

9.2 缓存插件 #

javascript
const Redis = require('ioredis');

const cachePlugin = {
    name: 'cache',
    version: '1.0.0',
    register: async (server, options) => {
        const redis = new Redis({
            host: options.host || 'localhost',
            port: options.port || 6379
        });
        
        server.decorate('server', 'cache', redis);
        
        server.method('cache.get', async (key) => {
            const data = await redis.get(key);
            return data ? JSON.parse(data) : null;
        });
        
        server.method('cache.set', async (key, value, ttl = 3600) => {
            await redis.setex(key, ttl, JSON.stringify(value));
        });
        
        server.events.on('stop', () => {
            redis.quit();
        });
    }
};

module.exports = cachePlugin;

9.3 日志插件 #

javascript
const fs = require('fs');
const path = require('path');

const loggerPlugin = {
    name: 'logger',
    version: '1.0.0',
    register: async (server, options) => {
        const logFile = fs.createWriteStream(
            path.join(options.logDir || 'logs', 'access.log'),
            { flags: 'a' }
        );
        
        server.ext('onRequest', (request, h) => {
            request.startTime = Date.now();
            return h.continue;
        });
        
        server.events.on('response', (request) => {
            const log = {
                timestamp: new Date().toISOString(),
                method: request.method,
                path: request.path,
                query: request.query,
                status: request.response.statusCode,
                duration: Date.now() - request.startTime,
                ip: request.info.remoteAddress
            };
            
            logFile.write(JSON.stringify(log) + '\n');
        });
        
        server.events.on('stop', () => {
            logFile.end();
        });
    }
};

module.exports = loggerPlugin;

十、插件测试 #

10.1 基本测试 #

javascript
const Hapi = require('@hapi/hapi');
const myPlugin = require('./my-plugin');

describe('My Plugin', () => {
    let server;
    
    beforeEach(async () => {
        server = Hapi.server({ port: 3000 });
        await server.register({
            plugin: myPlugin,
            options: { setting: 'value' }
        });
    });
    
    test('should register successfully', () => {
        expect(server.registrations['my-plugin']).toBeDefined();
    });
    
    test('should add routes', async () => {
        const response = await server.inject({
            method: 'GET',
            path: '/hello'
        });
        expect(response.statusCode).toBe(200);
    });
});

十一、总结 #

编写插件要点:

功能 方法
添加路由 server.route()
添加扩展点 server.ext()
添加方法 server.method()
添加装饰 server.decorate()
添加认证 server.auth.scheme()

下一步,让我们学习常用插件!

最后更新:2026-03-28