chore(config): 初始化项目配置文件

- 添加 .editorconfig 文件统一代码风格
- 配置 .env.development 环境变量文件
- 创建 .env.example 环境变量示例文件
- 设置 .eslintignore 忽略检查规则
- 配置 .eslintrc.js 代码检查规则
- 添加 .gitignore 文件忽略版本控制
- 设置 .prettierignore 忽略格式化规则
- 新增隐私政策HTML页面文件
- 创建API密钥编辑组件基础结构
This commit is contained in:
2025-12-15 13:29:17 +08:00
commit 1856a611ce
877 changed files with 176918 additions and 0 deletions

View File

@@ -0,0 +1,411 @@
/**
* 租户端口管理器
* 集成租户识别系统和端口管理系统
*/
import { getTenantId } from '@/utils/domain';
import { getTenantInfo } from '@/api/layout';
import { PortManager, type PortConfig } from './port-manager';
import { portStrategyManager, EnvironmentDetector } from './port-strategy';
import type { Environment } from './port-strategy';
// 租户端口绑定配置
export interface TenantPortBinding {
tenantId: string | number;
tenantCode: string;
environment: Environment;
assignedPort: number;
customDomain?: string;
isActive: boolean;
createdAt: number;
lastUsed: number;
metadata: {
projectName: string;
version: string;
description?: string;
};
}
// 端口分配结果
export interface PortAllocationResult {
success: boolean;
port?: number;
binding?: TenantPortBinding;
error?: string;
fallbackPorts?: number[];
recommendations?: string[];
}
// 租户端口缓存管理
class TenantPortCache {
private static readonly CACHE_KEY = 'tenant-port-bindings';
private static readonly CACHE_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7天
static get(): Map<string, TenantPortBinding> {
try {
const cached = localStorage.getItem(this.CACHE_KEY);
if (!cached) return new Map();
const data = JSON.parse(cached);
const now = Date.now();
// 清理过期缓存
const validEntries = Object.entries(data).filter(
([_, binding]: [string, any]) =>
now - binding.lastUsed < this.CACHE_EXPIRY
);
return new Map(validEntries);
} catch (error) {
console.warn('租户端口缓存读取失败:', error);
return new Map();
}
}
static set(cache: Map<string, TenantPortBinding>): void {
try {
const data = Object.fromEntries(cache);
localStorage.setItem(this.CACHE_KEY, JSON.stringify(data));
} catch (error) {
console.warn('租户端口缓存保存失败:', error);
}
}
static clear(): void {
localStorage.removeItem(this.CACHE_KEY);
}
static generateKey(tenantId: string | number, environment: Environment): string {
return `${tenantId}-${environment}`;
}
}
// 租户端口管理器
export class TenantPortManager {
private portManager: PortManager;
private bindings: Map<string, TenantPortBinding>;
private currentEnvironment: Environment;
constructor() {
this.portManager = new PortManager();
this.bindings = TenantPortCache.get();
this.currentEnvironment = EnvironmentDetector.detectEnvironment();
console.log('🏢 租户端口管理器初始化完成', {
environment: this.currentEnvironment,
bindingsCount: this.bindings.size
});
}
/**
* 为租户分配端口(主要方法)
*/
async allocatePortForTenant(options?: {
tenantId?: string | number;
preferredPort?: number;
forceNew?: boolean;
}): Promise<PortAllocationResult> {
try {
// 1. 获取租户信息
const tenantId = options?.tenantId || await getTenantId();
const tenantInfo = await getTenantInfo();
if (!tenantId) {
return {
success: false,
error: '无法获取租户ID',
recommendations: ['请检查租户配置', '确保已正确设置租户识别']
};
}
// 2. 检查现有绑定
const bindingKey = TenantPortCache.generateKey(tenantId, this.currentEnvironment);
const existingBinding = this.bindings.get(bindingKey);
if (existingBinding && !options?.forceNew) {
// 验证现有端口是否仍然可用
if (await this.validatePortBinding(existingBinding)) {
existingBinding.lastUsed = Date.now();
this.updateBinding(bindingKey, existingBinding);
console.log('📋 使用现有租户端口绑定:', existingBinding.assignedPort);
return {
success: true,
port: existingBinding.assignedPort,
binding: existingBinding
};
} else {
console.warn('⚠️ 现有端口绑定已失效,重新分配');
this.bindings.delete(bindingKey);
}
}
// 3. 分配新端口
const portConfig = await this.portManager.getRecommendedPort({
tenantId,
projectName: tenantInfo?.name || 'mp-vue',
preferredPort: options?.preferredPort
});
// 4. 创建租户端口绑定
const binding = this.createTenantBinding(tenantId, tenantInfo, portConfig);
this.updateBinding(bindingKey, binding);
console.log('🎯 为租户分配新端口:', {
tenantId,
port: binding.assignedPort,
environment: this.currentEnvironment
});
return {
success: true,
port: binding.assignedPort,
binding,
recommendations: this.generateRecommendations(binding)
};
} catch (error) {
console.error('❌ 租户端口分配失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
recommendations: ['检查网络连接', '验证租户配置', '尝试重新启动服务']
};
}
}
/**
* 验证端口绑定是否有效
*/
private async validatePortBinding(binding: TenantPortBinding): Promise<boolean> {
try {
// 检查端口是否仍然可用
const response = await fetch(`http://localhost:${binding.assignedPort}`, {
method: 'HEAD',
mode: 'no-cors',
signal: AbortSignal.timeout(2000)
});
// 如果能连接,说明端口被占用(可能是我们自己的服务)
return true;
} catch (error) {
// 连接失败,端口可能已释放
return false;
}
}
/**
* 创建租户端口绑定
*/
private createTenantBinding(
tenantId: string | number,
tenantInfo: any,
portConfig: PortConfig
): TenantPortBinding {
return {
tenantId,
tenantCode: tenantInfo?.code || String(tenantId),
environment: this.currentEnvironment,
assignedPort: portConfig.port,
customDomain: tenantInfo?.domain,
isActive: true,
createdAt: Date.now(),
lastUsed: Date.now(),
metadata: {
projectName: portConfig.projectName || 'mp-vue',
version: '1.0.0',
description: `${tenantInfo?.name || '租户'} - ${this.currentEnvironment}环境`
}
};
}
/**
* 更新绑定缓存
*/
private updateBinding(key: string, binding: TenantPortBinding): void {
this.bindings.set(key, binding);
TenantPortCache.set(this.bindings);
}
/**
* 生成建议
*/
private generateRecommendations(binding: TenantPortBinding): string[] {
const recommendations: string[] = [];
const strategy = portStrategyManager.getCurrentStrategy();
// 环境特定建议
switch (binding.environment) {
case 'development':
recommendations.push('开发环境:建议配置热重载和调试工具');
recommendations.push(`访问地址http://localhost:${binding.assignedPort}`);
break;
case 'test':
recommendations.push('测试环境:建议配置自动化测试和监控');
break;
case 'production':
recommendations.push('生产环境建议配置HTTPS和负载均衡');
if (binding.customDomain) {
recommendations.push(`自定义域名:${binding.customDomain}`);
}
break;
}
// 端口范围建议
if (binding.assignedPort < strategy.portRange[0] || binding.assignedPort > strategy.portRange[1]) {
recommendations.push('⚠️ 分配的端口超出推荐范围,可能存在冲突风险');
}
return recommendations;
}
/**
* 获取租户端口信息
*/
async getTenantPortInfo(tenantId?: string | number): Promise<{
current?: TenantPortBinding;
history: TenantPortBinding[];
recommendations: string[];
}> {
const targetTenantId = tenantId || await getTenantId();
const history: TenantPortBinding[] = [];
let current: TenantPortBinding | undefined;
// 查找当前和历史绑定
this.bindings.forEach(binding => {
if (binding.tenantId === targetTenantId) {
if (binding.environment === this.currentEnvironment && binding.isActive) {
current = binding;
}
history.push(binding);
}
});
// 按时间排序
history.sort((a, b) => b.lastUsed - a.lastUsed);
const recommendations = current
? this.generateRecommendations(current)
: ['当前环境暂无端口绑定,建议调用 allocatePortForTenant 分配端口'];
return { current, history, recommendations };
}
/**
* 释放租户端口
*/
async releaseTenantPort(tenantId?: string | number): Promise<boolean> {
try {
const targetTenantId = tenantId || await getTenantId();
const bindingKey = TenantPortCache.generateKey(targetTenantId, this.currentEnvironment);
const binding = this.bindings.get(bindingKey);
if (binding) {
binding.isActive = false;
this.updateBinding(bindingKey, binding);
console.log(`🔓 已释放租户 ${targetTenantId} 的端口 ${binding.assignedPort}`);
return true;
}
return false;
} catch (error) {
console.error('释放租户端口失败:', error);
return false;
}
}
/**
* 获取所有租户端口统计
*/
getAllTenantsPortStats(): {
totalBindings: number;
activeBindings: number;
environmentStats: Record<Environment, number>;
portRangeUsage: { min: number; max: number; average: number };
topTenants: Array<{ tenantId: string | number; bindingsCount: number }>;
} {
const stats = {
totalBindings: this.bindings.size,
activeBindings: 0,
environmentStats: {} as Record<Environment, number>,
portRangeUsage: { min: Infinity, max: 0, average: 0 },
topTenants: [] as Array<{ tenantId: string | number; bindingsCount: number }>
};
const tenantCounts = new Map<string | number, number>();
let portSum = 0;
this.bindings.forEach(binding => {
// 活跃绑定统计
if (binding.isActive) {
stats.activeBindings++;
}
// 环境统计
stats.environmentStats[binding.environment] =
(stats.environmentStats[binding.environment] || 0) + 1;
// 端口范围统计
stats.portRangeUsage.min = Math.min(stats.portRangeUsage.min, binding.assignedPort);
stats.portRangeUsage.max = Math.max(stats.portRangeUsage.max, binding.assignedPort);
portSum += binding.assignedPort;
// 租户统计
const count = tenantCounts.get(binding.tenantId) || 0;
tenantCounts.set(binding.tenantId, count + 1);
});
// 计算平均端口
stats.portRangeUsage.average = stats.totalBindings > 0
? Math.round(portSum / stats.totalBindings)
: 0;
// 修复无限大的情况
if (stats.portRangeUsage.min === Infinity) {
stats.portRangeUsage.min = 0;
}
// 排序租户使用量
stats.topTenants = Array.from(tenantCounts.entries())
.map(([tenantId, count]) => ({ tenantId, bindingsCount: count }))
.sort((a, b) => b.bindingsCount - a.bindingsCount)
.slice(0, 10);
return stats;
}
/**
* 清理过期绑定
*/
cleanupExpiredBindings(): number {
const now = Date.now();
const expiry = 7 * 24 * 60 * 60 * 1000; // 7天
let cleaned = 0;
this.bindings.forEach((binding, key) => {
if (now - binding.lastUsed > expiry) {
this.bindings.delete(key);
cleaned++;
}
});
if (cleaned > 0) {
TenantPortCache.set(this.bindings);
console.log(`🧹 清理了 ${cleaned} 个过期的租户端口绑定`);
}
return cleaned;
}
/**
* 重置所有绑定
*/
resetAllBindings(): void {
this.bindings.clear();
TenantPortCache.clear();
console.log('🔄 所有租户端口绑定已重置');
}
}
// 导出默认实例
export const tenantPortManager = new TenantPortManager();