feat(core/cache): support cache (#876)

* feat(core/cache): support cache

* build(create-nocobase-app): remove --cache-store-package cli option

* perf(core/cache): modify default cache config and remove unnecessary logic code

(cherry picked from commit 6e6086de7a908f38fe516e5340cb2154229c3843)

# Conflicts:
#	packages/core/server/src/application.ts
#	packages/core/server/src/helper.ts
This commit is contained in:
lyf-coder 2022-10-10 15:37:47 +08:00 committed by chenos
parent 3082a7d6f8
commit 92fda15efd
12 changed files with 253 additions and 8 deletions

View File

@ -27,6 +27,10 @@ DB_STORAGE=storage/db/nocobase.sqlite
# DB_PASSWORD=nocobase # DB_PASSWORD=nocobase
# DB_LOGGING=on # DB_LOGGING=on
################# CACHE #################
# default is memory cache, when develop mode,code's change will be clear memory cache, so can use 'cache-manager-fs-hash'
# CACHE_CONFIG={"storePackage":"cache-manager-fs-hash","ttl":86400,"max":1000}
################# STORAGE (Initialization only) ################# ################# STORAGE (Initialization only) #################
INIT_ROOT_EMAIL=admin@nocobase.com INIT_ROOT_EMAIL=admin@nocobase.com

3
.gitignore vendored
View File

@ -17,4 +17,5 @@ coverage
docs-dist/ docs-dist/
.npmrc .npmrc
dist/ dist/
docker/**/storage docker/**/storage
cache/diskstore-*

View File

@ -0,0 +1,5 @@
import { createDefaultCacheConfig } from '@nocobase/cache';
const cacheConfig = !!process.env.CACHE_CONFIG ? JSON.parse(process.env.CACHE_CONFIG) : createDefaultCacheConfig();
export default cacheConfig;

View File

@ -1,9 +1,11 @@
import database from './database'; import database from './database';
import plugins from './plugins'; import plugins from './plugins';
import resourcer from './resourcer'; import resourcer from './resourcer';
import cache from './cache';
export default { export default {
database, database,
resourcer, resourcer,
plugins, plugins,
cache,
}; };

View File

@ -13,7 +13,8 @@
"types": "./lib/index.d.ts", "types": "./lib/index.d.ts",
"dependencies": { "dependencies": {
"@nocobase/database": "0.7.4-alpha.7", "@nocobase/database": "0.7.4-alpha.7",
"@nocobase/resourcer": "0.7.4-alpha.7" "@nocobase/resourcer": "0.7.4-alpha.7",
"@nocobase/cache": "0.7.4-alpha.7"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,6 +1,7 @@
import Koa from 'koa'; import Koa from 'koa';
import { Database } from '@nocobase/database'; import { Database } from '@nocobase/database';
import { Action } from '@nocobase/resourcer'; import { Action } from '@nocobase/resourcer';
import { Cache } from '@nocobase/cache';
import lodash from 'lodash'; import lodash from 'lodash';
import * as actions from './actions'; import * as actions from './actions';
@ -10,9 +11,11 @@ export type Next = () => Promise<any>;
export interface Context extends Koa.Context { export interface Context extends Koa.Context {
db: Database; db: Database;
cache: Cache;
action: Action; action: Action;
body: any; body: any;
app: any; app: any;
[key: string]: any; [key: string]: any;
} }

8
packages/core/cache/.npmignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules
*.log
docs
__tests__
jest.config.js
tsconfig.json
src
.fatherrc.ts

26
packages/core/cache/package.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"name": "@nocobase/cache",
"version": "0.7.4-alpha.7",
"description": "",
"license": "Apache-2.0",
"licenses": [
{
"type": "Apache-2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0"
}
],
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"dependencies": {
"cache-manager": "^4.1.0"
},
"devDependencies": {
"@types/cache-manager": "^4.0.2",
"cache-manager-fs-hash": "^1.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nocobase/nocobase.git",
"directory": "packages/cache"
}
}

View File

@ -0,0 +1,74 @@
import { createCache, createDefaultCacheConfig } from '@nocobase/cache';
export function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
describe('cache', () => {
it('createCache-with-mem', async () => {
const cacheConfig = createDefaultCacheConfig();
cacheConfig.ttl = 1;
const cache = createCache(cacheConfig);
await cache.set('name', 'Emma');
expect(await cache.get('name')).toEqual('Emma');
await sleep(100);
expect(await cache.get('name')).toEqual('Emma');
await sleep(1005);
expect(await cache.get('name')).toBeUndefined();
});
it('createCache-with-single-config', async () => {
let cacheConfigStr =
'{"store":"memory","ttl":1,"max":10}';
let cacheConfig = JSON.parse(cacheConfigStr);
let cache = createCache(cacheConfig);
await cache.set('name', 'Emma');
expect(await cache.get('name')).toEqual('Emma');
await sleep(100);
expect(await cache.get('name')).toEqual('Emma');
await sleep(1005);
expect(await cache.get('name')).toBeUndefined();
cacheConfigStr =
'[{"store":"memory","ttl":1,"max":10}]';
cacheConfig = JSON.parse(cacheConfigStr);
cache = createCache(cacheConfig);
await cache.set('name', 'Emma');
expect(await cache.get('name')).toEqual('Emma');
await sleep(100);
expect(await cache.get('name')).toEqual('Emma');
await sleep(1005);
expect(await cache.get('name')).toBeUndefined();
});
it('createCache-with-default-cache-manager-fs-hash', async () => {
const cacheConfig = createDefaultCacheConfig();
cacheConfig.ttl = 1;
cacheConfig.storePackage = 'cache-manager-fs-hash';
const cache = createCache(cacheConfig);
await cache.set('name', 'Emma');
expect(await cache.get('name')).toEqual('Emma');
await sleep(100);
expect(await cache.get('name')).toEqual('Emma');
await sleep(1005);
expect(await cache.get('name')).toBeUndefined();
});
it('createCache-multi-cache', async () => {
const cacheConfigStr =
'[{"store":"memory","ttl":1,"max":10},{"storePackage":"cache-manager-fs-hash","ttl":10,"max":100}]';
const cacheConfig = JSON.parse(cacheConfigStr);
const cache = createCache(cacheConfig);
await cache.set('name', 'Emma');
expect(await cache.get('name')).toEqual('Emma');
await sleep(1005);
expect(await cache.get('name')).toEqual('Emma');
await sleep(5000);
expect(await cache.get('name')).toEqual('Emma');
await sleep(5000);
expect(await cache.get('name')).toBeUndefined();
});
});

70
packages/core/cache/src/index.ts vendored Normal file
View File

@ -0,0 +1,70 @@
import { CacheOptions, caching, CachingConfig, multiCaching, StoreConfig, WrapArgsType } from 'cache-manager';
/**
* be used for create cache {@link createCache}
*/
export type ICacheConfig = StoreConfig &
CacheOptions & {
// every storeConfig init a store instance
storePackage?: string;
};
/**
* create a default cache config object
* @returns {ICacheConfig}
*/
export function createDefaultCacheConfig(): ICacheConfig {
return {
ttl: 86400, // seconds
max: 1000,
store: 'memory',
};
}
/**
* cache and multi cache common method and only keep promise method
*/
export interface Cache {
set<T>(key: string, value: T, options?: CachingConfig): Promise<T>;
set<T>(key: string, value: T, ttl: number): Promise<T>;
wrap<T>(...args: WrapArgsType<T>[]): Promise<T>;
get<T>(key: string): Promise<T | undefined>;
del(key: string): Promise<any>;
reset(): Promise<void>;
}
/**
* create cache
* <br/> if cacheConfig is array and length gt 1 then will be return multi cache, else will be return cache
* @param {ICacheConfig | ICacheConfig[]} cacheConfig
* @returns {Cache}
*/
export function createCache(cacheConfig: ICacheConfig | ICacheConfig[] = createDefaultCacheConfig()): Cache {
if (Array.isArray(cacheConfig)) {
// multi cache
if (cacheConfig.length === 1) {
return createCacheByICacheConfig(cacheConfig[0]);
} else {
let caches = [];
for (const cacheConfigEle of cacheConfig) {
caches.push(createCacheByICacheConfig(cacheConfigEle));
}
return multiCaching(caches) as Cache;
}
} else {
return createCacheByICacheConfig(cacheConfig);
}
}
function createCacheByICacheConfig(cacheConfig: ICacheConfig): Cache {
// if storePackage exist then load storePackage and instead store
if (cacheConfig.storePackage) {
cacheConfig.store = require(cacheConfig.storePackage);
}
return caching(cacheConfig);
}

View File

@ -1,6 +1,6 @@
import { promisify } from 'util';
import { ACL } from '@nocobase/acl'; import { ACL } from '@nocobase/acl';
import { registerActions } from '@nocobase/actions'; import { registerActions } from '@nocobase/actions';
import { Cache, createCache, ICacheConfig } from '@nocobase/cache';
import Database, { Collection, CollectionOptions, IDatabaseOptions } from '@nocobase/database'; import Database, { Collection, CollectionOptions, IDatabaseOptions } from '@nocobase/database';
import Resourcer, { ResourceOptions } from '@nocobase/resourcer'; import Resourcer, { ResourceOptions } from '@nocobase/resourcer';
import { applyMixins, AsyncEmitter, Toposort, ToposortOptions } from '@nocobase/utils'; import { applyMixins, AsyncEmitter, Toposort, ToposortOptions } from '@nocobase/utils';
@ -11,12 +11,14 @@ import Koa, { DefaultContext as KoaDefaultContext, DefaultState as KoaDefaultSta
import compose from 'koa-compose'; import compose from 'koa-compose';
import { isBoolean } from 'lodash'; import { isBoolean } from 'lodash';
import semver from 'semver'; import semver from 'semver';
import { promisify } from 'util';
import { createACL } from './acl'; import { createACL } from './acl';
import { AppManager } from './app-manager'; import { AppManager } from './app-manager';
import { registerCli } from './commands'; import { registerCli } from './commands';
import { createI18n, createResourcer, registerMiddlewares } from './helper'; import { createI18n, createResourcer, registerMiddlewares } from './helper';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { InstallOptions, PluginManager } from './plugin-manager'; import { InstallOptions, PluginManager } from './plugin-manager';
const packageJson = require('../package.json'); const packageJson = require('../package.json');
export type PluginConfiguration = string | [string, any]; export type PluginConfiguration = string | [string, any];
@ -27,6 +29,7 @@ export interface ResourcerOptions {
export interface ApplicationOptions { export interface ApplicationOptions {
database?: IDatabaseOptions | Database; database?: IDatabaseOptions | Database;
cache?: ICacheConfig | ICacheConfig[];
resourcer?: ResourcerOptions; resourcer?: ResourcerOptions;
bodyParser?: any; bodyParser?: any;
cors?: any; cors?: any;
@ -39,11 +42,13 @@ export interface ApplicationOptions {
export interface DefaultState extends KoaDefaultState { export interface DefaultState extends KoaDefaultState {
currentUser?: any; currentUser?: any;
[key: string]: any; [key: string]: any;
} }
export interface DefaultContext extends KoaDefaultContext { export interface DefaultContext extends KoaDefaultContext {
db: Database; db: Database;
cache: Cache;
resourcer: Resourcer; resourcer: Resourcer;
i18n: any; i18n: any;
[key: string]: any; [key: string]: any;
@ -134,6 +139,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
protected _resourcer: Resourcer; protected _resourcer: Resourcer;
protected _cache: Cache;
protected _cli: Command; protected _cli: Command;
protected _i18n: i18n; protected _i18n: i18n;
@ -161,6 +168,10 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
return this._db; return this._db;
} }
get cache() {
return this._cache;
}
get resourcer() { get resourcer() {
return this._resourcer; return this._resourcer;
} }
@ -203,8 +214,10 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this._resourcer = createResourcer(options); this._resourcer = createResourcer(options);
this._cli = new Command('nocobase').usage('[command] [options]'); this._cli = new Command('nocobase').usage('[command] [options]');
this._i18n = createI18n(options); this._i18n = createI18n(options);
this._cache = createCache(options.cache);
this.context.db = this._db; this.context.db = this._db;
this.context.resourcer = this._resourcer; this.context.resourcer = this._resourcer;
this.context.cache = this._cache;
this._pm = new PluginManager({ this._pm = new PluginManager({
app: this, app: this,

View File

@ -4990,6 +4990,11 @@
"@types/connect" "*" "@types/connect" "*"
"@types/node" "*" "@types/node" "*"
"@types/cache-manager@^4.0.2":
version "4.0.2"
resolved "https://registry.npmmirror.com/@types/cache-manager/-/cache-manager-4.0.2.tgz#5e76dd9e7881c23f332c2f48e5f326bd05ba9ac9"
integrity sha512-fT5FMdzsiSX0AbgnS5gDvHl2Nco0h5zYyjwDQy4yPC7Ww6DeGMVKPRqIZtg9HOXDV2kkc18SL1B0N8f0BecrCA==
"@types/connect@*": "@types/connect@*":
version "3.4.35" version "3.4.35"
resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1"
@ -6697,6 +6702,11 @@ async-validator@^4.0.2:
resolved "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz#034a0fd2103a6b2ebf010da75183bec299247afe" resolved "https://registry.npmjs.org/async-validator/-/async-validator-4.0.7.tgz#034a0fd2103a6b2ebf010da75183bec299247afe"
integrity sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ== integrity sha512-Pj2IR7u8hmUEDOwB++su6baaRi+QvsgajuFB9j95foM1N2gy5HM4z60hfusIO0fBPG5uLAEl6yCJr1jNSVugEQ==
async@3.2.3, async@^3.2.0, async@~3.2.0:
version "3.2.3"
resolved "https://registry.npmjs.org/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
async@^2.6.2: async@^2.6.2:
version "2.6.4" version "2.6.4"
resolved "https://registry.npmmirror.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" resolved "https://registry.npmmirror.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
@ -6711,11 +6721,6 @@ async@^2.6.3, async@~2.6.1:
dependencies: dependencies:
lodash "^4.17.14" lodash "^4.17.14"
async@^3.2.0, async@~3.2.0:
version "3.2.3"
resolved "https://registry.npmjs.org/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
asynckit@^0.4.0: asynckit@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -7457,6 +7462,22 @@ cache-content-type@^1.0.0:
mime-types "^2.1.18" mime-types "^2.1.18"
ylru "^1.2.0" ylru "^1.2.0"
cache-manager-fs-hash@^1.0.0:
version "1.0.0"
resolved "https://registry.npmmirror.com/cache-manager-fs-hash/-/cache-manager-fs-hash-1.0.0.tgz#9a3f3fa239c48c54fc6b00575032b72c07dcad99"
integrity sha512-fK2vEAhWh7IAzBP+JwUEJkng0OPOxw3ji86SjQS5SWdLx2Cytku6xFNoQK32pedtuae/wBl/jA/X4abiAYHS/Q==
dependencies:
lockfile "^1.0.4"
cache-manager@^4.1.0:
version "4.1.0"
resolved "https://registry.npmmirror.com/cache-manager/-/cache-manager-4.1.0.tgz#aa986421f1c975a862d6de88edb9ab1d30f4bd39"
integrity sha512-ZGM6dLxrP65bfOZmcviWMadUOCICqpLs92+P/S5tj8onz+k+tB7Gr+SAgOUHCQtfm2gYEQDHiKeul4+tYPOJ8A==
dependencies:
async "3.2.3"
lodash.clonedeep "^4.5.0"
lru-cache "^7.10.1"
cacheable-request@^6.0.0: cacheable-request@^6.0.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@ -14504,6 +14525,13 @@ locate-path@^6.0.0:
dependencies: dependencies:
p-locate "^5.0.0" p-locate "^5.0.0"
lockfile@^1.0.4:
version "1.0.4"
resolved "https://registry.npmmirror.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609"
integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==
dependencies:
signal-exit "^3.0.2"
lodash-es@^4.17.11: lodash-es@^4.17.11:
version "4.17.21" version "4.17.21"
resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" resolved "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
@ -14536,6 +14564,11 @@ lodash.clone@4.5.0, lodash.clone@^4.3.2:
resolved "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" resolved "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6"
integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y= integrity sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=
lodash.clonedeep@^4.5.0:
version "4.5.0"
resolved "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==
lodash.debounce@^4.0.8: lodash.debounce@^4.0.8:
version "4.0.8" version "4.0.8"
resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@ -14735,6 +14768,11 @@ lru-cache@^6.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
lru-cache@^7.10.1:
version "7.14.0"
resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.14.0.tgz#21be64954a4680e303a09e9468f880b98a0b3c7f"
integrity sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==
luxon@^1.28.0: luxon@^1.28.0:
version "1.28.0" version "1.28.0"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"