feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error'
|
||||
}
|
||||
|
||||
interface LogEntry {
|
||||
timestamp: string;
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
context?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private static instance: Logger;
|
||||
private logDir: string;
|
||||
private logFile: string;
|
||||
private logLevel: LogLevel;
|
||||
private context: Record<string, unknown>;
|
||||
|
||||
private constructor(logDir: string = 'test-results/logs') {
|
||||
this.logDir = logDir;
|
||||
this.logFile = path.join(logDir, `test-run-${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
|
||||
this.logLevel = this.parseLogLevel(process.env.LOG_LEVEL || 'info');
|
||||
this.context = {};
|
||||
this.ensureLogDir();
|
||||
}
|
||||
|
||||
static getInstance(logDir?: string): Logger {
|
||||
if (!Logger.instance) {
|
||||
Logger.instance = new Logger(logDir);
|
||||
}
|
||||
return Logger.instance;
|
||||
}
|
||||
|
||||
private parseLogLevel(level: string): LogLevel {
|
||||
switch (level.toLowerCase()) {
|
||||
case 'debug': return LogLevel.DEBUG;
|
||||
case 'info': return LogLevel.INFO;
|
||||
case 'warn': return LogLevel.WARN;
|
||||
case 'error': return LogLevel.ERROR;
|
||||
default: return LogLevel.INFO;
|
||||
}
|
||||
}
|
||||
|
||||
private ensureLogDir(): void {
|
||||
if (!fs.existsSync(this.logDir)) {
|
||||
fs.mkdirSync(this.logDir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
setContext(context: Record<string, unknown>): this {
|
||||
this.context = { ...this.context, ...context };
|
||||
return this;
|
||||
}
|
||||
|
||||
clearContext(): this {
|
||||
this.context = {};
|
||||
return this;
|
||||
}
|
||||
|
||||
private formatTimestamp(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
private formatMessage(level: LogLevel, message: string): string {
|
||||
const timestamp = this.formatTimestamp();
|
||||
const levelStr = level.toUpperCase().padEnd(5);
|
||||
const contextStr = Object.keys(this.context).length > 0
|
||||
? ` [${JSON.stringify(this.context)}]`
|
||||
: '';
|
||||
return `${timestamp} | ${levelStr} |${contextStr} ${message}`;
|
||||
}
|
||||
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
|
||||
return levels.indexOf(level) >= levels.indexOf(this.logLevel);
|
||||
}
|
||||
|
||||
private writeToFile(entry: LogEntry): void {
|
||||
try {
|
||||
this.ensureLogDir();
|
||||
const logLine = JSON.stringify(entry) + '\n';
|
||||
fs.appendFileSync(this.logFile, logLine, 'utf-8');
|
||||
} catch {
|
||||
// Ignore file write errors
|
||||
}
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, context?: Record<string, unknown>): void {
|
||||
if (!this.shouldLog(level)) return;
|
||||
|
||||
const entry: LogEntry = {
|
||||
timestamp: this.formatTimestamp(),
|
||||
level,
|
||||
message,
|
||||
context: { ...this.context, ...context }
|
||||
};
|
||||
|
||||
this.writeToFile(entry);
|
||||
|
||||
const formattedMessage = this.formatMessage(level, message);
|
||||
const colors: Record<LogLevel, string> = {
|
||||
[LogLevel.DEBUG]: '\x1b[36m',
|
||||
[LogLevel.INFO]: '\x1b[32m',
|
||||
[LogLevel.WARN]: '\x1b[33m',
|
||||
[LogLevel.ERROR]: '\x1b[31m'
|
||||
};
|
||||
const reset = '\x1b[0m';
|
||||
console.log(`${colors[level]}${formattedMessage}${reset}`);
|
||||
}
|
||||
|
||||
debug(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.DEBUG, message, context);
|
||||
}
|
||||
|
||||
info(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.INFO, message, context);
|
||||
}
|
||||
|
||||
warn(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.WARN, message, context);
|
||||
}
|
||||
|
||||
error(message: string, error?: Error | unknown, context?: Record<string, unknown>): void {
|
||||
const errorContext = error instanceof Error
|
||||
? { errorMessage: error.message, stack: error.stack, ...context }
|
||||
: { error, ...context };
|
||||
this.log(LogLevel.ERROR, message, errorContext);
|
||||
}
|
||||
|
||||
section(title: string): void {
|
||||
const separator = '='.repeat(60);
|
||||
console.log(`\n${separator}`);
|
||||
console.log(` ${title}`);
|
||||
console.log(`${separator}\n`);
|
||||
this.info(`=== ${title} ===`);
|
||||
}
|
||||
|
||||
progress(current: number, total: number, message: string): void {
|
||||
const percentage = Math.round((current / total) * 100);
|
||||
const bar = this.createProgressBar(percentage);
|
||||
process.stdout.write(`\r${bar} ${percentage}% - ${message}`);
|
||||
if (current === total) {
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private createProgressBar(percentage: number, width: number = 30): string {
|
||||
const filled = Math.round((percentage / 100) * width);
|
||||
const empty = width - filled;
|
||||
return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
|
||||
}
|
||||
|
||||
getLogFile(): string {
|
||||
return this.logFile;
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = Logger.getInstance();
|
||||
Reference in New Issue
Block a user