Skip to content

Commit

Permalink
重构json和kv数据源,增加单元测试,优化代码
Browse files Browse the repository at this point in the history
  • Loading branch information
Yun-Shan committed Oct 16, 2023
1 parent c6de527 commit 0e45c18
Show file tree
Hide file tree
Showing 17 changed files with 485 additions and 365 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
registry-url: https://npm.pkg.github.com/
scope: '@enraged-dun-cookie-development-team'
- run: npm ci
Expand All @@ -33,10 +33,10 @@ jobs:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
registry-url: https://npm.pkg.github.com/
scope: '@enraged-dun-cookie-development-team'
- run: npm ci
Expand Down
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export default {
testEnvironment: 'node',
roots: ['<rootDir>/src', '<rootDir>/test'],
testMatch: ['**/test/**/*.ts', '!**/test/utils/**'],
transformIgnorePatterns: ['/node_modules/(?!(bilibili-dynamic)/)'],
transform: transform,
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
Expand Down
284 changes: 147 additions & 137 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@enraged-dun-cookie-development-team/common": "^0.0.1-alpha.28",
"@sinclair/typebox": "^0.31.17",
"@stoplight/json-ref-resolver": "^3.1.6",
"@swc/core": "^1.3.92",
"@swc/core": "^1.3.93",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"clone": "^2.1.2",
Expand All @@ -30,7 +30,7 @@
"@types/clone": "^2.1.2",
"@types/jest": "^29.5.5",
"@types/luxon": "^3.3.2",
"@types/node": "^20.8.4",
"@types/node": "^20.8.6",
"@typescript-eslint/eslint-plugin": "^6.7.5",
"@typescript-eslint/parser": "^6.7.5",
"eslint": "^8.51.0",
Expand All @@ -39,9 +39,9 @@
"husky": "^8.0.3",
"jest": "^29.7.0",
"jest-html-reporters": "^3.1.4",
"lint-staged": "^14.0.1",
"lint-staged": "^15.0.1",
"prettier": "^3.0.3",
"rollup": "^4.0.2",
"rollup": "^4.1.4",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-delete": "^2.0.0",
"ts-jest": "^29.1.1",
Expand Down
117 changes: 117 additions & 0 deletions src/datasource/DataContent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { DataSourceId } from './DataSource';
import { ErrorObject } from 'ajv';
import { Patch } from 'rfc6902';

type TimestampNormal = {
/**
* 平台的时间戳,必须是毫秒级的,精度不足的应当补零
*/
timestamp: number;
/**
* 时间戳精度
*/
timestampPrecision: 'day' | 'hour' | 'minute' | 'second' | 'ms';
};
type TimestampNone = {
/**
* 没有时间戳
*/
timestampPrecision: 'none';
};
export type Timestamp = TimestampNormal | TimestampNone;

export enum DataContentType {
COMMON = 'common',
JSON = 'json',
KV = 'kv',
}

type DataContentCommon = {
/**
* 通用数据类型
*/
type: DataContentType.COMMON;
/**
* 数据id,通常使用平台提供的id即可,不同数据源的id值可以重复
*/
id: string;
/**
* 饼的原始内容
*/
rawContent: string;
/**
* 饼的额外内容
*/
extraRawContent?: Record<string, string>;
};
/**
* 用于监视json
*/
export type DataContentJson<T = unknown> = {
/**
* json类型
*/
type: DataContentType.JSON;

/**
* 旧内容
*/
oldValue: T;
/**
* 新内容
*/
newValue: T;
/**
* 将oldValue转换成newValue的patch
*/
patch: Patch;
/**
* patch中涉及到的全部路径,代表有哪些路径被修改了
*/
changePaths: string[];
};

export type PrimitiveWithEmpty = string | boolean | number | undefined | null;
/**
* 用于监视简单值的key/value,
*/
export type DataContentKeyValue = {
/**
* key/value类型
*/
type: DataContentType.KV;

/**
* 旧KV键值对
*/
oldValue: Record<string, PrimitiveWithEmpty>;
/**
* 新KV键值对
*/
newValue: Record<string, PrimitiveWithEmpty>;

/**
* 所有更新的key
*/
changeKeys: string[];
};
export type DataContentUnion = DataContentCommon | DataContentJson | DataContentKeyValue;
/**
* 一个饼
*/
export type DataItem = {
/**
* 数据源id
*/
dataSourceId: DataSourceId;
/**
* 蹲饼器蹲到这个饼的时间(unix毫秒时间戳)
*/
fetchTime: number;
/**
* 响应结构不满足预定义结构时产生的异常。
* 如果是通过正常蹲饼流程拿到的DataItem对象,该字段不会被json序列化输出
*/
schemaErrors?: ErrorObject[];
} & DataContentUnion &
Timestamp;
25 changes: 15 additions & 10 deletions src/datasource/DataSource.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DataContentUnion, DataItem, FetchResult, Timestamp } from '../fetch/FetchResult';
import { FetchResult } from '../fetch/FetchResult';
import { CommonRequestOptions, Http } from '@enraged-dun-cookie-development-team/common/request';
import { Logger } from '@enraged-dun-cookie-development-team/common/logger';
import { Schema } from 'ajv';
import { DateTime } from 'luxon';
import { ErrorObject } from 'ajv/lib/types';
import { DataSourceConfig } from './DataSourceConfig';
import { JsonValidator } from '@enraged-dun-cookie-development-team/common/json';
import { DataContentType, DataContentUnion, DataItem, Timestamp } from './DataContent';

