@@ -4,7 +4,37 @@ import cockpit from 'cockpit';
44// content type
55axios . 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+
714const 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
3468const 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
73142class 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
0 commit comments