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
JavaScript
/**
* 模板仓储具体实现
*/
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');
}
}