import { StandardDataSource } from './standard'; import { Config, getTemplate, DataSourceConfig, hasChinese } from './utils'; import * as fs from 'fs-extra'; import * as path from 'path'; import { diff, Model } from './diff'; import { CodeGenerator, FilesManager } from './generators/generate'; import { info as debugInfo } from './debugLog'; import { FileStructures } from './generators/generate'; import { readRemoteDataSource } from './scripts'; import * as _ from 'lodash'; import { DsManager } from './DsManager'; process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0 as any; export class Manager { readonly lockFilename = 'api-lock.json'; allCurrentDataSources: StandardDataSource[] = []; currentDataSource: StandardDataSource; allConfigs: DataSourceConfig[]; remoteDataSource: StandardDataSource; currConfig: DataSourceConfig; fileManager: FilesManager; diffs = { modDiffs: [] as Model[], boDiffs: [] as Model[] }; report = debugInfo; setReport(report: typeof debugInfo) { this.report = report; if (this.fileManager) { this.fileManager.report = report; } } mapModel(model: T): Model { return Object.assign({}, model, { details: [] }) as any; } async selectDataSource(name: string) { this.currConfig = this.allConfigs.find(conf => conf.name === name); // 不再享受自动服务,sorry } makeCurrentAllSame() { if (this.allConfigs.length <= 1) { // Compatible with single origin without origin name this.allCurrentDataSources[0] = this.remoteDataSource; } else { const remoteName = this.remoteDataSource.name; const remoteDsIndex = this.allCurrentDataSources.findIndex(ds => ds.name === remoteName); if (remoteDsIndex === -1) { this.allCurrentDataSources.push(this.remoteDataSource); } else { this.allCurrentDataSources[remoteDsIndex] = this.remoteDataSource; } } this.currentDataSource = this.remoteDataSource; } makeCurrentSameMod(modName: string) { const isRemoteModExists = this.remoteDataSource.mods.find(iMod => iMod.name === modName); const isLocalModExists = this.currentDataSource.mods.find(iMod => iMod.name === modName); if (!isRemoteModExists) { // 删除模块 this.currentDataSource.mods = this.currentDataSource.mods.filter(mod => mod.name !== modName); return; } const remoteMod = this.remoteDataSource.mods.find(iMod => iMod.name === modName); if (isLocalModExists) { // 模块已存在。更新该模块 const index = this.currentDataSource.mods.findIndex(iMod => iMod.name === modName); this.currentDataSource.mods[index] = remoteMod; } else { // 模块不存在。创建该模块 this.currentDataSource.mods.push(remoteMod); this.currentDataSource.reOrder(); } } makeCurrentSameBase(baseName: string) { const isRemoteExists = this.remoteDataSource.baseClasses.find(base => base.name === baseName); const isLocalExists = this.currentDataSource.baseClasses.find(base => base.name === baseName); if (!isRemoteExists) { // 删除基类 this.currentDataSource.baseClasses = this.currentDataSource.baseClasses.filter( base => base.name !== baseName ); return; } const remoteBase = this.remoteDataSource.baseClasses.find(base => base.name === baseName); if (isLocalExists) { // 基类已存在, 更新该基类 const index = this.currentDataSource.baseClasses.findIndex(base => base.name === baseName); this.currentDataSource.baseClasses[index] = remoteBase; } else { // 基类不存在, 创建该基类 this.currentDataSource.baseClasses.push(remoteBase); this.currentDataSource.reOrder(); } } genTempCurrentFile() { DsManager.genTempCurrentFile(this.currConfig.outDir, this.allCurrentDataSources); } calDiffs() { const modDiffs = diff( this.currentDataSource.mods.map(this.mapModel), this.remoteDataSource.mods.map(this.mapModel) ); const boDiffs = diff( this.currentDataSource.baseClasses.map(this.mapModel), this.remoteDataSource.baseClasses.map(this.mapModel), false ); this.diffs = { modDiffs, boDiffs }; } constructor(private projectRoot: string, config: Config, configDir = process.cwd()) { this.allConfigs = config.getDataSourcesConfig(configDir); this.currConfig = this.allConfigs[0]; } pollingId = null; private polling(currConfig: DataSourceConfig) { this.pollingId = setTimeout(() => { this.readRemoteDataSource(currConfig); this.polling(currConfig); }, currConfig.pollingTime * 1000); } beginPolling(currConfig = this.currConfig) { if (this.pollingId) { clearTimeout(this.pollingId); } this.polling(currConfig); } stopPolling() { if (this.pollingId) { clearTimeout(this.pollingId); this.pollingId = null; } } // 简化要做的事情,聚焦 // 1、赋值 currConfig // 2、是否做 allCurrentDataSources、currentDataSource async ready() { this.currConfig = this.allConfigs[0]; } existsLocal() { return ( fs.existsSync(path.join(this.currConfig.outDir, this.lockFilename)) || fs.existsSync(path.join(this.currConfig.outDir, 'api.lock')) ); } async readLockFile(fileName?: string): Promise { let lockFile = path.join(this.currConfig.outDir, `${fileName || 'api-lock'}.json`); try { const localDataStr = await fs.readFile(lockFile, { encoding: 'utf8' }); return localDataStr; } catch (error) { return ''; } } async getCurrentDataSource (fileName?: string) { const localDataStr = await this.readLockFile(fileName); if (!localDataStr) { this.report('读取lock文件失败'); } const localDataObjects = JSON.parse(localDataStr) as StandardDataSource[]; this.allCurrentDataSources = localDataObjects.map(ldo => { return StandardDataSource.constructorFromLock(ldo, ldo.name); }); // Filter name changed origin this.allCurrentDataSources = this.allCurrentDataSources.filter(ldo => { return Boolean(this.allConfigs.find(config => config.name === ldo.name)); }); } // 暂时不支持api多处存放 getLockContent () { // 不存在 const root = this.currConfig.outDir; if (!fs.existsSync(root)) { this.report('指定输出目录不存在!'); return } const allCurrentDataSources = []; // 蠢逼方法 const collect = () => { const originDirs = fs.readdirSync(root); originDirs.forEach(originItem => { originItem = path.join(root, originItem); const stat = fs.lstatSync(originItem); if (!stat.isDirectory()) return; const originLock = JSON.parse(fs.readFileSync(path.join(originItem, 'api-lock.json'), { encoding: 'utf8' })); // 读取mods文件 const modDirs = fs.readdirSync(path.join(originItem, 'mods')); modDirs.forEach(modItem => { modItem = path.join(`${originItem}/mods`, modItem); const stat = fs.lstatSync(modItem); if (!stat.isDirectory()) return; const modLock = JSON.parse(fs.readFileSync(path.join(modItem, 'api-lock.json'), { encoding: 'utf8' })); const interDirs = fs.readdirSync(modItem); interDirs.forEach(interItem => { interItem = path.join(modItem, interItem); const stat = fs.lstatSync(interItem); if (!stat.isDirectory()) return; const interLock = JSON.parse(fs.readFileSync(path.join(interItem, 'api-lock.json'), { encoding: 'utf8' })); originLock.baseClasses.push(...interLock.baseClasses); delete interLock['baseClasses']; modLock.interfaces.push(interLock); }) originLock.baseClasses.push(...modLock.baseClasses); delete modLock['baseClasses']; originLock.mods.push(modLock); }) allCurrentDataSources.push(originLock); }) } collect(); return allCurrentDataSources.map(item => { return { mods: _.orderBy(item.mods, 'name'), name: item.name, baseClasses: _.orderBy(item.baseClasses, 'name') } }) as StandardDataSource[]; } async readCurrentDataSource() { try { this.report('读取本地数据中...'); this.allCurrentDataSources = this.getLockContent().map(ldo => { return StandardDataSource.constructorFromLock(ldo, ldo.name); }); this.report('读取本地完成'); if (this.allCurrentDataSources.length < this.allConfigs.length) { this.allConfigs.forEach(config => { if (!this.allCurrentDataSources.find(ds => ds.name === config.name)) { this.allCurrentDataSources.push( new StandardDataSource({ mods: [], name: config.name, baseClasses: [] }) ); } }); } this.currentDataSource = this.allCurrentDataSources[0]; if (this.currConfig.name && this.allCurrentDataSources.length > 1) { this.currentDataSource = this.allCurrentDataSources.find(ds => ds.name === this.currConfig.name) || new StandardDataSource({ mods: [], name: this.currConfig.name, baseClasses: [] }); } this.report('本地对象创建成功'); } catch (e) { throw new Error('读取 lock 文件错误!' + e.toString()); } } checkDataSource(dataSource: StandardDataSource) { const { mods, baseClasses } = dataSource; const errorModNames = [] as string[]; const errorBaseNames = [] as string[]; mods.forEach(mod => { if (hasChinese(mod.name)) { errorModNames.push(mod.name); } }); baseClasses.forEach(base => { if (hasChinese(base.name)) { errorBaseNames.push(base.name); } }); if (errorBaseNames.length && errorModNames.length) { const errMsg = ['当前数据源有如下项不符合规范,需要后端修改']; errorModNames.forEach(modName => errMsg.push(`模块名${modName}应该改为英文名!`)); errorBaseNames.forEach(baseName => errMsg.push(`基类名${baseName}应该改为英文名!`)); throw new Error(errMsg.join('\n')); } } async syncRemote () { // 生成current文件 if (!fs.existsSync(this.currConfig.outDir)) { // 扫描目录生成,没有数据(暂时使用根目录下有无api-current文件的判断) const promises = this.allConfigs.map(config => { return readRemoteDataSource(config, this.report); }); this.allCurrentDataSources = await Promise.all(promises); this.currentDataSource = this.allCurrentDataSources[0]; this.remoteDataSource = this.currentDataSource; } else { // 存在则使用current文件数据作为 await this.readCurrentDataSource(); await this.readRemoteDataSource(); } DsManager.genTempRemoteFile(this.currConfig.outDir, this.remoteDataSource); DsManager.genTempCurrentFile(this.currConfig.outDir, this.allCurrentDataSources); } async readRemoteDataSource(config = this.currConfig) { const remoteDataSource = await readRemoteDataSource(config, this.report); this.remoteDataSource = remoteDataSource; if (!this.currentDataSource) { this.currentDataSource = remoteDataSource; return remoteDataSource; } return remoteDataSource; } async lock() { await this.fileManager.saveLock(); } dispatch(files: {}) { return _.mapValues(files, (value: Function | {}) => { if (typeof value === 'function') { return value(); } if (typeof value === 'object') { return this.dispatch(value); } return value; }); } async getGeneratedFiles() { await this.setFilesManager(); const files = this.fileManager.fileStructures.getFileStructures(); try { return this.dispatch(files); } catch (err) { return {}; } } async update(oldFiles: {}) { const files = this.getGeneratedFiles(); try { await this.fileManager.regenerate(files, oldFiles); } catch (e) { console.log(e.stack); throw new Error(e); } } async regenerateFiles() { const files = await this.getGeneratedFiles(); // 覆写卡顿,改用删除并重新生成 // const originDirs = fs.readdirSync(this.currConfig.outDir); // originDirs.forEach(originItem => { // originItem = path.join(this.currConfig.outDir, originItem); // const stat = fs.lstatSync(originItem); // if (stat.isDirectory()) { // fs.rmdirSync(originItem, { recursive: true }); // } // }); // this.report('删除原文件成功;'); await this.fileManager.regenerate(files); // 删除current、remote fs.removeSync(path.join(this.currConfig.outDir, 'api-current.json')); fs.removeSync(path.join(this.currConfig.outDir, 'api-remote.json')); } async setFilesManager() { this.report('文件生成器创建中...'); const { default: Generator, FileStructures: MyFileStructures } = getTemplate( this.currConfig.templatePath, this.currConfig.templateType ); if (fs.existsSync(path.join(this.currConfig.outDir, 'api-current.json'))) { await this.getCurrentDataSource('api-current'); } else { // 合并时,无需做更新,lock文件的更新反馈 this.allCurrentDataSources = this.getLockContent().map(ldo => { return StandardDataSource.constructorFromLock(ldo, ldo.name); }); DsManager.genTempCurrentFile(this.currConfig.outDir, this.allCurrentDataSources); } this.report(`generator开始初始化。。。`); const generators = this.allCurrentDataSources.map(dataSource => { const config = this.getConfigByDataSourceName(dataSource.name); const generator: CodeGenerator = new Generator(this.currConfig.surrounding, config?.outDir); generator.setDataSource(dataSource); this.report(`${dataSource.name}数据源设置成功`); generator.setBaseClasses(dataSource); this.report(`${dataSource.name}baseClasses设置成功`); generator.usingMultipleOrigins = this.currConfig.usingMultipleOrigins; if (_.isFunction(generator.getDataSourceCallback)) { generator.getDataSourceCallback(dataSource); } return generator; }); let FileStructuresClazz = FileStructures as any; if (MyFileStructures) { FileStructuresClazz = MyFileStructures; } this.fileManager = new FilesManager( new FileStructuresClazz( generators, this.currConfig.usingMultipleOrigins, this.currConfig.surrounding, this.currConfig.outDir, this.currConfig.templateType ), this.currConfig.outDir ); this.fileManager.prettierConfig = this.currConfig.prettierConfig; this.report('文件生成器创建成功!'); this.fileManager.report = this.report; } /** 获取报表数据 */ getReportData() { const currProj = { originUrl: this.currConfig.originUrl, projectName: this.projectRoot } as any; return DsManager.getReportData(currProj); } /** 获取当前dataSource对应的config */ getConfigByDataSourceName(name: string) { if (name) { return this.allConfigs.find(config => config.name === name) || this.currConfig; } // 没有name时,表示是单数据源 return this.currConfig; } /** 打开接口变更报表 */ openReport() { const currProj = { originUrl: this.currConfig.originUrl, projectName: this.projectRoot } as any; DsManager.openReport(currProj); } }