Initial commit: HR Performance System
- Database schema with 31 tables for 4-card system - LLM API integration (Gemini, DeepSeek, OpenAI) - Error handling system with modal component - Connection test UI for LLM services - Environment configuration files - Complete database documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
211
utils/errorHandler.js
Normal file
211
utils/errorHandler.js
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* 錯誤處理工具
|
||||
* 統一處理應用程式中的錯誤
|
||||
*/
|
||||
|
||||
class ErrorHandler extends Error {
|
||||
constructor(statusCode, message, details = null) {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.details = details;
|
||||
this.timestamp = new Date().toISOString();
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 錯誤類型定義
|
||||
*/
|
||||
const ErrorTypes = {
|
||||
// 4xx Client Errors
|
||||
BAD_REQUEST: { code: 400, message: '請求參數錯誤' },
|
||||
UNAUTHORIZED: { code: 401, message: '未授權訪問' },
|
||||
FORBIDDEN: { code: 403, message: '禁止訪問' },
|
||||
NOT_FOUND: { code: 404, message: '資源不存在' },
|
||||
CONFLICT: { code: 409, message: '資源衝突' },
|
||||
VALIDATION_ERROR: { code: 422, message: '資料驗證失敗' },
|
||||
|
||||
// 5xx Server Errors
|
||||
INTERNAL_ERROR: { code: 500, message: '伺服器內部錯誤' },
|
||||
DATABASE_ERROR: { code: 500, message: '資料庫錯誤' },
|
||||
LLM_API_ERROR: { code: 500, message: 'LLM API 錯誤' },
|
||||
EXTERNAL_API_ERROR: { code: 502, message: '外部 API 錯誤' },
|
||||
SERVICE_UNAVAILABLE: { code: 503, message: '服務暫時無法使用' },
|
||||
};
|
||||
|
||||
/**
|
||||
* 建立錯誤實例
|
||||
*/
|
||||
function createError(type, customMessage = null, details = null) {
|
||||
const errorType = ErrorTypes[type] || ErrorTypes.INTERNAL_ERROR;
|
||||
const message = customMessage || errorType.message;
|
||||
return new ErrorHandler(errorType.code, message, details);
|
||||
}
|
||||
|
||||
/**
|
||||
* Express 錯誤處理中介層
|
||||
*/
|
||||
function handleError(err, req, res, next) {
|
||||
const statusCode = err.statusCode || 500;
|
||||
const message = err.message || '未知錯誤';
|
||||
|
||||
// 記錄錯誤日誌
|
||||
console.error('[Error]', {
|
||||
timestamp: new Date().toISOString(),
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
statusCode,
|
||||
message,
|
||||
details: err.details,
|
||||
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
|
||||
});
|
||||
|
||||
// 回傳錯誤訊息
|
||||
res.status(statusCode).json({
|
||||
success: false,
|
||||
error: {
|
||||
statusCode,
|
||||
message,
|
||||
details: err.details,
|
||||
timestamp: err.timestamp || new Date().toISOString(),
|
||||
path: req.path,
|
||||
// 只在開發環境顯示堆疊資訊
|
||||
...(process.env.NODE_ENV === 'development' && { stack: err.stack }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕獲非同步錯誤的包裝器
|
||||
*/
|
||||
function asyncHandler(fn) {
|
||||
return (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 驗證錯誤處理
|
||||
*/
|
||||
function validationError(errors) {
|
||||
return createError('VALIDATION_ERROR', '資料驗證失敗', errors);
|
||||
}
|
||||
|
||||
/**
|
||||
* 資料庫錯誤處理
|
||||
*/
|
||||
function databaseError(error) {
|
||||
const message = error.code === 'ER_DUP_ENTRY'
|
||||
? '資料重複,請檢查輸入的內容'
|
||||
: '資料庫操作失敗';
|
||||
|
||||
return createError('DATABASE_ERROR', message, {
|
||||
code: error.code,
|
||||
sqlMessage: error.sqlMessage,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* LLM API 錯誤處理
|
||||
*/
|
||||
function llmApiError(provider, error) {
|
||||
return createError('LLM_API_ERROR', `${provider} API 錯誤`, {
|
||||
provider,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端錯誤處理輔助函數
|
||||
*/
|
||||
const FrontendErrorHandler = {
|
||||
/**
|
||||
* 顯示錯誤訊息
|
||||
*/
|
||||
showError(error, options = {}) {
|
||||
const {
|
||||
title = '錯誤',
|
||||
duration = 5000,
|
||||
showDetails = false,
|
||||
} = options;
|
||||
|
||||
const errorData = error.response?.data?.error || error;
|
||||
const message = errorData.message || error.message || '發生未知錯誤';
|
||||
const details = showDetails ? errorData.details : null;
|
||||
|
||||
// 這裡可以整合不同的前端 UI 框架
|
||||
return {
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
statusCode: errorData.statusCode,
|
||||
timestamp: errorData.timestamp,
|
||||
duration,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 處理 API 錯誤
|
||||
*/
|
||||
handleApiError(error) {
|
||||
if (error.response) {
|
||||
// 伺服器回應錯誤
|
||||
const { status, data } = error.response;
|
||||
|
||||
switch (status) {
|
||||
case 400:
|
||||
return this.showError(error, { title: '請求錯誤' });
|
||||
case 401:
|
||||
return this.showError(error, { title: '未授權' });
|
||||
case 403:
|
||||
return this.showError(error, { title: '禁止訪問' });
|
||||
case 404:
|
||||
return this.showError(error, { title: '資源不存在' });
|
||||
case 422:
|
||||
return this.showError(error, {
|
||||
title: '資料驗證失敗',
|
||||
showDetails: true
|
||||
});
|
||||
case 500:
|
||||
return this.showError(error, { title: '伺服器錯誤' });
|
||||
default:
|
||||
return this.showError(error);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// 請求已發送但沒有收到回應
|
||||
return {
|
||||
title: '網路錯誤',
|
||||
message: '無法連接到伺服器,請檢查網路連線',
|
||||
duration: 5000,
|
||||
};
|
||||
} else {
|
||||
// 其他錯誤
|
||||
return this.showError(error, { title: '錯誤' });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 驗證表單錯誤
|
||||
*/
|
||||
handleValidationErrors(errors) {
|
||||
if (Array.isArray(errors)) {
|
||||
return errors.map(err => ({
|
||||
field: err.field || err.param,
|
||||
message: err.message || err.msg,
|
||||
}));
|
||||
}
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
ErrorHandler,
|
||||
ErrorTypes,
|
||||
createError,
|
||||
handleError,
|
||||
asyncHandler,
|
||||
validationError,
|
||||
databaseError,
|
||||
llmApiError,
|
||||
FrontendErrorHandler,
|
||||
};
|
||||
Reference in New Issue
Block a user