Initial commit

This commit is contained in:
theshy
2025-07-31 17:05:07 +08:00
parent 8fab3b19cc
commit 24f21144ab
91 changed files with 16311 additions and 159 deletions

111
lib/utils/api-response.ts Normal file
View File

@ -0,0 +1,111 @@
import { NextResponse } from "next/server";
import { AppError } from "../errors/app-error";
export interface ApiResponse<T = unknown> {
success: boolean;
data?: T;
error?: {
message: string;
code?: string;
details?: unknown;
};
meta?: {
timestamp: string;
requestId?: string;
};
}
export class ApiResponseHandler {
/**
* 成功响应
*/
static success<T>(
data: T,
statusCode: number = 200,
meta?: { requestId?: string }
): NextResponse<ApiResponse<T>> {
const response: ApiResponse<T> = {
success: true,
data,
meta: {
timestamp: new Date().toISOString(),
...meta,
},
};
return NextResponse.json(response, { status: statusCode });
}
/**
* 错误响应
*/
static error(
error: AppError | Error,
meta?: { requestId?: string }
): NextResponse<ApiResponse> {
const isAppError = error instanceof AppError;
const statusCode = isAppError ? error.statusCode : 500;
const code = isAppError ? error.code : "INTERNAL_SERVER_ERROR";
const response: ApiResponse = {
success: false,
error: {
message: error.message,
code,
details: isAppError && !error.isOperational ? error.stack : undefined,
},
meta: {
timestamp: new Date().toISOString(),
...meta,
},
};
return NextResponse.json(response, { status: statusCode });
}
/**
* 分页响应
*/
static paginated<T>(
data: T[],
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
},
meta?: { requestId?: string }
): NextResponse<ApiResponse<{ data: T[]; pagination: typeof pagination }>> {
const response: ApiResponse<{ data: T[]; pagination: typeof pagination }> =
{
success: true,
data: {
data,
pagination,
},
meta: {
timestamp: new Date().toISOString(),
...meta,
},
};
return NextResponse.json(response, { status: 200 });
}
/**
* 创建响应
*/
static created<T>(
data: T,
meta?: { requestId?: string }
): NextResponse<ApiResponse<T>> {
return this.success(data, 201, meta);
}
/**
* 无内容响应
*/
static noContent(): NextResponse {
return new NextResponse(null, { status: 204 });
}
}

6
lib/utils/cn.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

148
lib/utils/logger.ts Normal file
View File

@ -0,0 +1,148 @@
import { config } from "../config";
export enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3,
}
export interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
context?: Record<string, unknown>;
error?: Error;
}
class Logger {
private logLevel: LogLevel;
constructor() {
this.logLevel =
process.env.NODE_ENV === "development" ? LogLevel.DEBUG : LogLevel.INFO;
}
private formatMessage(entry: LogEntry): string {
const { timestamp, level, message, context, error } = entry;
const levelName = LogLevel[level];
const contextStr = context ? ` ${JSON.stringify(context)}` : "";
const errorStr = error ? `\n${error.stack}` : "";
return `[${timestamp}] ${levelName}: ${message}${contextStr}${errorStr}`;
}
private shouldLog(level: LogLevel): boolean {
return level >= this.logLevel;
}
private log(
level: LogLevel,
message: string,
context?: Record<string, unknown>,
error?: Error
): void {
if (!this.shouldLog(level)) return;
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
message,
context,
error,
};
const formattedMessage = this.formatMessage(entry);
switch (level) {
case LogLevel.DEBUG:
console.debug(formattedMessage);
break;
case LogLevel.INFO:
console.info(formattedMessage);
break;
case LogLevel.WARN:
console.warn(formattedMessage);
break;
case LogLevel.ERROR:
console.error(formattedMessage);
break;
}
// 在生产环境中,可以发送到外部日志服务
if (process.env.NODE_ENV === "production" && level >= LogLevel.ERROR) {
this.sendToExternalService(entry);
}
}
private sendToExternalService(entry: LogEntry): void {
// 这里可以集成 Sentry, LogRocket 等外部日志服务
// 示例:发送到 Sentry
if (process.env.SENTRY_DSN) {
// Sentry.captureException(entry.error || new Error(entry.message))
}
}
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>,
error?: Error
): void {
this.log(LogLevel.WARN, message, context, error);
}
error(
message: string,
context?: Record<string, unknown>,
error?: Error
): void {
this.log(LogLevel.ERROR, message, context, error);
}
// 记录 API 请求
logApiRequest(
method: string,
url: string,
statusCode: number,
duration: number
): void {
this.info("API Request", {
method,
url,
statusCode,
duration: `${duration}ms`,
});
}
// 记录数据库操作
logDbOperation(operation: string, table: string, duration: number): void {
this.debug("Database Operation", {
operation,
table,
duration: `${duration}ms`,
});
}
// 记录用户操作
logUserAction(
userId: string,
action: string,
details?: Record<string, unknown>
): void {
this.info("User Action", {
userId,
action,
...details,
});
}
}
export const logger = new Logger();

