UNPKG

fu-orm-code-generator

Version:

A Model Context Protocol (MCP) server for MyBatis code generation with enterprise-grade layered architecture

233 lines (193 loc) 6.94 kB
/** * 模板仓储具体实现 */ import { join } from 'node:path'; import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import { TemplateRepository } from '../../domain/repositories/TemplateRepository.js'; import { Template } from '../../domain/entities/Template.js'; import { config } from '../../config/index.js'; import { createLogger } from '../logging/Logger.js'; import { FileSystemError, NetworkError, NotFoundError, BusinessError } from '../../common/errors/index.js'; const execAsync = promisify(exec); const logger = createLogger('TemplateRepositoryImpl'); export class TemplateRepositoryImpl extends TemplateRepository { constructor(httpClient, fileSystem) { super(); this.httpClient = httpClient; this.fileSystem = fileSystem; this.templatesPath = config.paths.templates; } async getOnlineTemplates() { try { const data = await this.httpClient.get('template_list'); if (!data.data || !Array.isArray(data.data) || data.data.length === 0) { throw new BusinessError('TEMPLATE_LIST_EMPTY', '暂无可用模板'); } return data.data.map(templateData => Template.fromApiResponse(templateData)); } catch (error) { if (error instanceof BusinessError) { throw error; } throw new NetworkError(`获取在线模板列表失败: ${error.message}`); } } async getLocalTemplates() { try { await this.fileSystem.ensureDir(this.templatesPath); if (!await this.fileSystem.exists(this.templatesPath)) { return []; } const entries = await this.fileSystem.readDir(this.templatesPath, { withFileTypes: true, filter: entry => entry.isDirectory() && !entry.name.endsWith('_temp') }); const templates = []; for (const entry of entries) { const templatePath = join(this.templatesPath, entry.name); const stats = await this.fileSystem.getStats(templatePath); templates.push(new Template({ name: entry.name, description: '', isLocal: true, path: templatePath, size: stats.size, createdAt: stats.birthtime, updatedAt: stats.mtime })); } return templates; } catch (error) { throw new FileSystemError('readLocalTemplates', this.templatesPath, error); } } async getLocalTemplateByName(name) { const templates = await this.getLocalTemplates(); return templates.find(t => t.name === name) || null; } async templateExists(name) { const templatePath = join(this.templatesPath, name); return await this.fileSystem.exists(templatePath); } async downloadTemplate(name, downloadUrl, options = {}) { try { if (await this.templateExists(name)) { throw new BusinessError('TEMPLATE_ALREADY_EXISTS', `模板 "${name}" 已存在,无需重复下载`); } await this.fileSystem.ensureDir(this.templatesPath); const tempZipPath = this.fileSystem.createTempPath(`${name}_download`, '.zip'); const templateDir = join(this.templatesPath, name); // 下载文件 const buffer = await this.httpClient.download(downloadUrl, options); await this.fileSystem.writeFile(tempZipPath, new Uint8Array(buffer), { overwrite: true }); // 创建模板目录并移动zip文件 await this.fileSystem.ensureDir(templateDir); const finalZipPath = join(templateDir, 'template.zip'); await this.fileSystem.moveFile(tempZipPath, finalZipPath); // 解压文件 await this.fileSystem.extractZip(finalZipPath, templateDir); // 删除zip文件 await this.fileSystem.deleteFile(finalZipPath); const stats = await this.fileSystem.getStats(templateDir); return new Template({ name, isLocal: true, path: templateDir, size: stats.size, createdAt: stats.birthtime, updatedAt: stats.mtime }); } catch (error) { // 清理失败的下载 try { const templateDir = join(this.templatesPath, name); if (await this.fileSystem.exists(templateDir)) { await this.fileSystem.deleteDir(templateDir); } } catch (cleanupError) { logger.warn('Failed to cleanup failed download', { error: cleanupError.message }); } if (error instanceof BusinessError) { throw error; } throw new FileSystemError('downloadTemplate', name, error); } } async deleteLocalTemplate(name) { try { const templatePath = join(this.templatesPath, name); if (!await this.fileSystem.exists(templatePath)) { throw new NotFoundError('Template', name); } await this.fileSystem.deleteDir(templatePath); return true; } catch (error) { if (error instanceof NotFoundError) { throw error; } throw new FileSystemError('deleteTemplate', name, error); } } async getTemplateStats() { try { const templates = await this.getLocalTemplates(); return { totalTemplates: templates.length, templatesPath: this.templatesPath, totalSize: templates.reduce((sum, t) => sum + t.size, 0), lastUpdated: templates.length > 0 ? Math.max(...templates.map(t => t.updatedAt.getTime())) : null }; } catch (error) { throw new FileSystemError('getTemplateStats', this.templatesPath, error); } } async cleanupTempFiles(maxAge = 24 * 60 * 60 * 1000) { try { await this.fileSystem.cleanupTempFiles(maxAge); return 0; // FileSystem.cleanupTempFiles doesn't return count } catch (error) { logger.warn('Cleanup temp files failed', { error: error.message }); return 0; } } async validateTemplateIntegrity(name) { try { const templatePath = join(this.templatesPath, name); if (!await this.fileSystem.exists(templatePath)) { return false; } // 简单验证:检查是否包含基本文件 const files = await this.fileSystem.readDir(templatePath); return files.length > 0; } catch (error) { return false; } } async getTemplateFiles(name) { try { const templatePath = join(this.templatesPath, name); if (!await this.fileSystem.exists(templatePath)) { throw new NotFoundError('Template', name); } return await this.fileSystem.readDir(templatePath); } catch (error) { if (error instanceof NotFoundError) { throw error; } throw new FileSystemError('getTemplateFiles', name, error); } } async backupTemplate(name, backupLocation) { throw new Error('Backup functionality not implemented yet'); } async restoreTemplate(backupPath) { throw new Error('Restore functionality not implemented yet'); } }