Skip to content

Commit 6ccc647

Browse files
committed
Optimize API interface
1 parent e55e994 commit 6ccc647

4 files changed

Lines changed: 226 additions & 50 deletions

File tree

config-overrides.js

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
1-
const {
2-
override,
3-
addWebpackExternals,
4-
} = require('customize-cra')
1+
const { override, addWebpackExternals } = require('customize-cra');
2+
const CompressionPlugin = require('compression-webpack-plugin');
53

64
module.exports = override(
7-
addWebpackExternals({
8-
"cockpit": "cockpit"
9-
}),
10-
)
5+
addWebpackExternals({ "cockpit": "cockpit" }),
6+
(config, env) => {
7+
config.devtool = false;
8+
// 添加 CompressionPlugin
9+
config.plugins.push(
10+
new CompressionPlugin({
11+
algorithm: 'gzip',
12+
test: /\.(js|css)$/,
13+
threshold: 10240,
14+
minRatio: 0.8,
15+
})
16+
);
17+
return config;
18+
}
19+
);

package.json

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
"homepage": ".",
66
"dependencies": {
77
"@apollo/client": "^3.7.9",
8-
"@mui/material": "^5.12.2",
9-
"@mui/icons-material": "^5.11.16",
10-
"@emotion/styled": "^11.10.6",
118
"@emotion/react": "^11.11.0",
9+
"@emotion/styled": "^11.10.6",
10+
"@mui/icons-material": "^5.11.16",
11+
"@mui/material": "^5.12.2",
1212
"@patternfly/patternfly": "^4.224.2",
1313
"axios": "^1.3.4",
1414
"bootstrap": "5.1.3",
@@ -18,13 +18,13 @@
1818
"react-app-rewired": "^2.2.1",
1919
"react-bootstrap": "^2.1.2",
2020
"react-dom": "^18.2.0",
21+
"react-lazyload": "^3.2.0",
22+
"react-markdown": "^9.0.1",
2123
"react-redux": "^7.2.6",
2224
"react-router-dom": "^6.2.2",
23-
"redux-saga": "^1.1.3",
2425
"react-scripts": "^5.0.0",
25-
"sass": "^1.49.9",
26-
"react-lazyload": "^3.2.0",
27-
"react-markdown": "^9.0.1"
26+
"redux-saga": "^1.1.3",
27+
"sass": "^1.49.9"
2828
},
2929
"scripts": {
3030
"start": "react-app-rewired start",
@@ -49,5 +49,8 @@
4949
"last 1 firefox version",
5050
"last 1 safari version"
5151
]
52+
},
53+
"devDependencies": {
54+
"compression-webpack-plugin": "^11.1.0"
5255
}
53-
}
56+
}

src/helpers/api/apiCore.js

Lines changed: 193 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,37 @@ import cockpit from 'cockpit';
44
// content type
55
axios.defaults.headers.common['Content-Type'] = 'application/json; charset=utf-8';
66