163
lib/utils/notifications.ts Normal file
View File

@ -0,0 +1,163 @@
// 通知类型
export type NotificationType = "success" | "error" | "warning" | "info";
export interface AppNotification {
id: string;
type: NotificationType;
title: string;
message: string;
duration?: number;
timestamp: Date;
}
// 通知管理器
class NotificationManager {
private notifications: AppNotification[] = [];
private listeners: ((notifications: AppNotification[]) => void)[] = [];
// 添加通知
addNotification(
type: NotificationType,
title: string,
message: string,
duration: number = 5000
): string {
const id = `notification-${Date.now()}-${Math.random()}`;
const notification: AppNotification = {
id,
type,
title,
message,
duration,
timestamp: new Date(),
};
this.notifications.push(notification);
this.notifyListeners();
// 自动移除通知
if (duration > 0) {
setTimeout(() => {
this.removeNotification(id);
}, duration);
}
return id;
}
// 移除通知
removeNotification(id: string): void {
this.notifications = this.notifications.filter((n) => n.id !== id);
this.notifyListeners();
}
// 清空所有通知
clearAll(): void {
this.notifications = [];
this.notifyListeners();
}
// 获取所有通知
getNotifications(): AppNotification[] {
return [...this.notifications];
}
// 添加监听器
addListener(listener: (notifications: AppNotification[]) => void): void {
this.listeners.push(listener);
}
// 移除监听器
removeListener(listener: (notifications: AppNotification[]) => void): void {
this.listeners = this.listeners.filter((l) => l !== listener);
}
// 通知所有监听器
private notifyListeners(): void {
this.listeners.forEach((listener) => listener(this.notifications));
}
// 便捷方法
success(title: string, message: string, duration?: number): string {
return this.addNotification("success", title, message, duration);
}
error(title: string, message: string, duration?: number): string {
return this.addNotification("error", title, message, duration);
}
warning(title: string, message: string, duration?: number): string {
return this.addNotification("warning", title, message, duration);
}
info(title: string, message: string, duration?: number): string {
return this.addNotification("info", title, message, duration);
}
}
// 创建全局通知管理器实例
export const notificationManager = new NotificationManager();
// 浏览器通知 API
export class BrowserNotifications {
static async requestPermission(): Promise<boolean> {
if (!("Notification" in window)) {
console.warn("此浏览器不支持通知");
return false;
}
if (Notification.permission === "granted") {
return true;
}
if (Notification.permission === "denied") {
return false;
}
const permission = await Notification.requestPermission();
return permission === "granted";
}
static async showNotification(
title: string,
options?: NotificationOptions
): Promise<globalThis.Notification | null> {
if (!("Notification" in window)) {
return null;
}
if (Notification.permission !== "granted") {
const granted = await this.requestPermission();
if (!granted) {
return null;
}
}
return new globalThis.Notification(title, {
icon: "/favicon.ico",
badge: "/favicon.ico",
...options,
});
}
static async showRecordingComplete(): Promise<void> {
await this.showNotification("录音完成", {
body: "您的录音已成功保存",
tag: "recording-complete",
});
}
static async showUploadComplete(): Promise<void> {
await this.showNotification("上传完成", {
body: "录音文件已成功上传到服务器",
tag: "upload-complete",
});
}
static async showUploadError(): Promise<void> {
await this.showNotification("上传失败", {
body: "录音文件上传失败,请重试",
tag: "upload-error",
});
}
}

