Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit fd9c551

Browse files
author
catlog22
committed
feat: 添加服务模块,包含缓存管理、事件管理和预加载服务
1 parent ca77c11 commit fd9c551

5 files changed

Lines changed: 628 additions & 138 deletions

File tree

ccw/src/core/dashboard-generator.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const MODULE_FILES = [
106106
'i18n.js', // Must be loaded first for translations
107107
'utils.js',
108108
'state.js',
109+
'services.js', // CacheManager, EventManager, PreloadService - must be before main.js
109110
'api.js',
110111
'components/theme.js',
111112
'components/modals.js',

ccw/src/templates/dashboard-js/main.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ document.addEventListener('DOMContentLoaded', async () => {
55
// Initialize Lucide icons (must be first to render SVG icons)
66
try { lucide.createIcons(); } catch (e) { console.error('Lucide icons init failed:', e); }
77

8+
// Initialize preload services (must be early to start data fetching)
9+
try { initPreloadServices(); } catch (e) { console.error('Preload services init failed:', e); }
10+
811
// Initialize i18n (must be early to translate static content)
912
try { initI18n(); } catch (e) { console.error('I18n init failed:', e); }
1013

@@ -90,3 +93,49 @@ document.addEventListener('DOMContentLoaded', async () => {
9093
}
9194
});
9295
});
96+
97+
/**
98+
* 初始化预加载服务
99+
* 创建缓存管理器、事件管理器和预加载服务,并注册数据源
100+
*/
101+
function initPreloadServices() {
102+
// 初始化服务实例
103+
window.cacheManager = new CacheManager('ccw.cache.');
104+
window.eventManager = new EventManager();
105+
window.preloadService = new PreloadService(window.cacheManager, window.eventManager);
106+
107+
// 注册高优先级数据源(页面进入时立即预加载)
108+
window.preloadService.register('dashboard-init',
109+
() => fetch('/api/codexlens/dashboard-init').then(r => r.ok ? r.json() : Promise.reject(r)),
110+
{ isHighPriority: true, ttl: 300000 } // 5分钟
111+
);
112+
113+
window.preloadService.register('workspace-status',
114+
() => {
115+
const path = encodeURIComponent(projectPath || '');
116+
return fetch('/api/codexlens/workspace-status?path=' + path).then(r => r.ok ? r.json() : Promise.reject(r));
117+
},
118+
{ isHighPriority: true, ttl: 120000 } // 2分钟
119+
);
120+
121+
window.preloadService.register('cli-status',
122+
() => fetch('/api/cli/status').then(r => r.ok ? r.json() : Promise.reject(r)),
123+
{ isHighPriority: true, ttl: 300000 } // 5分钟
124+
);
125+
126+
// 注册中优先级数据源
127+
window.preloadService.register('codexlens-models',
128+
() => fetch('/api/codexlens/models').then(r => r.ok ? r.json() : Promise.reject(r)),
129+
{ isHighPriority: false, ttl: 600000 } // 10分钟
130+
);
131+
132+
window.preloadService.register('cli-config',
133+
() => fetch('/api/cli/config').then(r => r.ok ? r.json() : Promise.reject(r)),
134+
{ isHighPriority: false, ttl: 300000 } // 5分钟
135+
);
136+
137+
// 立即触发高优先级预加载(静默后台执行)
138+
window.preloadService.runInitialPreload();
139+
140+
console.log('[Preload] Services initialized, high-priority preload started');
141+
}
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
// ========================================
2+
// Services Module
3+
// ========================================
4+
// 核心服务类:缓存管理、事件管理、预加载服务
5+
6+
// ========== CacheManager ==========
7+
/**
8+
* 基于 sessionStorage 的缓存管理器
9+
* 支持 TTL 过期机制
10+
*/
11+
class CacheManager {
12+
/**
13+
* 创建缓存管理器实例
14+
* @param {string} prefix - 缓存键前缀
15+
* @param {Storage} storage - 存储对象(默认 sessionStorage)
16+
*/
17+
constructor(prefix = 'ccw.cache.', storage = sessionStorage) {
18+
this.prefix = prefix;
19+
this.storage = storage;
20+
}
21+
22+
/**
23+
* 获取缓存数据
24+
* @param {string} key - 缓存键
25+
* @returns {*} 缓存数据,过期或不存在返回 null
26+
*/
27+
get(key) {
28+
try {
29+
const fullKey = this.prefix + key;
30+
const raw = this.storage.getItem(fullKey);
31+
if (!raw) return null;
32+
33+
const cached = JSON.parse(raw);
34+
// 检查是否过期
35+
if (cached.timestamp && Date.now() > cached.timestamp) {
36+
this.storage.removeItem(fullKey);
37+
return null;
38+
}
39+
40+
return cached.data;
41+
} catch (e) {
42+
console.error('[CacheManager] 获取缓存失败:', e);
43+
return null;
44+
}
45+
}
46+
47+
/**
48+
* 设置缓存数据
49+
* @param {string} key - 缓存键
50+
* @param {*} data - 要缓存的数据
51+
* @param {number} ttl - 过期时间(毫秒),默认 3 分钟
52+
*/
53+
set(key, data, ttl = 180000) {
54+
try {
55+
const fullKey = this.prefix + key;
56+
const cached = {
57+
data: data,
58+
timestamp: Date.now() + ttl
59+
};
60+
this.storage.setItem(fullKey, JSON.stringify(cached));
61+
} catch (e) {
62+
console.error('[CacheManager] 设置缓存失败:', e);
63+
}
64+
}
65+
66+
/**
67+
* 检查缓存是否有效
68+
* @param {string} key - 缓存键
69+
* @returns {boolean} 缓存是否存在且未过期
70+
*/
71+
isValid(key) {
72+
try {
73+
const fullKey = this.prefix + key;
74+
const raw = this.storage.getItem(fullKey);
75+
if (!raw) return false;
76+
77+
const cached = JSON.parse(raw);
78+
if (cached.timestamp && Date.now() > cached.timestamp) {
79+
return false;
80+
}
81+
return true;
82+
} catch (e) {
83+
return false;
84+
}
85+
}
86+
87+
/**
88+
* 使单个缓存失效
89+
* @param {string} key - 缓存键
90+
*/
91+
invalidate(key) {
92+
try {
93+
const fullKey = this.prefix + key;
94+
this.storage.removeItem(fullKey);
95+
} catch (e) {
96+
console.error('[CacheManager] 使缓存失效失败:', e);
97+
}
98+
}
99+
100+
/**
101+
* 清除所有带前缀的缓存
102+
*/
103+
invalidateAll() {
104+
try {
105+
const keysToRemove = [];
106+
for (let i = 0; i < this.storage.length; i++) {
107+
const key = this.storage.key(i);
108+
if (key && key.startsWith(this.prefix)) {
109+
keysToRemove.push(key);
110+
}
111+
}
112+
keysToRemove.forEach(key => this.storage.removeItem(key));
113+
} catch (e) {
114+
console.error('[CacheManager] 清除所有缓存失败:', e);
115+
}
116+
}
117+
}
118+
119+
120+
// ========== EventManager ==========
121+
/**
122+
* 简单的发布/订阅事件管理器
123+
*/
124+
class EventManager {
125+
/**
126+
* 创建事件管理器实例
127+
*/
128+
constructor() {
129+
this.events = {};
130+
}
131+
132+
/**
133+
* 订阅事件
134+
* @param {string} eventName - 事件名称
135+
* @param {Function} fn - 回调函数
136+
*/
137+
on(eventName, fn) {
138+
if (!this.events[eventName]) {
139+
this.events[eventName] = [];
140+
}
141+
this.events[eventName].push(fn);
142+
}
143+
144+
/**
145+
* 取消订阅事件
146+
* @param {string} eventName - 事件名称
147+
* @param {Function} fn - 要移除的回调函数
148+
*/
149+
off(eventName, fn) {
150+
if (!this.events[eventName]) return;
151+
this.events[eventName] = this.events[eventName].filter(f => f !== fn);
152+
}
153+
154+
/**
155+
* 触发事件
156+
* @param {string} eventName - 事件名称
157+
* @param {*} data - 传递给回调的数据
158+
*/
159+
emit(eventName, data) {
160+
if (!this.events[eventName]) return;
161+
this.events[eventName].forEach(fn => {
162+
try {
163+
fn(data);
164+
} catch (e) {
165+
console.error('[EventManager] 事件回调执行失败:', eventName, e);
166+
}
167+
});
168+
}
169+
}
170+
171+
172+
// ========== PreloadService ==========
173+
/**
174+
* 预加载服务
175+
* 管理数据源注册和预加载,防止重复请求
176+
*/
177+
class PreloadService {
178+
/**
179+
* 创建预加载服务实例
180+
* @param {CacheManager} cacheManager - 缓存管理器实例
181+
* @param {EventManager} eventManager - 事件管理器实例
182+
*/
183+
constructor(cacheManager, eventManager) {
184+
this.cacheManager = cacheManager;
185+
this.eventManager = eventManager;
186+
// 已注册的数据源
187+
this.sources = new Map();
188+
// 进行中的请求 Promise,防止重复请求
189+
this.inFlight = new Map();
190+
}
191+
192+
/**
193+
* 注册数据源
194+
* @param {string} key - 数据源标识
195+
* @param {Function} fetchFn - 获取数据的异步函数
196+
* @param {Object} options - 配置选项
197+
* @param {boolean} options.isHighPriority - 是否高优先级(初始预加载)
198+
* @param {number} options.ttl - 缓存 TTL(毫秒)
199+
*/
200+
register(key, fetchFn, options = {}) {
201+
this.sources.set(key, {
202+
fetchFn: fetchFn,
203+
isHighPriority: options.isHighPriority || false,
204+
ttl: options.ttl || 180000
205+
});
206+
}
207+
208+
/**
209+
* 预加载指定数据源
210+
* @param {string} key - 数据源标识
211+
* @param {Object} options - 预加载选项
212+
* @param {boolean} options.force - 是否强制刷新(忽略缓存)
213+
* @returns {Promise<*>} 预加载的数据
214+
*/
215+
async preload(key, options = {}) {
216+
const source = this.sources.get(key);
217+
if (!source) {
218+
console.warn('[PreloadService] 未找到数据源:', key);
219+
return null;
220+
}
221+
222+
// 检查缓存(非强制刷新时)
223+
if (!options.force && this.cacheManager.isValid(key)) {
224+
return this.cacheManager.get(key);
225+
}
226+
227+
// 检查是否有进行中的请求
228+
if (this.inFlight.has(key)) {
229+
return this.inFlight.get(key);
230+
}
231+
232+
// 创建新请求
233+
const requestPromise = this._doFetch(key, source);
234+
this.inFlight.set(key, requestPromise);
235+
236+
try {
237+
const result = await requestPromise;
238+
return result;
239+
} finally {
240+
// 请求完成后移除
241+
this.inFlight.delete(key);
242+
}
243+
}
244+
245+
/**
246+
* 执行实际的数据获取
247+
* @private
248+
*/
249+
async _doFetch(key, source) {
250+
try {
251+
const data = await source.fetchFn();
252+
// 存入缓存
253+
this.cacheManager.set(key, data, source.ttl);
254+
// 发出更新事件
255+
this.eventManager.emit('data:updated:' + key, data);
256+
this.eventManager.emit('data:updated', { key: key, data: data });
257+
return data;
258+
} catch (e) {
259+
console.error('[PreloadService] 预加载失败:', key, e);
260+
// 发出错误事件
261+
this.eventManager.emit('data:error:' + key, e);
262+
throw e;
263+
}
264+
}
265+
266+
/**
267+
* 运行所有高优先级预加载
268+
* @returns {Promise<void>}
269+
*/
270+
async runInitialPreload() {
271+
const highPriorityKeys = [];
272+
this.sources.forEach((source, key) => {
273+
if (source.isHighPriority) {
274+
highPriorityKeys.push(key);
275+
}
276+
});
277+
278+
// 并行执行所有高优先级预加载
279+
await Promise.allSettled(
280+
highPriorityKeys.map(key => this.preload(key))
281+
);
282+
}
283+
}
284+
285+
286+
// ========== 导出到 window ==========
287+
window.CacheManager = CacheManager;
288+
window.EventManager = EventManager;
289+
window.PreloadService = PreloadService;

0 commit comments

Comments
 (0)