- ✅ 成功运行迁移工具,生成28个模块的完整NestJS代码 - ✅ 生成所有实体、服务、控制器、验证器等组件 - ✅ 修复npm依赖冲突,更新package-lock.json - ✅ 添加Docker测试脚本和配置文件 - ✅ 完善迁移工具的调试日志和错误处理 - 🔧 包含增量更新工具和质量检查工具 - 📊 迁移统计:28个模块,数千个文件,耗时26.47秒 主要变更: - wwjcloud-nest/src/core/* - 生成的业务模块代码 - tools/* - 迁移工具和辅助脚本 - wwjcloud-nest/package.json - 依赖更新 - docker/* - 容器化配置和测试脚本
459 lines
12 KiB
TypeScript
459 lines
12 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
||
import { ConfigService } from '@nestjs/config';
|
||
import { promises as fs } from 'fs';
|
||
import * as path from 'path';
|
||
import { ILanguageService } from './language.interface';
|
||
|
||
/**
|
||
* 语言服务
|
||
* 基于 NestJS 实现模块化多语言支持
|
||
* 符合NestJS规范的服务层实现
|
||
* 对应PHP: think\Lang 类
|
||
* 对应Java: MessageSource 实现
|
||
*/
|
||
@Injectable()
|
||
export class LanguageService implements ILanguageService {
|
||
private readonly logger = new Logger(LanguageService.name);
|
||
private readonly languageCache = new Map<string, Map<string, any>>();
|
||
private readonly moduleCache = new Map<string, Set<string>>(); // 记录已加载的模块
|
||
|
||
constructor(private readonly configService: ConfigService) {
|
||
this.initializeLanguagePacks();
|
||
}
|
||
|
||
/**
|
||
* 获取消息
|
||
* @param key 消息键
|
||
* @param args 参数
|
||
* @param module 模块名
|
||
* @param type 类型 (api|dict|validate)
|
||
* @param language 语言
|
||
* @returns 消息内容
|
||
*/
|
||
async getMessage(
|
||
key: string,
|
||
args?: any,
|
||
module: string = 'common',
|
||
type: string = 'api',
|
||
language?: string,
|
||
): Promise<string> {
|
||
try {
|
||
const currentLanguage = language || this.getCurrentLanguage();
|
||
const cacheKey = `${type}.${module}.${key}`;
|
||
|
||
// 确保模块已加载
|
||
await this.ensureModuleLoaded(module, type, currentLanguage);
|
||
|
||
const message = this.getMessageFromCache(cacheKey, currentLanguage);
|
||
|
||
if (message && message !== key) {
|
||
// 支持参数替换
|
||
if (args && typeof args === 'object') {
|
||
return this.replaceMessageParams(message, args);
|
||
}
|
||
return message;
|
||
}
|
||
|
||
this.logger.warn(
|
||
`未找到消息: ${key} (模块: ${module}, 类型: ${type}, 语言: ${currentLanguage})`,
|
||
);
|
||
return key;
|
||
} catch (error) {
|
||
this.logger.warn(`获取消息失败: ${key}`, error);
|
||
return key;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取消息(实例方法)
|
||
* @param key 消息键
|
||
* @param args 参数
|
||
* @param module 模块名
|
||
* @param type 类型
|
||
* @param language 语言
|
||
* @returns 消息内容
|
||
*/
|
||
async get(
|
||
key: string,
|
||
args?: any,
|
||
module: string = 'common',
|
||
type: string = 'api',
|
||
language?: string,
|
||
): Promise<string> {
|
||
return await this.getMessage(key, args, module, type, language);
|
||
}
|
||
|
||
/**
|
||
* 初始化语言包
|
||
* 只加载通用语言包,其他模块按需加载
|
||
*/
|
||
private async initializeLanguagePacks(): Promise<void> {
|
||
try {
|
||
const supportedLanguages = this.getSupportedLanguages();
|
||
for (const language of supportedLanguages) {
|
||
// 只加载通用模块
|
||
await this.loadModuleLanguagePack('common', 'api', language);
|
||
await this.loadModuleLanguagePack('common', 'dict', language);
|
||
await this.loadModuleLanguagePack('common', 'validate', language);
|
||
}
|
||
this.logger.log('通用语言包初始化完成');
|
||
} catch (error) {
|
||
this.logger.error('语言包初始化失败', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 确保模块已加载
|
||
* @param module 模块名
|
||
* @param type 类型
|
||
* @param language 语言
|
||
*/
|
||
private async ensureModuleLoaded(
|
||
module: string,
|
||
type: string,
|
||
language: string,
|
||
): Promise<void> {
|
||
const moduleKey = `${module}.${type}`;
|
||
const languageKey = `${language}.${moduleKey}`;
|
||
|
||
if (
|
||
!this.moduleCache.has(language) ||
|
||
!this.moduleCache.get(language)!.has(moduleKey)
|
||
) {
|
||
await this.loadModuleLanguagePack(module, type, language);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载模块语言包
|
||
* @param module 模块名
|
||
* @param type 类型 (api|dict|validate)
|
||
* @param language 语言
|
||
*/
|
||
private async loadModuleLanguagePack(
|
||
module: string,
|
||
type: string,
|
||
language: string,
|
||
): Promise<void> {
|
||
try {
|
||
const langDir = path.join(process.cwd(), 'src', 'lang', language);
|
||
const languagePack = new Map<string, any>();
|
||
|
||
// 1. 加载通用语言包
|
||
if (module !== 'common') {
|
||
await this.loadCommonLanguagePack(langDir, type, languagePack);
|
||
}
|
||
|
||
// 2. 加载模块语言包
|
||
await this.loadModuleSpecificPack(langDir, module, type, languagePack);
|
||
|
||
// 3. 加载插件语言包
|
||
await this.loadAddonLanguagePacks(langDir, type, languagePack);
|
||
|
||
// 4. 缓存语言包
|
||
if (!this.languageCache.has(language)) {
|
||
this.languageCache.set(language, new Map());
|
||
}
|
||
|
||
const languageCache = this.languageCache.get(language)!;
|
||
for (const [key, value] of languagePack) {
|
||
languageCache.set(key, value);
|
||
}
|
||
|
||
// 5. 记录已加载的模块
|
||
if (!this.moduleCache.has(language)) {
|
||
this.moduleCache.set(language, new Set());
|
||
}
|
||
this.moduleCache.get(language)!.add(`${module}.${type}`);
|
||
|
||
this.logger.log(`模块语言包加载完成: ${module}.${type} (${language})`);
|
||
} catch (error) {
|
||
this.logger.error(
|
||
`加载模块语言包失败: ${module}.${type} (${language})`,
|
||
error,
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载通用语言包
|
||
*/
|
||
private async loadCommonLanguagePack(
|
||
langDir: string,
|
||
type: string,
|
||
languagePack: Map<string, any>,
|
||
): Promise<void> {
|
||
const commonDir = path.join(langDir, 'common');
|
||
const filePath = path.join(commonDir, `${type}.json`);
|
||
|
||
try {
|
||
const content = await fs.readFile(filePath, 'utf8');
|
||
const data = JSON.parse(content);
|
||
|
||
// 合并到语言包,添加前缀
|
||
for (const [key, value] of Object.entries(data)) {
|
||
languagePack.set(`${type}.common.${key}`, value);
|
||
}
|
||
} catch (error) {
|
||
this.logger.warn(`加载通用语言包失败: ${type}`, error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载模块特定语言包
|
||
*/
|
||
private async loadModuleSpecificPack(
|
||
langDir: string,
|
||
module: string,
|
||
type: string,
|
||
languagePack: Map<string, any>,
|
||
): Promise<void> {
|
||
const moduleDir = path.join(langDir, module);
|
||
const filePath = path.join(moduleDir, `${type}.json`);
|
||
|
||
try {
|
||
const content = await fs.readFile(filePath, 'utf8');
|
||
const data = JSON.parse(content);
|
||
|
||
// 合并到语言包,添加前缀
|
||
for (const [key, value] of Object.entries(data)) {
|
||
languagePack.set(`${type}.${module}.${key}`, value);
|
||
}
|
||
} catch (error) {
|
||
this.logger.warn(`加载模块语言包失败: ${module}.${type}`, error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载插件语言包
|
||
*/
|
||
private async loadAddonLanguagePacks(
|
||
langDir: string,
|
||
type: string,
|
||
languagePack: Map<string, any>,
|
||
): Promise<void> {
|
||
const addonsDir = path.join(langDir, 'addons');
|
||
|
||
try {
|
||
const addonDirs = await fs.readdir(addonsDir);
|
||
for (const addonDir of addonDirs) {
|
||
const addonPath = path.join(addonsDir, addonDir);
|
||
const stat = await fs.stat(addonPath);
|
||
|
||
if (stat.isDirectory()) {
|
||
const filePath = path.join(addonPath, `${type}.json`);
|
||
try {
|
||
const content = await fs.readFile(filePath, 'utf8');
|
||
const data = JSON.parse(content);
|
||
|
||
// 合并到语言包,添加前缀
|
||
for (const [key, value] of Object.entries(data)) {
|
||
languagePack.set(`${type}.addon.${addonDir}.${key}`, value);
|
||
}
|
||
} catch (error) {
|
||
this.logger.warn(`加载插件语言包失败: ${addonDir}.${type}`, error);
|
||
}
|
||
}
|
||
}
|
||
} catch (error) {
|
||
this.logger.warn(`扫描插件语言包失败: ${type}`, error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 合并语言数据
|
||
*/
|
||
private mergeLanguageData(
|
||
target: Map<string, any>,
|
||
source: Record<string, any>,
|
||
): void {
|
||
for (const [key, value] of Object.entries(source)) {
|
||
target.set(key, value);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 从缓存获取消息
|
||
*/
|
||
private getMessageFromCache(key: string, language: string): string {
|
||
const languagePack = this.languageCache.get(language);
|
||
if (languagePack && languagePack.has(key)) {
|
||
return languagePack.get(key);
|
||
}
|
||
|
||
return key; // 未找到,返回key作为fallback
|
||
}
|
||
|
||
/**
|
||
* 替换消息参数
|
||
* 对应 Java: MessageFormat.format()
|
||
*/
|
||
private replaceMessageParams(
|
||
message: string,
|
||
args: Record<string, any>,
|
||
): string {
|
||
let result = message;
|
||
for (const [key, value] of Object.entries(args)) {
|
||
const placeholder = `{${key}}`;
|
||
result = result.replace(new RegExp(placeholder, 'g'), String(value));
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 获取当前语言
|
||
* @returns 当前语言
|
||
*/
|
||
getCurrentLanguage(): string {
|
||
return this.getDefaultLanguage();
|
||
}
|
||
|
||
/**
|
||
* 设置语言
|
||
* @param language 语言代码
|
||
*/
|
||
setLanguage(language: string): void {
|
||
this.logger.log(`设置语言: ${language}`);
|
||
}
|
||
|
||
/**
|
||
* 获取支持的语言列表
|
||
* @returns 支持的语言列表
|
||
*/
|
||
getSupportedLanguages(): string[] {
|
||
return this.configService.get('app.supportedLocales', ['zh_CN', 'en_US']);
|
||
}
|
||
|
||
/**
|
||
* 获取默认语言
|
||
* @returns 默认语言
|
||
*/
|
||
getDefaultLanguage(): string {
|
||
return this.configService.get('app.defaultLanguage', 'zh_CN');
|
||
}
|
||
|
||
/**
|
||
* 检查语言是否支持
|
||
* @param language 语言代码
|
||
* @returns 是否支持
|
||
*/
|
||
isLanguageSupported(language: string): boolean {
|
||
const supportedLanguages = this.getSupportedLanguages();
|
||
return supportedLanguages.includes(language);
|
||
}
|
||
|
||
/**
|
||
* 重新加载语言包
|
||
*/
|
||
async reloadLanguagePack(language: string): Promise<void> {
|
||
try {
|
||
// 清除该语言的所有缓存
|
||
this.languageCache.delete(language);
|
||
this.moduleCache.delete(language);
|
||
|
||
// 重新加载通用语言包
|
||
await this.loadModuleLanguagePack('common', 'api', language);
|
||
await this.loadModuleLanguagePack('common', 'dict', language);
|
||
await this.loadModuleLanguagePack('common', 'validate', language);
|
||
|
||
this.logger.log(`重新加载语言包完成: ${language}`);
|
||
} catch (error) {
|
||
this.logger.error(`重新加载语言包失败: ${language}`, error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重新加载所有语言包
|
||
*/
|
||
async reloadAllLanguagePacks(): Promise<void> {
|
||
try {
|
||
this.languageCache.clear();
|
||
this.moduleCache.clear();
|
||
await this.initializeLanguagePacks();
|
||
this.logger.log('所有语言包重新加载完成');
|
||
} catch (error) {
|
||
this.logger.error('重新加载所有语言包失败', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取API消息
|
||
* @param key 消息键
|
||
* @param args 参数
|
||
* @param module 模块名
|
||
* @param language 语言
|
||
*/
|
||
async getApiMessage(
|
||
key: string,
|
||
args?: any,
|
||
module: string = 'common',
|
||
language?: string,
|
||
): Promise<string> {
|
||
const currentLanguage = language || this.getCurrentLanguage();
|
||
return this.getMessage(key, args, module, 'api', currentLanguage);
|
||
}
|
||
|
||
/**
|
||
* 获取字典数据
|
||
* @param key 字典键
|
||
* @param args 参数
|
||
* @param module 模块名
|
||
* @param language 语言
|
||
*/
|
||
async getDictData(
|
||
key: string,
|
||
args?: any,
|
||
module: string = 'common',
|
||
language?: string,
|
||
): Promise<string> {
|
||
const currentLanguage = language || this.getCurrentLanguage();
|
||
return this.getMessage(key, args, module, 'dict', currentLanguage);
|
||
}
|
||
|
||
/**
|
||
* 获取验证器消息
|
||
* @param key 验证器键
|
||
* @param args 参数
|
||
* @param module 模块名
|
||
* @param language 语言
|
||
*/
|
||
async getValidateMessage(
|
||
key: string,
|
||
args?: any,
|
||
module: string = 'common',
|
||
language?: string,
|
||
): Promise<string> {
|
||
const currentLanguage = language || this.getCurrentLanguage();
|
||
return this.getMessage(key, args, module, 'validate', currentLanguage);
|
||
}
|
||
|
||
/**
|
||
* 批量获取消息
|
||
* @param keys 消息键数组
|
||
* @param module 模块名
|
||
* @param type 类型
|
||
* @param language 语言
|
||
*/
|
||
async getBatchMessages(
|
||
keys: string[],
|
||
module: string = 'common',
|
||
type: string = 'api',
|
||
language?: string,
|
||
): Promise<Record<string, string>> {
|
||
const results: Record<string, string> = {};
|
||
const currentLanguage = language || this.getCurrentLanguage();
|
||
|
||
for (const key of keys) {
|
||
results[key] = await this.getMessage(
|
||
key,
|
||
undefined,
|
||
module,
|
||
type,
|
||
currentLanguage,
|
||
);
|
||
}
|
||
|
||
return results;
|
||
}
|
||
}
|