/**
* 显示信息
Expand All @@ -21,18 +22,17 @@ export interface DisplayInfo {
readonly name: string;
}

export interface CacheInfo<T> {
id: string;
content: T;
}

export class DataSourceTypeInfo {
/**
* @param platform 唯一的平台名
* @param type 相同平台下的唯一数据源类型
* @param reqCountEachFetch 每次蹲饼的请求数量
*/
constructor(readonly platform: string, readonly type: string, readonly reqCountEachFetch: number = 1) {}
constructor(
readonly platform: string,
readonly type: string,
readonly reqCountEachFetch: number = 1
) {}

get id() {
return this.platform + ':' + this.type;
Expand All @@ -47,6 +47,7 @@ export type DataSourceId = ReturnType<typeof DataSourceId>;

const PERSIST_CACHE_COUNT = 100;

// noinspection JSUnusedGlobalSymbols
export abstract class DataSource {
protected readonly logger: Logger;
readonly id: DataSourceId;
Expand All @@ -63,7 +64,11 @@ export abstract class DataSource {
* @param dataId 数据源id,相同平台唯一,不同平台可以重复(用于内部识别)
* @param config 自定义配置
*/
protected constructor(readonly type: DataSourceTypeInfo, dataId: string, readonly config: DataSourceConfig) {
protected constructor(
readonly type: DataSourceTypeInfo,
dataId: string,
readonly config: DataSourceConfig
) {
this.logger = config.logger;
this.id = DataSourceId(type, dataId);
this.idStr = `${this.id.typeId}:${this.id.dataId}`;
Expand Down Expand Up @@ -140,13 +145,13 @@ export abstract class DataSource {
const newCookies: DataItem[] = [];
const newCookieIds: string[] = [];
for (const item of items) {
if (item.type === 'common') {
if (item.type === DataContentType.COMMON) {
if (!this.cookieIdCacheMap[item.id]) {
this.cookieIdCacheMap[item.id] = true;
newCookies.push(item);
newCookieIds.push(item.id);
}
} else if (item.type === 'json' || item.type === 'kv') {
} else if (item.type === DataContentType.JSON || item.type === DataContentType.KV) {
newCookies.push(item);
}
}
Expand Down
90 changes: 47 additions & 43 deletions src/datasource/JsonDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,60 @@
import { DataSource, DataSourceTypeInfo } from './DataSource';
import { createPatch } from 'rfc6902';
import { DataContentJson } from '../fetch/FetchResult';
import { Pointer } from 'rfc6902/pointer';
import { DataSourceConfig } from './DataSourceConfig';

const DEFAULT_KEY = 'default';

export abstract class JsonDataSource extends DataSource {
private readonly jsonValues: Record<string, unknown> = {};
import { DataContentJson, DataContentType } from './DataContent';

/**
* Json数据源
*/
// noinspection JSUnusedGlobalSymbols
export abstract class JsonDataSource<T = unknown> extends DataSource {
protected oldValue: T | undefined;
/**
* 暂时只在首次检查新值时初始化
*/
protected inited = false;

/**
* @see DataSource#constructor
*/
protected constructor(type: DataSourceTypeInfo, dataId: string, config: DataSourceConfig) {
protected constructor(
type: DataSourceTypeInfo,
dataId: string,
config: DataSourceConfig,
private readonly monitorPointers: Pointer[]
) {
super(type, dataId, config);
}

protected set(name: string, value: unknown): void;
protected set(value: unknown): void;
protected set(_key: unknown, _value?: unknown) {
const { key, value } = this.extraKV(_key, _value);
this.jsonValues[key] = value;
}

protected hasKey(name = DEFAULT_KEY) {
return !!this.jsonValues[name];
}

protected diff(name: string, value: unknown): void;
protected diff(value: unknown): void;
protected diff(_key: unknown, _value?: unknown) {
const { key, value } = this.extraKV(_key, _value);
const oldValue = this.jsonValues[key];
return createPatch(oldValue, value);
}

protected createContent<T = unknown>(oldValue: T, newValue: T): DataContentJson {
const patch = createPatch(oldValue, newValue);
const paths = patch.map((it) => it.path);
return {
type: 'json',
oldValue: oldValue,
newValue: newValue,
patch: patch,
changePaths: paths,
};
}

private extraKV(name: unknown, value?: unknown) {
if (typeof value === 'undefined' || typeof name !== 'string') {
return { key: DEFAULT_KEY, value: name };
} else {
return { key: name, value: value };
protected createContentIfChanged(newValue: T): DataContentJson | undefined {
if (!this.inited) {
this.oldValue = newValue;
this.inited = true;
return;
}
try {
const patch = createPatch(this.oldValue, newValue);
if (patch.length === 0) return;
let changed = false;
for (const pointer of this.monitorPointers) {
const subPatch = createPatch(pointer.get(this.oldValue), pointer.get(newValue));
if (subPatch.length > 0) {
changed = true;
break;
}
}
if (!changed) return;
const paths = patch.map((it) => it.path);
return {
type: DataContentType.JSON,
oldValue: this.oldValue,
newValue: newValue,
patch: patch,
changePaths: paths,
};
} finally {
this.oldValue = newValue;
}
}
}
Loading

0 comments on commit 0e45c18

Please sign in to comment.