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:
donald
2025-12-03 23:34:13 +08:00
commit c24634f4b7
18 changed files with 8179 additions and 0 deletions

211
utils/errorHandler.js Normal file
View 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,
};