@jx3box/jx3box-ui 2.3.9 → 2.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/agents/global-config-cdn.md +30 -0
- package/package.json +1 -1
- package/service/cms.js +50 -2
- package/service/header.js +63 -3
- package/service/thx.js +8 -5
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# 全局配置 CDN 读取约定
|
|
2
|
+
|
|
3
|
+
## 背景
|
|
4
|
+
|
|
5
|
+
`/api/cms/config` 中有一批低频更新的全局配置,后端会构建到 CDN JSON:
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
https://cdn.jx3box.com/config/global.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
这类配置在公共组件库和其它魔盒前端项目中应优先读取 CDN 静态 JSON,减少页面内多组件重复请求 config 接口。
|
|
12
|
+
|
|
13
|
+
## 实现原则
|
|
14
|
+
|
|
15
|
+
- 在项目服务层封装统一 helper,不要让具体组件直接请求 CDN。
|
|
16
|
+
- helper 优先读取内存缓存,再读 `sessionStorage`,没有缓存时请求 `global.json`。
|
|
17
|
+
- 同一页面生命周期内要复用 pending promise,避免多个组件并发触发多次请求。
|
|
18
|
+
- 对旧调用保持返回结构兼容,比如继续返回 `{ key, val }` 或数组,减少组件改动面。
|
|
19
|
+
- CDN 请求失败、缺少目标 key、或查询不适合静态 JSON 时,fallback 到旧 `/api/cms/config`。
|
|
20
|
+
- 公共头可以预热全局配置,但其它组件不能隐式依赖公共头加载顺序。
|
|
21
|
+
|
|
22
|
+
## 本仓落点
|
|
23
|
+
|
|
24
|
+
- `service/header.js`:`getGlobalConfig()` 负责读取 CDN JSON,并做内存缓存、`sessionStorage` 缓存、TTL 和 pending promise 复用。
|
|
25
|
+
- `service/cms.js`:`getConfig()` 优先从 `getGlobalConfig()` 合成旧接口兼容结构,失败时回退旧接口。
|
|
26
|
+
- `service/thx.js`:`getBoxcoinStatus()` 走统一 `getConfig({ key: "boxcoin" })`。
|
|
27
|
+
|
|
28
|
+
## 适用范围
|
|
29
|
+
|
|
30
|
+
后续其它魔盒项目如果提到“全局配置读取换 CDN”或“config 改成读 `global.json`”,默认按本约定处理:优先服务层兼容改造,组件侧尽量少动,迁移期保留旧接口 fallback。
|
package/package.json
CHANGED
package/service/cms.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { $cms } from "@jx3box/jx3box-common/js/api";
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
4
|
+
import { getGlobalConfig } from "./header";
|
|
5
|
+
|
|
6
|
+
const APP_CONFIG_KEYS = ["android_apk", "android_versions", "app_logo", "app_version_desc", "apple_url", "harmony_url"];
|
|
7
|
+
|
|
4
8
|
function getPostAuthors(post_id) {
|
|
5
9
|
return $cms({ mute: true }).get(`/api/cms/post/${post_id}/authors`);
|
|
6
10
|
}
|
|
@@ -80,8 +84,7 @@ function getTopicBucket(params) {
|
|
|
80
84
|
return $cms().get(`/api/cms/topic/bucket`, { params });
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
function getConfig(params) {
|
|
87
|
+
function getLegacyConfig(params) {
|
|
85
88
|
return $cms()
|
|
86
89
|
.get(`/api/cms/config`, { params })
|
|
87
90
|
.then((res) => {
|
|
@@ -89,6 +92,51 @@ function getConfig(params) {
|
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
function normalizeConfigItem(key, value, subtype = "") {
|
|
96
|
+
return {
|
|
97
|
+
key,
|
|
98
|
+
val: value,
|
|
99
|
+
subtype,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getConfigFromGlobal(params = {}) {
|
|
104
|
+
return getGlobalConfig().then((config) => {
|
|
105
|
+
if (!config || typeof config !== "object") return null;
|
|
106
|
+
|
|
107
|
+
if (params.key) {
|
|
108
|
+
const keys = String(params.key)
|
|
109
|
+
.split(",")
|
|
110
|
+
.map((key) => key.trim())
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
|
|
113
|
+
if (!keys.length) return null;
|
|
114
|
+
if (!keys.every((key) => Object.prototype.hasOwnProperty.call(config, key))) return null;
|
|
115
|
+
|
|
116
|
+
const items = keys.map((key) => normalizeConfigItem(key, config[key]));
|
|
117
|
+
return keys.length === 1 ? items[0] : items;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (params.subtype === "app") {
|
|
121
|
+
if (!APP_CONFIG_KEYS.every((key) => Object.prototype.hasOwnProperty.call(config, key))) return null;
|
|
122
|
+
return APP_CONFIG_KEYS.map((key) => normalizeConfigItem(key, config[key], "app"));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 获取config
|
|
130
|
+
function getConfig(params = {}) {
|
|
131
|
+
return getConfigFromGlobal(params)
|
|
132
|
+
.then((data) => {
|
|
133
|
+
return data || getLegacyConfig(params);
|
|
134
|
+
})
|
|
135
|
+
.catch(() => {
|
|
136
|
+
return getLegacyConfig(params);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
92
140
|
// 获取用户config
|
|
93
141
|
function getUserConfig(params) {
|
|
94
142
|
return $cms()
|
package/service/header.js
CHANGED
|
@@ -2,6 +2,47 @@ import axios from "axios";
|
|
|
2
2
|
import { $cms, $next } from "@jx3box/jx3box-common/js/api";
|
|
3
3
|
import JX3BOX from "@jx3box/jx3box-common/data/jx3box.json";
|
|
4
4
|
|
|
5
|
+
const GLOBAL_CONFIG_STORAGE_KEY = "jx3box:global-config";
|
|
6
|
+
const GLOBAL_CONFIG_TTL = 6 * 60 * 60 * 1000;
|
|
7
|
+
let globalConfigCache = null;
|
|
8
|
+
let globalConfigPending = null;
|
|
9
|
+
|
|
10
|
+
function getGlobalConfigUrl() {
|
|
11
|
+
const root = JX3BOX.__ossRoot || "https://cdn.jx3box.com/";
|
|
12
|
+
return `${root.replace(/\/?$/, "/")}config/global.json`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getNow() {
|
|
16
|
+
return Date.now ? Date.now() : new Date().getTime();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readGlobalConfigStorage() {
|
|
20
|
+
if (typeof sessionStorage === "undefined") return null;
|
|
21
|
+
try {
|
|
22
|
+
const raw = sessionStorage.getItem(GLOBAL_CONFIG_STORAGE_KEY);
|
|
23
|
+
if (!raw) return null;
|
|
24
|
+
const cache = JSON.parse(raw);
|
|
25
|
+
if (!cache?.data || !cache?.time) return null;
|
|
26
|
+
if (getNow() - cache.time > GLOBAL_CONFIG_TTL) return null;
|
|
27
|
+
return cache.data;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writeGlobalConfigStorage(data) {
|
|
34
|
+
if (typeof sessionStorage === "undefined") return;
|
|
35
|
+
try {
|
|
36
|
+
sessionStorage.setItem(
|
|
37
|
+
GLOBAL_CONFIG_STORAGE_KEY,
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
time: getNow(),
|
|
40
|
+
data,
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
}
|
|
45
|
+
|
|
5
46
|
function getLetter() {
|
|
6
47
|
return $next({ mute: true }).get("/api/letter/unread/count");
|
|
7
48
|
}
|
|
@@ -34,9 +75,28 @@ function getGames() {
|
|
|
34
75
|
|
|
35
76
|
// 获取全局配置
|
|
36
77
|
function getGlobalConfig() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
if (globalConfigCache) return Promise.resolve(globalConfigCache);
|
|
79
|
+
|
|
80
|
+
const storageConfig = readGlobalConfigStorage();
|
|
81
|
+
if (storageConfig) {
|
|
82
|
+
globalConfigCache = storageConfig;
|
|
83
|
+
return Promise.resolve(globalConfigCache);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!globalConfigPending) {
|
|
87
|
+
globalConfigPending = axios
|
|
88
|
+
.get(getGlobalConfigUrl())
|
|
89
|
+
.then((res) => {
|
|
90
|
+
globalConfigCache = res.data || {};
|
|
91
|
+
writeGlobalConfigStorage(globalConfigCache);
|
|
92
|
+
return globalConfigCache;
|
|
93
|
+
})
|
|
94
|
+
.finally(() => {
|
|
95
|
+
globalConfigPending = null;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return globalConfigPending;
|
|
40
100
|
}
|
|
41
101
|
|
|
42
102
|
export { getLetter, getMsg, getNav, getPanel, getBox, getMenu, getGames, getGlobalConfig };
|
package/service/thx.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { $pay
|
|
1
|
+
import { $pay } from "@jx3box/jx3box-common/js/api";
|
|
2
|
+
import { getConfig } from "./cms";
|
|
2
3
|
|
|
3
4
|
function getPostBoxcoinRecords(postType, postId, params) {
|
|
4
5
|
return $pay().get(`/api/inspire/article/${postType}/${postId}/history`, {
|
|
@@ -23,10 +24,12 @@ function getPostBoxcoinConfig(postType) {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
function getBoxcoinStatus() {
|
|
26
|
-
return
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return getConfig({ key: "boxcoin" }).then((data) => {
|
|
28
|
+
return {
|
|
29
|
+
data: {
|
|
30
|
+
data,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
30
33
|
});
|
|
31
34
|
}
|
|
32
35
|
|