193
lib/utils/performance.ts Normal file
View File

@ -0,0 +1,193 @@
import { logger } from "./logger";
export interface PerformanceMetric {
name: string;
duration: number;
timestamp: number;
metadata?: Record<string, unknown>;
}
class PerformanceMonitor {
private metrics: PerformanceMetric[] = [];
private maxMetrics = 1000;
/**
* 测量函数执行时间
*/
async measure<T>(
name: string,
fn: () => Promise<T>,
metadata?: Record<string, unknown>
): Promise<T> {
const startTime = Date.now();
try {
const result = await fn();
const duration = Date.now() - startTime;
this.recordMetric(name, duration, metadata);
return result;
} catch (error) {
const duration = Date.now() - startTime;
this.recordMetric(`${name}_error`, duration, {
...metadata,
error: true,
});
throw error;
}
}
/**
* 同步测量函数执行时间
*/
measureSync<T>(
name: string,
fn: () => T,
metadata?: Record<string, unknown>
): T {
const startTime = Date.now();
try {
const result = fn();
const duration = Date.now() - startTime;
this.recordMetric(name, duration, metadata);
return result;
} catch (error) {
const duration = Date.now() - startTime;
this.recordMetric(`${name}_error`, duration, {
...metadata,
error: true,
});
throw error;
}
}
/**
* 记录性能指标
*/
recordMetric(
name: string,
duration: number,
metadata?: Record<string, unknown>
): void {
const metric: PerformanceMetric = {
name,
duration,
timestamp: Date.now(),
metadata,
};
this.metrics.push(metric);
// 限制指标数量
if (this.metrics.length > this.maxMetrics) {
this.metrics = this.metrics.slice(-this.maxMetrics / 2);
}
// 记录慢查询
if (duration > 1000) {
logger.warn("Slow operation detected", {
name,
duration,
metadata,
});
}
}
/**
* 获取性能统计
*/
getStats(): {
totalMetrics: number;
averageDuration: number;
slowestOperations: PerformanceMetric[];
fastestOperations: PerformanceMetric[];
operationCounts: Record<string, number>;
} {
if (this.metrics.length === 0) {
return {
totalMetrics: 0,
averageDuration: 0,
slowestOperations: [],
fastestOperations: [],
operationCounts: {},
};
}
const totalDuration = this.metrics.reduce(
(sum, metric) => sum + metric.duration,
0
);
const averageDuration = totalDuration / this.metrics.length;
// 按名称分组统计
const operationCounts: Record<string, number> = {};
this.metrics.forEach((metric) => {
operationCounts[metric.name] = (operationCounts[metric.name] || 0) + 1;
});
// 获取最慢的操作
const slowestOperations = [...this.metrics]
.sort((a, b) => b.duration - a.duration)
.slice(0, 10);
// 获取最快的操作
const fastestOperations = [...this.metrics]
.sort((a, b) => a.duration - b.duration)
.slice(0, 10);
return {
totalMetrics: this.metrics.length,
averageDuration,
slowestOperations,
fastestOperations,
operationCounts,
};
}
/**
* 清理旧指标
*/
cleanup(maxAge: number = 24 * 60 * 60 * 1000): void {
const cutoff = Date.now() - maxAge;
this.metrics = this.metrics.filter((metric) => metric.timestamp > cutoff);
}
/**
* 导出性能报告
*/
generateReport(): string {
const stats = this.getStats();
return `
Performance Report
==================
Total Metrics: ${stats.totalMetrics}
Average Duration: ${stats.averageDuration.toFixed(2)}ms
Slowest Operations:
${stats.slowestOperations
.map((op) => ` ${op.name}: ${op.duration}ms`)
.join("\n")}
Operation Counts:
${Object.entries(stats.operationCounts)
.sort(([, a], [, b]) => b - a)
.map(([name, count]) => ` ${name}: ${count}`)
.join("\n")}
`.trim();
}
}
export const performanceMonitor = new PerformanceMonitor();
// 定期清理旧指标
if (typeof window === "undefined") {
setInterval(() => {
performanceMonitor.cleanup();
}, 60 * 60 * 1000); // 每小时清理一次
}