From 16ce4114443acb26dbd4480121efe1f9556e9af2 Mon Sep 17 00:00:00 2001 From: "Xc@centOs" Date: Sun, 15 Sep 2024 15:26:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=9C=A8=E6=9F=90=E4=B8=80?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E6=88=B3=E7=8A=B6=E6=80=81=E4=B8=8B=EF=BC=8C?= =?UTF-8?q?=E5=AF=B9=E6=9F=90=E8=A1=8C=E8=BF=9B=E8=A1=8C=E6=9F=90=E4=B8=AA?= =?UTF-8?q?action=E7=9A=84=E5=88=A4=E6=96=AD=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- es/debugStore/index.js | 1 - es/features/cache.d.ts | 3 ++- es/features/cache.js | 36 ++++++++++++++++++++++++---- es/features/runningTree.js | 31 +++++++++++++++++++----- es/features/subscriber.js | 4 ++-- es/page.react.d.ts | 18 +++++++------- lib/debugStore/index.js | 5 ++-- lib/features/cache.d.ts | 3 ++- lib/features/cache.js | 34 ++++++++++++++++++++++++--- lib/features/runningTree.js | 31 +++++++++++++++++++----- lib/features/subscriber.js | 4 ++-- lib/page.react.d.ts | 18 +++++++------- src/features/cache.ts | 47 +++++++++++++++++++++++++++++++++---- src/features/runningTree.ts | 33 ++++++++++++++++++++------ 14 files changed, 211 insertions(+), 57 deletions(-) diff --git a/es/debugStore/index.js b/es/debugStore/index.js index 2e800be8..377b8007 100644 --- a/es/debugStore/index.js +++ b/es/debugStore/index.js @@ -5,7 +5,6 @@ import { makeIntrinsicLogics } from 'oak-domain/lib/store/IntrinsicLogics'; import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid'; import { LocalStorage } from '../features/localStorage'; import { cloneDeep } from 'oak-domain/lib/utils/lodash'; - async function initDataInStore(store, initialData, stat) { store.resetInitialData(initialData, stat); } diff --git a/es/features/cache.d.ts b/es/features/cache.d.ts index b1fabece..637ebe91 100644 --- a/es/features/cache.d.ts +++ b/es/features/cache.d.ts @@ -34,6 +34,7 @@ export declare class Cache extends Featu private attrUpdateMatrix?; private connector; private baseRelationAuth; + private entityActionAuthDict; constructor(storageSchema: StorageSchema, connector: Connector>, frontendContextBuilder: (store: CacheStore) => SyncContext, checkers: Array>>, localStorage: LocalStorage, common: CommonConfiguration); /** * 处理cache中需要缓存的数据 @@ -96,7 +97,7 @@ export declare class Cache extends Featu action: ED[T]['Action']; data?: ED[T]['Operation']['data']; filter?: ED[T]['Operation']['filter']; - }, checkerTypes?: CheckerType[]): boolean | OakUserException; + }, checkerTypes?: CheckerType[]): boolean | { [A in ED[T]["Action"]]: boolean | OakUserException; }[ED[T]["Action"]]; redoOperation(opers: Array<{ entity: keyof ED; operation: ED[keyof ED]['Operation']; diff --git a/es/features/cache.js b/es/features/cache.js index f3b9c58b..c81d1b2e 100644 --- a/es/features/cache.js +++ b/es/features/cache.js @@ -1,5 +1,5 @@ import { Feature } from '../types/Feature'; -import { pull, intersection, unset, union } from 'oak-domain/lib/utils/lodash'; +import { set, pull, intersection, unset, union } from 'oak-domain/lib/utils/lodash'; import { CacheStore } from '../cacheStore/CacheStore'; import { OakRowUnexistedException, OakException, OakUserException } from 'oak-domain/lib/types/Exception'; import { assert } from 'oak-domain/lib/utils/assert'; @@ -22,6 +22,8 @@ export class Cache extends Feature { attrUpdateMatrix; connector; baseRelationAuth; + // 缓存在某一时间戳状态下,对某行进行某个action的判断结果 + entityActionAuthDict; constructor(storageSchema, connector, frontendContextBuilder, checkers, localStorage, common) { super(); this.syncEventsCallbacks = []; @@ -37,6 +39,7 @@ export class Cache extends Feature { // 现在这个init变成了异步行为,不知道有没有影响。by Xc 20231126 this.initPromise = new Promise((resolve) => this.initSavedLogic(resolve)); this.buildEntityGraph(); + this.entityActionAuthDict = {}; } /** * 处理cache中需要缓存的数据 @@ -86,7 +89,7 @@ export class Cache extends Feature { this.refreshing++; const { result, opRecords, message } = await this.connector.callAspect(name, params, ignoreContext ? undefined : this.context || this.contextBuilder()); callback && callback(result, opRecords); - if (opRecords) { + if (opRecords?.length) { this.syncInner(opRecords); } this.refreshing--; @@ -257,14 +260,19 @@ export class Cache extends Feature { syncInner(records) { this.begin(); this.cacheStore.sync(records, this.context); + const ts2 = this.cacheStore.getLastUpdateTs(); + // 同步以后,当前所有缓存的action结果都不成立了 + this.entityActionAuthDict = {}; // 唤起同步注册的回调 this.syncEventsCallbacks.map((ele) => ele(records)); this.context.commit(); this.context = undefined; } sync(records) { - this.syncInner(records); - this.publish(); + if (records.length) { + this.syncInner(records); + this.publish(); + } } /** * 前端缓存做operation只可能是测试权限,必然回滚 @@ -325,10 +333,24 @@ export class Cache extends Feature { return result; } checkOperation(entity, operation, checkerTypes) { + // 缓存同一id对同一action的判断,避免性能低下 + const { action, data, filter } = operation; + let id; + let ts; + if (filter && !data && typeof filter.id === 'string') { + id = filter.id; + ts = this.cacheStore.getLastUpdateTs(); + if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) { + return this.entityActionAuthDict[ts][entity][id][action]; + } + } const rollback = this.begin(true); try { this.cacheStore.check(entity, operation, this.context, checkerTypes); rollback && rollback(); + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, true); + } return true; } catch (err) { @@ -336,6 +358,9 @@ export class Cache extends Feature { if (err instanceof OakRowUnexistedException) { // 有外键缺失,尝试发一下请求 this.fetchRows(err.getRows()); + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, false); + } return false; } /** @@ -345,6 +370,9 @@ export class Cache extends Feature { /* if (!(err instanceof OakUserException)) { throw err; } */ + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, err); + } return err; } } diff --git a/es/features/runningTree.js b/es/features/runningTree.js index f024b5ea..91e10233 100644 --- a/es/features/runningTree.js +++ b/es/features/runningTree.js @@ -3,6 +3,7 @@ import { cloneDeep, unset, uniq } from "oak-domain/lib/utils/lodash"; import { combineFilters, getRelevantIds } from "oak-domain/lib/store/filter"; import { createOperationsFromModies } from 'oak-domain/lib/store/modi'; import { judgeRelation } from "oak-domain/lib/store/relation"; +import { CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types"; import { Feature } from '../types/Feature'; import { generateNewId } from 'oak-domain/lib/utils/uuid'; export const MODI_NEXT_PATH_SUFFIX = ':next'; @@ -729,9 +730,14 @@ class ListNode extends EntityNode { } } const id = item.id || generateNewId(); + const now = Date.now(); this.ulManager.push(lsn, { action: 'create', - data: Object.assign(item, { id }), + data: Object.assign(item, { + id, + [CreateAtAttribute]: now, + [UpdateAtAttribute]: now, + }), }); return id; } @@ -748,7 +754,9 @@ class ListNode extends EntityNode { removeItemInner(id, lsn) { this.ulManager.push(lsn, { action: 'remove', - data: {}, + data: { + [DeleteAtAttribute]: Date.now(), + }, filter: { id, }, @@ -794,7 +802,9 @@ class ListNode extends EntityNode { updateItemInner(lsn, data, id, action) { this.ulManager.push(lsn, { action: action || 'update', - data, + data: Object.assign({}, data, { + [UpdateAtAttribute]: Date.now(), + }), filter: { id, }, @@ -1215,9 +1225,14 @@ class SingleNode extends EntityNode { }); } } + const now = Date.now(); this.ulManager.push(lsn, { action: 'create', - data: Object.assign({}, data, { id }), + data: Object.assign({}, data, { + id, + [CreateAtAttribute]: now, + [UpdateAtAttribute]: now, + }), }); this.refreshListChildren(); this.setDirty(); @@ -1238,7 +1253,9 @@ class SingleNode extends EntityNode { undefinedAttrs.forEach((attr) => unset(data, attr)); this.ulManager.push(lsn, { action: action || 'update', - data, + data: Object.assign({}, data, { + [UpdateAtAttribute]: Date.now(), + }), filter: { id: this.getId(), } @@ -1269,7 +1286,9 @@ class SingleNode extends EntityNode { assert(id); this.ulManager.push(lsn, { action: 'remove', - data: {}, + data: { + [DeleteAtAttribute]: Date.now(), + }, filter: { id, } diff --git a/es/features/subscriber.js b/es/features/subscriber.js index f1e94c0e..03d9eef4 100644 --- a/es/features/subscriber.js +++ b/es/features/subscriber.js @@ -105,7 +105,7 @@ export class SubScriber extends Feature { }); if (!optionInited) { let count = 0; - socket.on('connect_error', async () => { + socket.on('connect_error', async (err) => { count++; if (count > 50) { // 可能socket地址改变了,刷新重连 @@ -113,7 +113,7 @@ export class SubScriber extends Feature { } socket.removeAllListeners(); socket.disconnect(); - this.socket = null; + this.url = undefined; await this.connect(); resolve(undefined); }); diff --git a/es/page.react.d.ts b/es/page.react.d.ts index 70c2b5db..45851b65 100644 --- a/es/page.react.d.ts +++ b/es/page.react.d.ts @@ -49,21 +49,21 @@ export declare function createComponent(options: { url: string; } & OakNavigateToParameters, state?: Record | undefined, disableNamespace?: boolean | undefined): Promise; - addItem(data: Omit & { + addItem(data: Omit & { id?: string | undefined; }, path?: string | undefined): string; - addItems(data: (Omit & { + addItems(data: (Omit & { id?: string | undefined; })[], path?: string | undefined): string[]; removeItem(id: string, path?: string | undefined): void; removeItems(ids: string[], path?: string | undefined): void; - updateItem(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void; - updateItems(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void; + updateItem(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void; + updateItems(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void; recoverItem(id: string, path?: string | undefined): void; recoverItems(ids: string[], path?: string | undefined): void; resetItem(id: string, path?: string | undefined): void; - update(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void; - create(data: Omit, path?: string | undefined): void; + update(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void; + create(data: Omit, path?: string | undefined): void; remove(path?: string | undefined): void; isCreation(path?: string | undefined): boolean; clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void; @@ -75,9 +75,9 @@ export declare function createComponent; isDirty(path?: string | undefined): boolean; getFreshValue(path?: string | undefined): Partial | Partial[] | undefined; - checkOperation(entity: T2_2, operation: Omit, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException; - tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException; - getOperations(path?: string | undefined): { + checkOperation(entity: T2_2, operation: Omit, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException; }[ED[T2_2]["Action"]]; + tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException; }[ED[keyof ED]["Action"]]; + getOperations(path?: string | undefined): { entity: keyof ED; operation: ED[keyof ED]["Operation"]; }[] | undefined; diff --git a/lib/debugStore/index.js b/lib/debugStore/index.js index 976dbe7c..91a816e9 100644 --- a/lib/debugStore/index.js +++ b/lib/debugStore/index.js @@ -7,6 +7,7 @@ const DebugStore_1 = require("./DebugStore"); const IntrinsicLogics_1 = require("oak-domain/lib/store/IntrinsicLogics"); const uuid_1 = require("oak-domain/lib/utils/uuid"); const localStorage_1 = require("../features/localStorage"); +const lodash_1 = require("oak-domain/lib/utils/lodash"); async function initDataInStore(store, initialData, stat) { store.resetInitialData(initialData, stat); } @@ -41,8 +42,8 @@ async function materializeData(data, stat, localStorage) { async function execWatcher(store, watcher, context) { if (watcher.hasOwnProperty('actionData')) { const { entity, action, filter, actionData } = watcher; - const filter2 = typeof filter === 'function' ? await filter() : filter; - const data = typeof actionData === 'function' ? await actionData() : actionData; // 这里有个奇怪的编译错误,不理解 by Xc + const filter2 = typeof filter === 'function' ? await filter() : (0, lodash_1.cloneDeep)(filter); + const data = typeof actionData === 'function' ? await actionData() : (0, lodash_1.cloneDeep)(actionData); // 这里有个奇怪的编译错误,不理解 by Xc const result = await store.operate(entity, { id: await (0, uuid_1.generateNewIdAsync)(), action: action, diff --git a/lib/features/cache.d.ts b/lib/features/cache.d.ts index b1fabece..637ebe91 100644 --- a/lib/features/cache.d.ts +++ b/lib/features/cache.d.ts @@ -34,6 +34,7 @@ export declare class Cache extends Featu private attrUpdateMatrix?; private connector; private baseRelationAuth; + private entityActionAuthDict; constructor(storageSchema: StorageSchema, connector: Connector>, frontendContextBuilder: (store: CacheStore) => SyncContext, checkers: Array>>, localStorage: LocalStorage, common: CommonConfiguration); /** * 处理cache中需要缓存的数据 @@ -96,7 +97,7 @@ export declare class Cache extends Featu action: ED[T]['Action']; data?: ED[T]['Operation']['data']; filter?: ED[T]['Operation']['filter']; - }, checkerTypes?: CheckerType[]): boolean | OakUserException; + }, checkerTypes?: CheckerType[]): boolean | { [A in ED[T]["Action"]]: boolean | OakUserException; }[ED[T]["Action"]]; redoOperation(opers: Array<{ entity: keyof ED; operation: ED[keyof ED]['Operation']; diff --git a/lib/features/cache.js b/lib/features/cache.js index 394420ff..0360da83 100644 --- a/lib/features/cache.js +++ b/lib/features/cache.js @@ -25,6 +25,8 @@ class Cache extends Feature_1.Feature { attrUpdateMatrix; connector; baseRelationAuth; + // 缓存在某一时间戳状态下,对某行进行某个action的判断结果 + entityActionAuthDict; constructor(storageSchema, connector, frontendContextBuilder, checkers, localStorage, common) { super(); this.syncEventsCallbacks = []; @@ -40,6 +42,7 @@ class Cache extends Feature_1.Feature { // 现在这个init变成了异步行为,不知道有没有影响。by Xc 20231126 this.initPromise = new Promise((resolve) => this.initSavedLogic(resolve)); this.buildEntityGraph(); + this.entityActionAuthDict = {}; } /** * 处理cache中需要缓存的数据 @@ -89,7 +92,7 @@ class Cache extends Feature_1.Feature { this.refreshing++; const { result, opRecords, message } = await this.connector.callAspect(name, params, ignoreContext ? undefined : this.context || this.contextBuilder()); callback && callback(result, opRecords); - if (opRecords) { + if (opRecords?.length) { this.syncInner(opRecords); } this.refreshing--; @@ -260,14 +263,19 @@ class Cache extends Feature_1.Feature { syncInner(records) { this.begin(); this.cacheStore.sync(records, this.context); + const ts2 = this.cacheStore.getLastUpdateTs(); + // 同步以后,当前所有缓存的action结果都不成立了 + this.entityActionAuthDict = {}; // 唤起同步注册的回调 this.syncEventsCallbacks.map((ele) => ele(records)); this.context.commit(); this.context = undefined; } sync(records) { - this.syncInner(records); - this.publish(); + if (records.length) { + this.syncInner(records); + this.publish(); + } } /** * 前端缓存做operation只可能是测试权限,必然回滚 @@ -328,10 +336,24 @@ class Cache extends Feature_1.Feature { return result; } checkOperation(entity, operation, checkerTypes) { + // 缓存同一id对同一action的判断,避免性能低下 + const { action, data, filter } = operation; + let id; + let ts; + if (filter && !data && typeof filter.id === 'string') { + id = filter.id; + ts = this.cacheStore.getLastUpdateTs(); + if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) { + return this.entityActionAuthDict[ts][entity][id][action]; + } + } const rollback = this.begin(true); try { this.cacheStore.check(entity, operation, this.context, checkerTypes); rollback && rollback(); + if (id && ts) { + (0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, true); + } return true; } catch (err) { @@ -339,6 +361,9 @@ class Cache extends Feature_1.Feature { if (err instanceof Exception_1.OakRowUnexistedException) { // 有外键缺失,尝试发一下请求 this.fetchRows(err.getRows()); + if (id && ts) { + (0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, false); + } return false; } /** @@ -348,6 +373,9 @@ class Cache extends Feature_1.Feature { /* if (!(err instanceof OakUserException)) { throw err; } */ + if (id && ts) { + (0, lodash_1.set)(this.entityActionAuthDict, `${ts}.${entity}.${id}.${action}`, err); + } return err; } } diff --git a/lib/features/runningTree.js b/lib/features/runningTree.js index 3d64273e..3bdd87d8 100644 --- a/lib/features/runningTree.js +++ b/lib/features/runningTree.js @@ -6,6 +6,7 @@ const lodash_1 = require("oak-domain/lib/utils/lodash"); const filter_1 = require("oak-domain/lib/store/filter"); const modi_1 = require("oak-domain/lib/store/modi"); const relation_1 = require("oak-domain/lib/store/relation"); +const types_1 = require("oak-domain/lib/types"); const Feature_1 = require("../types/Feature"); const uuid_1 = require("oak-domain/lib/utils/uuid"); exports.MODI_NEXT_PATH_SUFFIX = ':next'; @@ -732,9 +733,14 @@ class ListNode extends EntityNode { } } const id = item.id || (0, uuid_1.generateNewId)(); + const now = Date.now(); this.ulManager.push(lsn, { action: 'create', - data: Object.assign(item, { id }), + data: Object.assign(item, { + id, + [types_1.CreateAtAttribute]: now, + [types_1.UpdateAtAttribute]: now, + }), }); return id; } @@ -751,7 +757,9 @@ class ListNode extends EntityNode { removeItemInner(id, lsn) { this.ulManager.push(lsn, { action: 'remove', - data: {}, + data: { + [types_1.DeleteAtAttribute]: Date.now(), + }, filter: { id, }, @@ -797,7 +805,9 @@ class ListNode extends EntityNode { updateItemInner(lsn, data, id, action) { this.ulManager.push(lsn, { action: action || 'update', - data, + data: Object.assign({}, data, { + [types_1.UpdateAtAttribute]: Date.now(), + }), filter: { id, }, @@ -1218,9 +1228,14 @@ class SingleNode extends EntityNode { }); } } + const now = Date.now(); this.ulManager.push(lsn, { action: 'create', - data: Object.assign({}, data, { id }), + data: Object.assign({}, data, { + id, + [types_1.CreateAtAttribute]: now, + [types_1.UpdateAtAttribute]: now, + }), }); this.refreshListChildren(); this.setDirty(); @@ -1241,7 +1256,9 @@ class SingleNode extends EntityNode { undefinedAttrs.forEach((attr) => (0, lodash_1.unset)(data, attr)); this.ulManager.push(lsn, { action: action || 'update', - data, + data: Object.assign({}, data, { + [types_1.UpdateAtAttribute]: Date.now(), + }), filter: { id: this.getId(), } @@ -1272,7 +1289,9 @@ class SingleNode extends EntityNode { (0, assert_1.assert)(id); this.ulManager.push(lsn, { action: 'remove', - data: {}, + data: { + [types_1.DeleteAtAttribute]: Date.now(), + }, filter: { id, } diff --git a/lib/features/subscriber.js b/lib/features/subscriber.js index 6ee91945..7c9a8d79 100644 --- a/lib/features/subscriber.js +++ b/lib/features/subscriber.js @@ -109,7 +109,7 @@ class SubScriber extends Feature_1.Feature { }); if (!optionInited) { let count = 0; - socket.on('connect_error', async () => { + socket.on('connect_error', async (err) => { count++; if (count > 50) { // 可能socket地址改变了,刷新重连 @@ -117,7 +117,7 @@ class SubScriber extends Feature_1.Feature { } socket.removeAllListeners(); socket.disconnect(); - this.socket = null; + this.url = undefined; await this.connect(); resolve(undefined); }); diff --git a/lib/page.react.d.ts b/lib/page.react.d.ts index 70c2b5db..45851b65 100644 --- a/lib/page.react.d.ts +++ b/lib/page.react.d.ts @@ -49,21 +49,21 @@ export declare function createComponent(options: { url: string; } & OakNavigateToParameters, state?: Record | undefined, disableNamespace?: boolean | undefined): Promise; - addItem(data: Omit & { + addItem(data: Omit & { id?: string | undefined; }, path?: string | undefined): string; - addItems(data: (Omit & { + addItems(data: (Omit & { id?: string | undefined; })[], path?: string | undefined): string[]; removeItem(id: string, path?: string | undefined): void; removeItems(ids: string[], path?: string | undefined): void; - updateItem(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void; - updateItems(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void; + updateItem(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void; + updateItems(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void; recoverItem(id: string, path?: string | undefined): void; recoverItems(ids: string[], path?: string | undefined): void; resetItem(id: string, path?: string | undefined): void; - update(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void; - create(data: Omit, path?: string | undefined): void; + update(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void; + create(data: Omit, path?: string | undefined): void; remove(path?: string | undefined): void; isCreation(path?: string | undefined): boolean; clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void; @@ -75,9 +75,9 @@ export declare function createComponent; isDirty(path?: string | undefined): boolean; getFreshValue(path?: string | undefined): Partial | Partial[] | undefined; - checkOperation(entity: T2_2, operation: Omit, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | import("oak-domain/lib/types").OakUserException; - tryExecute(path?: string | undefined, action?: string | undefined): boolean | import("oak-domain/lib/types").OakUserException; - getOperations(path?: string | undefined): { + checkOperation(entity: T2_2, operation: Omit, checkerTypes?: (CheckerType | "relation")[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException; }[ED[T2_2]["Action"]]; + tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException; }[ED[keyof ED]["Action"]]; + getOperations(path?: string | undefined): { entity: keyof ED; operation: ED[keyof ED]["Operation"]; }[] | undefined; diff --git a/src/features/cache.ts b/src/features/cache.ts index 1594014b..c3dc0514 100644 --- a/src/features/cache.ts +++ b/src/features/cache.ts @@ -2,7 +2,7 @@ import { EntityDict, OperateOption, SelectOption, OpRecord, AspectWrapper, Check import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { AspectDict as CommonAspectDict } from 'oak-common-aspect'; import { Feature } from '../types/Feature'; -import { merge, pull, intersection, omit, unset, union } from 'oak-domain/lib/utils/lodash'; +import { set, pull, intersection, omit, unset, union } from 'oak-domain/lib/utils/lodash'; import { CacheStore } from '../cacheStore/CacheStore'; import { OakRowUnexistedException, OakRowInconsistencyException, OakException, OakUserException } from 'oak-domain/lib/types/Exception'; import { AsyncContext } from 'oak-domain/lib/store/AsyncRowStore'; @@ -49,6 +49,17 @@ export class Cache extends Feature { private attrUpdateMatrix?: AttrUpdateMatrix; private connector: Connector>; private baseRelationAuth: BaseRelationAuth; + + // 缓存在某一时间戳状态下,对某行进行某个action的判断结果 + private entityActionAuthDict: { + [ts: number]: { + [T in keyof ED]?: { + [I: string]: { + [A in ED[T]['Action']]: boolean | OakUserException; + } + }; + }; + }; constructor( storageSchema: StorageSchema, @@ -81,6 +92,7 @@ export class Cache extends Feature { ); this.buildEntityGraph(); + this.entityActionAuthDict = {}; } /** @@ -151,7 +163,7 @@ export class Cache extends Feature { this.refreshing++; const { result, opRecords, message } = await this.connector.callAspect(name as string, params, ignoreContext ? undefined : this.context || this.contextBuilder()); callback && callback(result, opRecords); - if (opRecords) { + if (opRecords?.length) { this.syncInner(opRecords); } this.refreshing--; @@ -370,6 +382,10 @@ export class Cache extends Feature { this.begin(); this.cacheStore!.sync(records, this.context!); + const ts2 = this.cacheStore.getLastUpdateTs(); + + // 同步以后,当前所有缓存的action结果都不成立了 + this.entityActionAuthDict = {}; // 唤起同步注册的回调 this.syncEventsCallbacks.map((ele) => ele(records)); this.context!.commit(); @@ -377,8 +393,10 @@ export class Cache extends Feature { } sync(records: OpRecord[]) { - this.syncInner(records); - this.publish(); + if (records.length) { + this.syncInner(records); + this.publish(); + } } /** @@ -455,10 +473,25 @@ export class Cache extends Feature { }, checkerTypes?: CheckerType[] ) { + // 缓存同一id对同一action的判断,避免性能低下 + const { action, data, filter } = operation; + let id: string | undefined; + let ts: number | undefined; + if (filter && !data && typeof filter.id === 'string') { + id = filter.id; + ts = this.cacheStore.getLastUpdateTs(); + if (this.entityActionAuthDict[ts]?.[entity]?.[id]?.hasOwnProperty(action)) { + return this.entityActionAuthDict[ts]![entity]![id]![action]!; + } + } + const rollback = this.begin(true); try { this.cacheStore!.check(entity, operation, this.context!, checkerTypes); rollback && rollback(); + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, true); + } return true; } catch (err) { @@ -466,6 +499,9 @@ export class Cache extends Feature { if (err instanceof OakRowUnexistedException) { // 有外键缺失,尝试发一下请求 this.fetchRows(err.getRows()); + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, false); + } return false; } /** @@ -475,6 +511,9 @@ export class Cache extends Feature { /* if (!(err instanceof OakUserException)) { throw err; } */ + if (id && ts) { + set(this.entityActionAuthDict, `${ts}.${entity as string}.${id}.${action}`, err); + } return err as OakUserException; } } diff --git a/src/features/runningTree.ts b/src/features/runningTree.ts index cc427b71..6a7c3e19 100644 --- a/src/features/runningTree.ts +++ b/src/features/runningTree.ts @@ -3,7 +3,7 @@ import { cloneDeep, difference, unset, merge, uniq, omit } from "oak-domain/lib/ import { combineFilters, getRelevantIds } from "oak-domain/lib/store/filter"; import { createOperationsFromModies } from 'oak-domain/lib/store/modi'; import { judgeRelation } from "oak-domain/lib/store/relation"; -import { EntityDict, StorageSchema, OpRecord, CreateOpResult, RemoveOpResult, AspectWrapper, AuthDefDict, CascadeRelationItem, CascadeActionItem, UpdateOpResult } from "oak-domain/lib/types"; +import { EntityDict, StorageSchema, OpRecord, CreateOpResult, RemoveOpResult, AspectWrapper, AuthDefDict, CascadeRelationItem, CascadeActionItem, UpdateOpResult, CreateAtAttribute, UpdateAtAttribute, DeleteAtAttribute } from "oak-domain/lib/types"; import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain'; import { AspectDict as CommonAspectDict } from 'oak-common-aspect'; @@ -900,9 +900,14 @@ class ListNode< } } const id = item.id || generateNewId(); + const now = Date.now(); this.ulManager.push(lsn, { action: 'create', - data: Object.assign(item, { id }), + data: Object.assign(item, { + id, + [CreateAtAttribute]: now, + [UpdateAtAttribute]: now, + }) as ED[T]['CreateSingle']['data'], } as Omit); return id; } @@ -924,7 +929,9 @@ class ListNode< private removeItemInner(id: string, lsn: number) { this.ulManager.push(lsn, { action: 'remove', - data: {}, + data: { + [DeleteAtAttribute]: Date.now(), + }, filter: { id, }, @@ -990,7 +997,9 @@ class ListNode< ) { this.ulManager.push(lsn, { action: action || 'update', - data, + data: Object.assign({}, data, { + [UpdateAtAttribute]: Date.now(), + }), filter: { id, }, @@ -1504,9 +1513,15 @@ class SingleNode); this.refreshListChildren(); this.setDirty(); @@ -1531,7 +1546,9 @@ class SingleNode