gitlab-activity-mcp
Version:
GitLab Activity MCP Server - Generate professional activity reports and analysis
226 lines (225 loc) • 7.68 kB
JavaScript
import { join } from 'path';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { logger } from 'mcp-framework';
import { Low } from 'lowdb';
import { JSONFile } from 'lowdb/node';
/**
* 缓存服务,用于缓存 GitLab API 响应数据
*/
class CacheService {
db;
cacheDuration; // 缓存持续时间(毫秒)
currentAccessToken;
constructor(cachePath, cacheDurationHours = 24) {
const defaultPath = join(process.cwd(), 'cache', 'gitlab-cache.json');
const dbPath = cachePath || process.env.GITLAB_CACHE_PATH || defaultPath;
// 创建 cache 目录&文件
const cacheDir = join(process.cwd(), 'cache');
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir);
}
if (!existsSync(dbPath)) {
writeFileSync(dbPath, '{}');
}
// 创建数据库适配器
const adapter = new JSONFile(dbPath);
this.db = new Low(adapter, { users: {}, projects: {} });
this.cacheDuration = cacheDurationHours * 60 * 60 * 1000; // 转换为毫秒
this.currentAccessToken = process.env.GITLAB_ACCESS_TOKEN || '';
// 初始化数据库
logger.info(`[CacheService] 初始化数据库 ${dbPath}`);
this.initializeDatabase();
}
/**
* 初始化数据库
*/
async initializeDatabase() {
try {
await this.db.read();
// 如果数据库为空,设置默认数据
if (!this.db.data) {
this.db.data = { users: {}, projects: {}, accessToken: this.currentAccessToken };
await this.db.write();
}
else {
// 检查 access token 是否变化,如果变化则清空缓存
await this.checkAndClearCacheOnTokenChange();
}
}
catch (error) {
logger.warn(`Cache database initialization warning: ${String(error)}`);
// 如果读取失败,创建默认数据
this.db.data = { users: {}, projects: {}, accessToken: this.currentAccessToken };
}
}
/**
* 检查 access token 是否变化,如果变化则清空缓存
*/
async checkAndClearCacheOnTokenChange() {
try {
if (this.db.data?.accessToken && this.db.data.accessToken !== this.currentAccessToken) {
logger.info(`Access token changed, clearing all cache, oldAccessToken: ${this.db.data.accessToken}, currentAccessToken: ${this.currentAccessToken}`);
this.db.data = { users: {}, projects: {}, accessToken: this.currentAccessToken };
await this.db.write();
}
else if (!this.db.data?.accessToken) {
// 如果没有记录 access token,更新它
if (this.db.data) {
this.db.data.accessToken = this.currentAccessToken;
await this.db.write();
}
}
}
catch (error) {
logger.warn(`Failed to check access token change: ${String(error)}`);
}
}
/**
* 检查缓存是否有效
*/
isValidCache(timestamp) {
return Date.now() - timestamp < this.cacheDuration;
}
/**
* 获取用户缓存
*/
async getUser(userId) {
try {
await this.db.read();
const userCache = this.db.data?.users[userId];
if (userCache && this.isValidCache(userCache.timestamp)) {
logger.info(`Cache hit for user: ${userId}`);
return userCache.data;
}
return null;
}
catch (error) {
logger.warn(`Failed to read user cache: ${String(error)}`);
return null;
}
}
/**
* 设置用户缓存
*/
async setUser(userId, data) {
try {
await this.db.read();
if (!this.db.data) {
this.db.data = { users: {}, projects: {} };
}
this.db.data.users[userId] = {
data,
timestamp: Date.now(),
};
await this.db.write();
logger.info(`Cache set for user: ${userId}`);
}
catch (error) {
logger.warn(`Failed to write user cache: ${String(error)}`);
}
}
/**
* 获取项目缓存
*/
async getProject(projectId) {
try {
await this.db.read();
const projectCache = this.db.data?.projects[projectId];
if (projectCache && this.isValidCache(projectCache.timestamp)) {
logger.info(`Cache hit for project: ${projectId}`);
return projectCache.data;
}
return null;
}
catch (error) {
logger.warn(`Failed to read project cache: ${String(error)}`);
return null;
}
}
/**
* 设置项目缓存
*/
async setProject(projectId, data) {
try {
await this.db.read();
if (!this.db.data) {
this.db.data = { users: {}, projects: {} };
}
this.db.data.projects[projectId] = {
data,
timestamp: Date.now(),
};
await this.db.write();
logger.info(`Cache set for project: ${projectId}`);
}
catch (error) {
logger.warn(`Failed to write project cache: ${String(error)}`);
}
}
/**
* 清除过期缓存
*/
async clearExpiredCache() {
try {
await this.db.read();
if (!this.db.data)
return;
const now = Date.now();
let hasChanges = false;
// 清除过期用户缓存
for (const [userId, userCache] of Object.entries(this.db.data.users)) {
if (!this.isValidCache(userCache.timestamp)) {
delete this.db.data.users[userId];
hasChanges = true;
}
}
// 清除过期项目缓存
for (const [projectId, projectCache] of Object.entries(this.db.data.projects)) {
if (!this.isValidCache(projectCache.timestamp)) {
delete this.db.data.projects[projectId];
hasChanges = true;
}
}
if (hasChanges) {
await this.db.write();
logger.info('Expired cache entries cleared');
}
}
catch (error) {
logger.warn(`Failed to clear expired cache: ${String(error)}`);
}
}
/**
* 清除所有缓存
*/
async clearAllCache() {
try {
this.db.data = { users: {}, projects: {}, accessToken: this.currentAccessToken };
await this.db.write();
logger.info('All cache cleared');
}
catch (error) {
logger.warn(`Failed to clear all cache: ${String(error)}`);
}
}
/**
* 获取缓存统计信息
*/
async getCacheStats() {
try {
await this.db.read();
if (!this.db.data) {
return { userCount: 0, projectCount: 0 };
}
return {
userCount: Object.keys(this.db.data.users).length,
projectCount: Object.keys(this.db.data.projects).length,
};
}
catch (error) {
logger.warn(`Failed to get cache stats: ${String(error)}`);
return { userCount: 0, projectCount: 0 };
}
}
}
export const cacheService = new CacheService();