Skip to content

Commit aa9e8e9

Browse files
committed
feat: 重构缓存逻辑, 支持配置各项缓存 TTL 配置(前端 >= 2.16.0), 不再支持其他方式自定义; scriptResourceCache 缓存操作支持传入自定义 TTL
1 parent 7e421db commit aa9e8e9

8 files changed

Lines changed: 188 additions & 161 deletions

File tree

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store",
3-
"version": "2.20.84",
3+
"version": "2.21.0",
44
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.",
55
"main": "src/main.js",
66
"scripts": {

backend/src/constants.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const GIST_BACKUP_FILE_NAME = 'Sub-Store';
1212
export const ARTIFACT_REPOSITORY_KEY = 'Sub-Store Artifacts Repository';
1313
export const RESOURCE_CACHE_KEY = '#sub-store-cached-resource';
1414
export const HEADERS_RESOURCE_CACHE_KEY = '#sub-store-cached-headers-resource';
15-
export const CHR_EXPIRATION_TIME_KEY = '#sub-store-chr-expiration-time'; // Custom expiration time key; (Loon|Surge) Default write 1 min
16-
export const CACHE_EXPIRATION_TIME_MS = 60 * 60 * 1000; // 1 hour
17-
export const SCRIPT_RESOURCE_CACHE_KEY = '#sub-store-cached-script-resource'; // cached-script-resource CSR
18-
export const CSR_EXPIRATION_TIME_KEY = '#sub-store-csr-expiration-time'; // Custom expiration time key; (Loon|Surge) Default write 48 hour
15+
export const SCRIPT_RESOURCE_CACHE_KEY = '#sub-store-cached-script-resource';
16+
export const DEFAULT_CACHE_TTL = 60 * 60 * 1000; // 1 hour
17+
export const DEFAULT_HEADERS_CACHE_TTL = 60 * 1000; // 1 min
18+
export const DEFAULT_SCRIPT_CACHE_TTL = 48 * 3600 * 1000; // 48 hours

backend/src/restful/settings.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ async function updateSettings(req, res) {
4242
...settings,
4343
...req.body,
4444
};
45+
[
46+
'defaultTimeout',
47+
'cacheThreshold',
48+
'resourceCacheTtl',
49+
'headersCacheTtl',
50+
'scriptCacheTtl',
51+
].map((key) => {
52+
let value = Number(newSettings[key]);
53+
if (!isFinite(value) || value <= 0) {
54+
delete newSettings[key];
55+
}
56+
});
4557
$.write(newSettings, SETTINGS_KEY);
4658
if (
4759
req.body.githubUser ||
Lines changed: 47 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import $ from '@/core/app';
22
import {
33
HEADERS_RESOURCE_CACHE_KEY,
4-
CHR_EXPIRATION_TIME_KEY,
4+
DEFAULT_HEADERS_CACHE_TTL,
5+
SETTINGS_KEY,
56
} from '@/constants';
67

78
class ResourceCache {
89
constructor() {
9-
this.expires = getExpiredTime();
1010
if (!$.read(HEADERS_RESOURCE_CACHE_KEY)) {
1111
$.write('{}', HEADERS_RESOURCE_CACHE_KEY);
1212
}
@@ -24,18 +24,18 @@ class ResourceCache {
2424
this._cleanup();
2525
}
2626

27-
_cleanup() {
28-
// clear obsolete cached resource
27+
_cleanup(prefix, ttl) {
28+
const resolvedTTL = normalizeTTL(ttl) ?? 0;
2929
let clear = false;
30+
const now = Date.now();
3031
Object.entries(this.resourceCache).forEach((entry) => {
31-
const [id, updated] = entry;
32-
if (!updated.time) {
33-
// clear old version cache
34-
delete this.resourceCache[id];
35-
$.delete(`#${id}`);
36-
clear = true;
37-
}
38-
if (new Date().getTime() - updated.time > this.expires) {
32+
const [id, cached] = entry;
33+
34+
if (
35+
!cached.time ||
36+
cached.time < now - resolvedTTL ||
37+
(prefix && id.startsWith(prefix))
38+
) {
3939
delete this.resourceCache[id];
4040
clear = true;
4141
}
@@ -52,66 +52,55 @@ class ResourceCache {
5252
$.write(JSON.stringify(this.resourceCache), HEADERS_RESOURCE_CACHE_KEY);
5353
}
5454

55-
get(id) {
56-
const updated = this.resourceCache[id] && this.resourceCache[id].time;
57-
if (updated && new Date().getTime() - updated <= this.expires) {
58-
return this.resourceCache[id].data;
55+
gettime(id) {
56+
const time = this.resourceCache[id] && this.resourceCache[id].time;
57+
if (time && new Date().getTime() <= time) {
58+
return this.resourceCache[id].time;
5959
}
6060
return null;
6161
}
6262

63-
gettime(id) {
64-
const updated = this.resourceCache[id] && this.resourceCache[id].time;
65-
if (updated && new Date().getTime() - updated <= this.expires) {
66-
return this.resourceCache[id].time;
63+
get(id, ttl, remove) {
64+
const resolvedTTL = normalizeTTL(ttl) ?? 0;
65+
const cached = this.resourceCache[id];
66+
const time = cached && cached.time;
67+
if (time) {
68+
if (Date.now() + resolvedTTL <= time) return cached.data;
69+
if (remove) {
70+
delete this.resourceCache[id];
71+
this._persist();
72+
}
6773
}
6874
return null;
6975
}
7076

71-
set(id, value) {
72-
this.resourceCache[id] = { time: new Date().getTime(), data: value };
77+
set(id, value, ttl) {
78+
const resolvedTTL = normalizeTTL(ttl) ?? getTTL();
79+
this.resourceCache[id] = {
80+
time: Date.now() + resolvedTTL,
81+
data: value,
82+
};
7383
this._persist();
7484
}
7585
}
7686

77-
function getExpiredTime() {
78-
// console.log($.read(CHR_EXPIRATION_TIME_KEY));
79-
if (!$.read(CHR_EXPIRATION_TIME_KEY)) {
80-
$.write('6e4', CHR_EXPIRATION_TIME_KEY); // 1分钟
81-
}
82-
let expiration = 6e4;
83-
if ($.env.isLoon) {
84-
const loont = {
85-
// Loon 插件自义定
86-
'1\u5206\u949f': 6e4,
87-
'5\u5206\u949f': 3e5,
88-
'10\u5206\u949f': 6e5,
89-
'30\u5206\u949f': 18e5, // "30分钟"
90-
'1\u5c0f\u65f6': 36e5,
91-
'2\u5c0f\u65f6': 72e5,
92-
'3\u5c0f\u65f6': 108e5,
93-
'6\u5c0f\u65f6': 216e5,
94-
'12\u5c0f\u65f6': 432e5,
95-
'24\u5c0f\u65f6': 864e5,
96-
'48\u5c0f\u65f6': 1728e5,
97-
'72\u5c0f\u65f6': 2592e5, // "72小时"
98-
'\u53c2\u6570\u4f20\u5165': 'readcachets', // "参数输入"
99-
};
100-
let intimed = $.read(
101-
'#\u54cd\u5e94\u5934\u7f13\u5b58\u6709\u6548\u671f',
102-
); // Loon #响应头缓存有效期
103-
// console.log(intimed);
104-
if (intimed in loont) {
105-
expiration = loont[intimed];
106-
if (expiration === 'readcachets') {
107-
expiration = intimed;
108-
}
87+
function normalizeTTL(ttl) {
88+
const value = Number(ttl);
89+
if (!isFinite(value)) return null;
90+
if (value > 0) return value;
91+
return null;
92+
}
93+
94+
function getTTL() {
95+
const settings = $.read(SETTINGS_KEY);
96+
let ttl = settings?.headersCacheTtl;
97+
if (ttl) {
98+
ttl = Number(ttl);
99+
if (isFinite(ttl) && ttl > 0) {
100+
return ttl * 1000;
109101
}
110-
return expiration;
111-
} else {
112-
expiration = $.read(CHR_EXPIRATION_TIME_KEY);
113-
return expiration;
114102
}
103+
return DEFAULT_HEADERS_CACHE_TTL;
115104
}
116105

117106
export default new ResourceCache();
Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import $ from '@/core/app';
2-
import { CACHE_EXPIRATION_TIME_MS, RESOURCE_CACHE_KEY } from '@/constants';
2+
import {
3+
RESOURCE_CACHE_KEY,
4+
DEFAULT_CACHE_TTL,
5+
SETTINGS_KEY,
6+
} from '@/constants';
37

48
class ResourceCache {
5-
constructor(expires) {
6-
this.expires = expires;
9+
constructor() {
710
if (!$.read(RESOURCE_CACHE_KEY)) {
811
$.write('{}', RESOURCE_CACHE_KEY);
912
}
@@ -21,18 +24,18 @@ class ResourceCache {
2124
this._cleanup();
2225
}
2326

24-
_cleanup() {
25-
// clear obsolete cached resource
27+
_cleanup(prefix, ttl) {
28+
const resolvedTTL = normalizeTTL(ttl) ?? 0;
2629
let clear = false;
30+
const now = Date.now();
2731
Object.entries(this.resourceCache).forEach((entry) => {
28-
const [id, updated] = entry;
29-
if (!updated.time) {
30-
// clear old version cache
31-
delete this.resourceCache[id];
32-
$.delete(`#${id}`);
33-
clear = true;
34-
}
35-
if (new Date().getTime() - updated.time > this.expires) {
32+
const [id, cached] = entry;
33+
34+
if (
35+
!cached.time ||
36+
cached.time < now - resolvedTTL ||
37+
(prefix && id.startsWith(prefix))
38+
) {
3639
delete this.resourceCache[id];
3740
clear = true;
3841
}
@@ -49,18 +52,55 @@ class ResourceCache {
4952
$.write(JSON.stringify(this.resourceCache), RESOURCE_CACHE_KEY);
5053
}
5154

52-
get(id) {
53-
const updated = this.resourceCache[id] && this.resourceCache[id].time;
54-
if (updated && new Date().getTime() - updated <= this.expires) {
55-
return this.resourceCache[id].data;
55+
gettime(id) {
56+
const time = this.resourceCache[id] && this.resourceCache[id].time;
57+
if (time && new Date().getTime() <= time) {
58+
return this.resourceCache[id].time;
59+
}
60+
return null;
61+
}
62+
63+
get(id, ttl, remove) {
64+
const resolvedTTL = normalizeTTL(ttl) ?? 0;
65+
const cached = this.resourceCache[id];
66+
const time = cached && cached.time;
67+
if (time) {
68+
if (Date.now() + resolvedTTL <= time) return cached.data;
69+
if (remove) {
70+
delete this.resourceCache[id];
71+
this._persist();
72+
}
5673
}
5774
return null;
5875
}
5976

60-
set(id, value) {
61-
this.resourceCache[id] = { time: new Date().getTime(), data: value };
77+
set(id, value, ttl) {
78+
const resolvedTTL = normalizeTTL(ttl) ?? getTTL();
79+
this.resourceCache[id] = {
80+
time: Date.now() + resolvedTTL,
81+
data: value,
82+
};
6283
this._persist();
6384
}
6485
}
6586

66-
export default new ResourceCache(CACHE_EXPIRATION_TIME_MS);
87+
function normalizeTTL(ttl) {
88+
const value = Number(ttl);
89+
if (!isFinite(value)) return null;
90+
if (value > 0) return value;
91+
return null;
92+
}
93+
94+
function getTTL() {
95+
const settings = $.read(SETTINGS_KEY);
96+
let ttl = settings?.resourceCacheTtl;
97+
if (ttl) {
98+
ttl = Number(ttl);
99+
if (isFinite(ttl) && ttl > 0) {
100+
return ttl * 1000;
101+
}
102+
}
103+
return DEFAULT_CACHE_TTL;
104+
}
105+
106+
export default new ResourceCache();

0 commit comments

Comments
 (0)