collect-system/apps/server/src/models/base/row-cache.service.ts

183 lines
6.6 KiB
TypeScript
Raw Normal View History

2024-12-30 08:26:40 +08:00
import { UserProfile, RowModelRequest, RowRequestSchema } from "@nicestack/common";
import { RowModelService } from "./row-model.service";
import { isFieldCondition, LogicalCondition, SQLBuilder } from "./sql-builder";
import EventBus from "@server/utils/event-bus";
import supejson from "superjson-cjs"
import { deleteByPattern } from "@server/utils/redis/utils";
import { redis } from "@server/utils/redis/redis.service";
import { z } from "zod";
export class RowCacheService extends RowModelService {
constructor(tableName: string, private enableCache: boolean = true) {
super(tableName)
if (this.enableCache) {
EventBus.on("dataChanged", async ({ type, data }) => {
if (type === tableName) {
const dataArray = Array.isArray(data) ? data : [data];
for (const item of dataArray) {
try {
if (item.id) {
this.invalidateRowCacheById(item.id)
}
if (item.parentId) {
this.invalidateRowCacheById(item.parentId)
}
} catch (err) {
console.error(`Error deleting cache for type ${tableName}:`, err);
}
}
}
});
}
}
protected getRowCacheKey(id: string) {
return `row-data-${id}`;
}
private async invalidateRowCacheById(id: string) {
if (!this.enableCache) return;
const pattern = this.getRowCacheKey(id);
await deleteByPattern(pattern);
}
createJoinSql(request?: RowModelRequest): string[] {
return []
}
protected async getRowRelation(args: { data: any, staff?: UserProfile }) {
return args.data;
}
protected async setResPermissions(
data: any,
staff?: UserProfile,
) {
return data
}
protected async getRowDto(
data: any,
staff?: UserProfile,
): Promise<any> {
// 如果没有id直接返回原数据
if (!data?.id) return data;
// 如果未启用缓存,直接处理并返回数据
if (!this.enableCache) {
return this.processDataWithPermissions(data, staff);
}
const key = this.getRowCacheKey(data.id);
try {
// 尝试从缓存获取数据
const cachedData = await this.getCachedData(key, staff);
// 如果缓存命中,直接返回
if (cachedData) return cachedData;
// 处理数据并缓存
const processedData = await this.processDataWithPermissions(data, staff);
await redis.set(key, supejson.stringify(processedData));
return processedData;
} catch (err) {
this.logger.error('Error in getRowDto:', err);
throw err;
}
}
private async getCachedData(
key: string,
staff?: UserProfile
): Promise<any | null> {
const cachedDataStr = await redis.get(key);
if (!cachedDataStr) return null;
const cachedData = supejson.parse(cachedDataStr) as any;
if (!cachedData?.id) return null;
return staff
? this.setResPermissions(cachedData, staff)
: cachedData;
}
private async processDataWithPermissions(
data: any,
staff?: UserProfile
): Promise<any> {
// 处理权限
const permData = staff
? await this.setResPermissions(data, staff)
: data;
// 获取关联数据
return this.getRowRelation({ data: permData, staff });
}
protected createGetRowsFilters(
request: z.infer<typeof RowRequestSchema>,
staff?: UserProfile,
) {
const condition = super.createGetRowsFilters(request);
if (isFieldCondition(condition)) return {};
const baseCondition: LogicalCondition[] = [
{
field: `${this.tableName}.deleted_at`,
op: 'blank',
type: 'date',
},
];
condition.AND = [...baseCondition, ...condition.AND];
return condition;
}
createUnGroupingRowSelect(request?: RowModelRequest): string[] {
return [
`${this.tableName}.id AS id`,
SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`),
];
}
protected createGroupingRowSelect(
request: RowModelRequest,
wrapperSql: boolean,
): string[] {
const colsToSelect = super.createGroupingRowSelect(request, wrapperSql);
return colsToSelect.concat([
SQLBuilder.rowNumber(`${this.tableName}.id`, `${this.tableName}.id`),
]);
}
protected async getRowsSqlWrapper(
sql: string,
request?: RowModelRequest,
staff?: UserProfile,
): Promise<string> {
const groupingSql = SQLBuilder.join([
SQLBuilder.select([
...this.createGroupingRowSelect(request, true),
`${this.tableName}.id AS id`,
]),
SQLBuilder.from(this.tableName),
SQLBuilder.join(this.createJoinSql(request)),
SQLBuilder.where(this.createGetRowsFilters(request, staff)),
]);
const { rowGroupCols, valueCols, groupKeys } = request;
if (this.isDoingGroup(request)) {
const rowGroupCol = rowGroupCols[groupKeys.length];
const groupByField = rowGroupCol?.field?.replace('.', '_');
return SQLBuilder.join([
SQLBuilder.select([
groupByField,
...super.createAggSqlForWrapper(request),
'COUNT(id) AS child_count',
]),
SQLBuilder.from(`(${groupingSql})`),
SQLBuilder.where({
field: 'row_num',
value: '1',
op: 'equals',
}),
SQLBuilder.groupBy([groupByField]),
SQLBuilder.orderBy(
this.getOrderByColumns(request).map((item) => item.replace('.', '_')),
),
this.getLimitSql(request),
]);
} else
return SQLBuilder.join([
SQLBuilder.select(['*']),
SQLBuilder.from(`(${sql})`),
SQLBuilder.where({
field: 'row_num',
value: '1',
op: 'equals',
}),
this.getLimitSql(request),
]);
// return super.getRowsSqlWrapper(sql, request)
}
}