@sourceloop / feature-toggle-service
概述
提供维护不同级别特性标志的功能的微服务。提供了对系统级、租户级和用户级的初始支持。
工作流程
该服务提供了CRUD api,用于将值插入到特定于特性切换的数据表中,这些数据表在决定是否允许用户使用某个特定特性时可以读取。 这个特性是feature-toggle包的扩展——@sourceloop/feature-toggle,它提供了一个方法级装饰器来检查特定的API是否可访问。
提供了系统级、租户级和用户级特性标志的初始实现。
安装
npm i @sourceloop/feature-toggle-service
使用
- 创建一个新的Loopback4应用程序(如果你还没有一个)lb4 testapp
- 安装服务- npm i @sourceloop/feature-toggle-service
- 设置 环境变量
- 运行 迁移。(这将在数据库中创建相应的表)
- 添加
FeatureToggleServiceComponent到您的Loopback4应用程序(在application.ts)。// import the FeatureToggleServiceComponent import {FeatureToggleServiceComponent} from '@sourceloop/feature-toggle-service'; // add controllers to your application (optional) this.bind(FeatureToggleBindings.Config).to({ bindControllers: true, useCustomSequence: false, }); // add Component for FeatureToggleService this.component(FeatureToggleServiceComponent); - 建立一个 Loopback4数据源 与
dataSourceName属性设置为FeatureToggleDbName。您可以看到一个示例数据源 在这里. - 启动应用程序
npm start
非对称令牌签名和验证
如果您正在使用非对称令牌签名和验证,则应该在服务中提供认证数据源,并在facade级别上提供认证数据源。auth数据库的数据源文件示例为:-
验证DB数据源:-
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
import {juggler} from '@loopback/repository';
import {AuthDbSourceName} from '@sourceloop/core';
const DEFAULT_MAX_CONNECTIONS = 25;
const DEFAULT_DB_IDLE_TIMEOUT_MILLIS = 60000;
const DEFAULT_DB_CONNECTION_TIMEOUT_MILLIS = 2000;
const config = {
name: 'auth',
connector: 'postgresql',
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
schema: process.env.DB_SCHEMA,
password: process.env.DB_PASSWORD,
database: process.env.AUTH_DB,
};
// Observe application's life cycle to disconnect the datasource when
// application is stopped. This allows the application to be shut down
// gracefully. The `stop()` method is inherited from `juggler.DataSource`.
// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html
@lifeCycleObserver('datasource')
export class AuthDataSource
extends juggler.DataSource
implements LifeCycleObserver
{
static dataSourceName = AuthDbSourceName;
static readonly defaultConfig = config;
constructor(
@inject('datasources.config.auth', {optional: true})
dsConfig: object = config,
) {
if (!!+(process.env.ENABLE_DB_CONNECTION_POOLING ?? 0)) {
const dbPool = {
max: +(process.env.DB_MAX_CONNECTIONS ?? DEFAULT_MAX_CONNECTIONS),
idleTimeoutMillis: +(
process.env.DB_IDLE_TIMEOUT_MILLIS ?? DEFAULT_DB_IDLE_TIMEOUT_MILLIS
),
connectionTimeoutMillis: +(
process.env.DB_CONNECTION_TIMEOUT_MILLIS ??
DEFAULT_DB_CONNECTION_TIMEOUT_MILLIS
),
};
dsConfig = {...dsConfig, ...dbPool};
}
super(dsConfig);
}
}
验证Cache Redis数据源:-
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
import {AnyObject, juggler} from '@loopback/repository';
import {readFileSync} from 'fs';
import {AuthCacheSourceName} from '@sourceloop/core';
const config = {
name: process.env.REDIS_NAME,
connector: 'kv-redis',
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD,
db: process.env.REDIS_DATABASE,
url: process.env.REDIS_URL,
tls:
+process.env.REDIS_TLS_ENABLED! && process.env.REDIS_TLS_CERT
? {
ca: readFileSync(process.env.REDIS_TLS_CERT),
}
: undefined,
sentinels:
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINELS
? JSON.parse(process.env.REDIS_SENTINELS)
: undefined,
sentinelPassword:
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINEL_PASSWORD
? process.env.REDIS_SENTINEL_PASSWORD
: undefined,
role:
+process.env.REDIS_HAS_SENTINELS! && process.env.REDIS_SENTINEL_ROLE
? process.env.REDIS_SENTINEL_ROLE
: undefined,
};
@lifeCycleObserver('datasource')
export class RedisDataSource
extends juggler.DataSource
implements LifeCycleObserver
{
static readonly dataSourceName = AuthCacheSourceName;
static readonly defaultConfig = config;
constructor(
@inject(`datasources.config.${process.env.REDIS_NAME}`, {optional: true})
dsConfig: AnyObject = config,
) {
if (
+process.env.REDIS_HAS_SENTINELS! &&
!!process.env.REDIS_SENTINEL_HOST &&
!!process.env.REDIS_SENTINEL_PORT
) {
dsConfig.sentinels = [
{
host: process.env.REDIS_SENTINEL_HOST,
port: +process.env.REDIS_SENTINEL_PORT,
},
];
}
super(dsConfig);
}
}
环境变量
| 名字 | 要求 | 默认值 | 描述 |
|---|---|---|---|
NODE_ENV | Y | 节点环境值,即 dev, test, prod | |
LOG_LEVEL | Y | 日志级别值,即 error, warn, info, verbose, debug | |
DB_HOST | Y | 数据库服务器的主机名。 | |
DB_PORT | Y | 数据库服务器的端口。 | |
DB_USER | Y | 数据库的用户。 | |
DB_PASSWORD | Y | 数据库用户的密码。 | |
DB_DATABASE | Y | 数据库服务器上要连接的数据库。 | |
DB_SCHEMA | Y | 用于数据源的数据库模式。在PostgreSQL中,这将是 public 除非为服务显式地制定了模式。 | |
JWT_SECRET | Y | JWT令牌的对称签名密钥。 | |
JWT_ISSUER | Y | JWT令牌的发行者。 |
建立 DataSource
下面是一个示例实现 DataSource 使用环境变量和PostgreSQL作为数据源实现。
import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
import {juggler} from '@loopback/repository';
import {FeatureToggleDbName} from '@sourceloop/feature-toggle-service';
const config = {
name: FeatureToggleDbName,
connector: 'postgresql',
url: '',
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
schema: process.env.DB_SCHEMA,
};
@lifeCycleObserver('datasource')
export class FeatureToggleDbDataSource
extends juggler.DataSource
implements LifeCycleObserver
{
static dataSourceName = FeatureToggleDbName;
static readonly defaultConfig = config;
constructor(
@inject('datasources.config.feature', {optional: true})
dsConfig: object = config,
) {
super(dsConfig);
}
}
迁移
此服务所需的迁移将在安装过程中自动处理 FEATURETOGGLE_MIGRATION 或者 SOURCELOOP_MIGRATION env变量。迁移使用 db-migrate 与 db-migrate-pg 驱动程序,因此您必须安装这些包才能使用自动迁移。请注意,如果您正在使用一些预先存在的迁移或数据库,它们可能会受到影响。在这种情况下,建议您在项目根目录下复制迁移文件,使用 FEATURETOGGLE_MIGRATION_COPY 或者 SOURCELOOP_MIGRATION_COPY env变量。您可以根据您的特定需求定制或挑选复制文件中的迁移,然后将它们应用到DB。
这个迁移脚本同时支持MySQL和PostgreSQL数据库,由环境变量控制。通过设置MYSQL_MIGRATION为“true”,脚本使用MySQL配置文件运行迁移;否则,默认为PostgreSQL。
此外,现在有一个选项可以选择SQL迁移或PostgreSQL迁移。
注:对于 @sourceloop/cli 用户可以在搭建过程中通过选择“数据源类型”选项来指定此选项。
API文档
常见的头
授权:承载,其中是使用JWT颁发者和密钥签名的JWT令牌。
Content-Type: application/json 如果API方法不是GET,则在响应和请求中
常用请求路径参数
{version}:定义API的版本
常见的反应
200:成功响应。响应体随API的变化而变化 401: Unauthorized: JWT令牌缺失或无效 403: Forbidden:不允许执行相关API 404:实体未找到 400:错误请求(错误消息随API的不同而不同) 201: No content: Empty Response
API的细节
参观 OpenAPI规范文档
-@2x.png)