7+
// 配置缓存对象
8+
const configCache = {
9+
apiKey: null,
10+
nginxPort: null,
11+
fetching: new Set()
12+
};
13+
714
const getNginxConfig = async () => {
15+
// 检查缓存
16+
if (configCache.nginxPort) {
17+
return configCache.nginxPort;
18+
}
19+
20+
// 防止并发重复请求
21+
if (configCache.fetching.has('nginx')) {
22+
return new Promise((resolve, reject) => {
23+
const checkCache = () => {
24+
if (configCache.nginxPort) {
25+
resolve(configCache.nginxPort);
26+
} else if (!configCache.fetching.has('nginx')) {
27+
reject(new Error("Failed to get nginx config"));
28+
} else {
29+
setTimeout(checkCache, 100);
30+
}
31+
};
32+
checkCache();
33+
});
34+
}
35+
36+
configCache.fetching.add('nginx');
37+
838
try {
939
var script = "docker exec -i websoft9-apphub apphub getconfig --section nginx_proxy_manager";
1040
let content = (await cockpit.spawn(["/bin/bash", "-c", script], { superuser: "try" })).trim();
@@ -15,6 +45,8 @@ const getNginxConfig = async () => {
1545
throw new Error("Nginx Listen Port Not Set");
1646
}
1747

48+
// 缓存结果
49+
configCache.nginxPort = listen_port;
1850
return listen_port;
1951
}
2052
catch (error) {
@@ -28,17 +60,45 @@ const getNginxConfig = async () => {
2860
else {
2961
throw new Error(errorText || "Get Nginx Listen Port Error");
3062
}
63+
} finally {
64+
configCache.fetching.delete('nginx');
3165
}
3266
}
3367

3468
const getApiKey = async () => {
69+
// 检查缓存
70+
if (configCache.apiKey) {
71+
return configCache.apiKey;
72+
}
73+
74+
// 防止并发重复请求
75+
if (configCache.fetching.has('apikey')) {
76+
return new Promise((resolve, reject) => {
77+
const checkCache = () => {
78+
if (configCache.apiKey) {
79+
resolve(configCache.apiKey);
80+
} else if (!configCache.fetching.has('apikey')) {
81+
reject(new Error("Failed to get api key"));
82+
} else {
83+
setTimeout(checkCache, 100);
84+
}
85+
};
86+
checkCache();
87+
});
88+
}
89+
90+
configCache.fetching.add('apikey');
91+
3592
try {
3693
var script = "docker exec -i websoft9-apphub apphub getconfig --section api_key --key key";
3794
const api_key = (await cockpit.spawn(["/bin/bash", "-c", script], { superuser: "try" })).trim();
3895
if (!api_key) {
3996
throw new Error(" Api Key Not Set");
4097
}
41-
return api_key
98+
99+
// 缓存结果
100+
configCache.apiKey = api_key;
101+
return api_key;
42102
}
43103
catch (error) {
44104
const errorText = [error.problem, error.reason, error.message]
@@ -51,48 +111,152 @@ const getApiKey = async () => {
51111
else {
52112
throw new Error(errorText || "Get The Apphub's Api Key Error");
53113
}
114+
} finally {
115+
configCache.fetching.delete('apikey');
54116
}
55117
}
56118

57-
// intercepting to capture errors
58-
axios.interceptors.response.use(
59-
(response) => {
60-
if (response.status === 200) {
61-
return response.data;
62-
}
63-
// else if (response.status === 204) {
64-
// return null;
65-
// }
66-
},
67-
(error) => {
68-
error.message = error.response?.data?.details || error.message || "Unknown Error";
69-
return Promise.reject(error);
119+
// 检查是否为配置错误并清除相应缓存
120+
const handleConfigError = (error) => {
121+
const status = error.response?.status;
122+
const details = error.response?.data?.details;
123+
124+
// API Key 无效 - 后端返回 400 + "Invalid API Key"
125+
if (status === 400 && details === "Invalid API Key") {
126+
console.warn('[Config Error] Invalid API Key detected, clearing cache');
127+
configCache.apiKey = null;
128+
return true;
70129
}
71-
);
130+
131+
// Nginx 端口/连接错误
132+
if (status === 404 || status === 502 || status === 503 ||
133+
error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') {
134+
console.warn('[Config Error] Nginx connection error detected, clearing cache');
135+
configCache.nginxPort = null;
136+
return true;
137+
}
138+
139+
return false;
140+
};
72141

73142
class APICore {
143+
constructor() {
144+
this.axiosInstance = null;
145+
}
146+
147+
// 创建带缓存配置的 axios 实例
148+
async getAxiosInstance() {
149+
if (!this.axiosInstance) {
150+
// 并行获取配置,避免串行等待
151+
const [apiKey, nginxPort] = await Promise.all([
152+
getApiKey(),
153+
getNginxConfig()
154+
]);
155+
156+
this.axiosInstance = axios.create({
157+
baseURL: `${window.location.protocol}//${window.location.hostname}:${nginxPort}/api`,
158+
headers: {
159+
'Content-Type': 'application/json; charset=utf-8',
160+
'x-api-key': apiKey
161+
},
162+
timeout: 30000
163+
});
164+
165+
// 只为这个实例设置拦截器,不影响全局
166+
this.setupInstanceInterceptors();
167+
}
168+
return this.axiosInstance;
169+
}
170+
171+
// 为当前实例设置拦截器
172+
setupInstanceInterceptors() {
173+
// 响应拦截器
174+
this.axiosInstance.interceptors.response.use(
175+
(response) => {
176+
// 成功响应,返回数据
177+
return response.status === 200 ? response.data : response;
178+
},
179+
(error) => {
180+
// 检查并处理配置错误
181+
const isConfigError = handleConfigError(error);
182+
if (isConfigError) {
183+
// 标记为配置错误,调用方可以选择重试
184+
error.configError = true;
185+
}
186+
187+
error.message = error.response?.data?.details || error.message || "Unknown Error";
188+
return Promise.reject(error);
189+
}
190+
);
191+
}
192+
193+
// 重置实例(配置错误时使用)
194+
resetInstance() {
195+
this.axiosInstance = null;
196+
}
197+
198+
// 带重试的请求方法
199+
async requestWithRetry(method, url, data = null, params = null) {
200+
let lastError;
201+
202+
// 最多重试一次
203+
for (let attempt = 0; attempt < 2; attempt++) {
204+
try {
205+
const instance = await this.getAxiosInstance();
206+
207+
let response;
208+
switch (method.toLowerCase()) {
209+
case 'get':
210+
response = await instance.get(url, { params });
211+
break;
212+
case 'post':
213+
response = await instance.post(url, data, { params });
214+
break;
215+
case 'put':
216+
response = await instance.put(url, data, { params });
217+
break;
218+
case 'delete':
219+
response = await instance.delete(url, { params });
220+
break;
221+
default:
222+
throw new Error(`Unsupported method: ${method}`);
223+
}
224+
225+
// 直接返回拦截器处理后的结果
226+
return response;
227+
} catch (error) {
228+
lastError = error;
229+
230+
// 如果是配置错误且是第一次尝试,重置实例并重试
231+
if (error.configError && attempt === 0) {
232+
console.info(`[API Retry] Config error detected, resetting instance and retrying...`);
233+
this.resetInstance();
234+
await new Promise(resolve => setTimeout(resolve, 200)); // 短暂等待
235+
continue;
236+
}
237+
238+
// 其他情况直接抛出错误
239+
break;
240+
}
241+
}
242+
243+
throw lastError;
244+
}
245+
74246
get = async (url, params) => {
75-
axios.defaults.headers.common['x-api-key'] = await getApiKey();
76-
axios.defaults.baseURL = `${window.location.protocol}//${window.location.hostname}:` + await getNginxConfig() + "/api";
77-
return axios.get(url, { params });
247+
return this.requestWithRetry('get', url, null, params);
78248
};
79249

80-
post = async (url, params, data) => {
81-
axios.defaults.headers.common['x-api-key'] = await getApiKey();
82-
axios.defaults.baseURL = `${window.location.protocol}//${window.location.hostname}:` + await getNginxConfig() + "/api";
83-
return axios.post(url, data, { params });
250+
post = async (url, data, params) => {
251+
return this.requestWithRetry('post', url, data, params);
84252
};
85253

86-
put = async (url, params, data) => {
87-
axios.defaults.headers.common['x-api-key'] = await getApiKey();
88-
axios.defaults.baseURL = `${window.location.protocol}//${window.location.hostname}:` + await getNginxConfig() + "/api";
89-
return axios.put(url, data, { params });
254+
put = async (url, data, params) => {
255+
return this.requestWithRetry('put', url, data, params);
90256
};
91257

92258
delete = async (url, params) => {
93-
axios.defaults.headers.common['x-api-key'] = await getApiKey();
94-
axios.defaults.baseURL = `${window.location.protocol}//${window.location.hostname}:` + await getNginxConfig() + "/api";
95-
return axios.delete(url, { params });
259+
return this.requestWithRetry('delete', url, null, params);
96260
};
97261
}
98262

src/helpers/api/appHub.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,25 @@ import { APICore } from "./apiCore";
33
const api = new APICore();
44

55
// 获取产品目录
6-
function AppCatalog(locale: String, params: any): Promise<any> {
6+
function AppCatalog(locale, params) {
77
const baseUrl = `/apps/catalog/${locale}`;
88
return api.get(`${baseUrl}`, params);
99
}
1010

1111
// 获取产品列表
12-
function AppAvailable(locale: String, params: any): Promise<any> {
12+
function AppAvailable(locale, params) {
1313
const baseUrl = `/apps/available/${locale}`;
1414
return api.get(`${baseUrl}`, params);
1515
}
1616

1717
// 安装应用
18-
function AppInstall(params: any, body: any): Promise<any> {
18+
function AppInstall(params, body) {
1919
const baseUrl = '/apps/install';
20-
return api.post(`${baseUrl}`, params, body);
20+
return api.post(`${baseUrl}`, body, params);
2121
}
2222

2323
// 获取设置
24-
function GetSettingsBySection(section: String, params: any): Promise<any> {
24+
function GetSettingsBySection(section, params) {
2525
const baseUrl = `/settings/${section}`;
2626
return api.get(`${baseUrl}`, params);
2727
}

0 commit comments

Comments
 (0)