430 lines
15 KiB
Markdown
430 lines
15 KiB
Markdown
# สรุปการตรวจสอบ Unhandled Exception และ Crash Loop Risks
|
|
## ทั้งหมด 140 Controllers ใน BMA EHR Organization Backend
|
|
|
|
**วันที่ตรวจสอบ:** 8 พฤษภาคม 2568
|
|
**Framework:** TSOA + Express + TypeORM
|
|
**สถานะ:** ✅ ตรวจสอบครบทุก Controllers แล้ว
|
|
|
|
---
|
|
|
|
## ภาพรวมสถิติ
|
|
|
|
### จำนวน Controllers ที่ตรวจสอบ
|
|
| Batch | ช่วง Controllers | จำนวน | สถานะ |
|
|
|-------|-----------------|--------|--------|
|
|
| 1 | 1-10 | 10 | ✅ เสร็จสิ้น |
|
|
| 2 | 11-20 | 10 | ✅ เสร็จสิ้น |
|
|
| 3 | 21-30 | 10 | ✅ เสร็จสิ้น |
|
|
| 4 | 31-40 | 10 | ✅ เสร็จสิ้น |
|
|
| 5 | 41-50 | 10 | ✅ เสร็จสิ้น |
|
|
| 6 | 51-60 | 10 | ✅ เสร็จสิ้น |
|
|
| 7 | 61-70 | 10 | ✅ เสร็จสิ้น |
|
|
| 8 | 71-80 | 10 | ✅ เสร็จสิ้น |
|
|
| 9 | 81-90 | 10 | ✅ เสร็จสิ้น |
|
|
| 10 | 91-100 | 10 | ✅ เสร็จสิ้น |
|
|
| 11 | 101-110 | 10 | ✅ เสร็จสิ้น |
|
|
| 12 | 111-120 | 10 | ✅ เสร็จสิ้น |
|
|
| 13 | 121-130 | 10 | ✅ เสร็จสิ้น |
|
|
| 14 | 131-140 | 10 | ✅ เสร็จสิ้น |
|
|
| **รวม** | **1-140** | **140** | **✅ 100%** |
|
|
|
|
### สรุปจำนวนปัญหาที่พบ
|
|
|
|
| ระดับความรุนแรง | จำนวนจุดเสี่ยง | อธิบาย |
|
|
|---------------------|-------------------|---------|
|
|
| 🔴 **CRITICAL** | 23 | มีโอกาสทำให้ Service Crash สูงมาก |
|
|
| 🟠 **HIGH** | 35 | มีโอกาสทำให้เกิด Unhandled Exception |
|
|
| 🟡 **MEDIUM** | 28 | อาจทำให้เกิดปัญหาในสถานการณ์เฉพาะ |
|
|
| 🟢 **LOW** | 12 | ควรปรับปรุงแต่ไม่กระทบต่อการทำงาน |
|
|
| 🐛 **BUG** | 18 | ข้อผิดพลาดใน Logic |
|
|
| **รวมทั้งหมด** | **116** | - |
|
|
|
|
---
|
|
|
|
## ปัญหา CRITICAL ที่ต้องแก้ไขโดยเร็ว (P0)
|
|
|
|
### 1. Redis Client Connection Leak (4 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `AuthRoleController.ts` (2 จุด)
|
|
- `PermissionController.ts` (7 จุด)
|
|
|
|
**ปัญหา:**
|
|
- สร้าง Redis Client ใหม่ทุกครั้งแต่ไม่ปิด connection
|
|
- ทำให้เกิด connection pool exhaustion
|
|
- อาจทำให้ service crash เมื่อถึง limit
|
|
|
|
**วิธีแก้ไข:**
|
|
```typescript
|
|
let redisClient;
|
|
try {
|
|
redisClient = await this.redis.createClient({...});
|
|
// ... operations
|
|
} finally {
|
|
if (redisClient) {
|
|
redisClient.quit();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. Promise.all Without Error Handling (8 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `AuthRoleController.ts`
|
|
- `DevelopmentRequestController.ts` (3 จุด)
|
|
- `EmployeePositionController.ts` (2 จุด)
|
|
- `EmployeeTempPositionController.ts`
|
|
- `ImportDataController.ts`
|
|
|
|
**ปัญหา:**
|
|
- ใช้ Promise.all โดยไม่มี try-catch
|
|
- ถ้ามี operation ไหน fail จะเกิด unhandled rejection
|
|
- อาจทำให้ data inconsistency
|
|
|
|
**วิธีแก้ไข:**
|
|
```typescript
|
|
try {
|
|
await Promise.all(items.map(async (item) => {
|
|
try {
|
|
await processItem(item);
|
|
} catch (error) {
|
|
console.error(`Failed to process ${item}:`, error);
|
|
throw error;
|
|
}
|
|
}));
|
|
} catch (error) {
|
|
throw new HttpError(HttpStatus.INTERNAL_SERVER_ERROR, "Operation failed");
|
|
}
|
|
```
|
|
|
|
### 3. Async forEach Without Proper Error Handling (5 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `EmployeePositionController.ts`
|
|
- `ProfileSalaryTempController` (4 จุด)
|
|
|
|
**ปัญหา:**
|
|
- ใช้ forEach กับ async function ซึ่งไม่รอ completion
|
|
- Error ที่เกิดใน loop จะไม่ถูก handle
|
|
- อาจทำให้ data ไม่ถูกต้อง
|
|
|
|
**วิธีแก้ไข:**
|
|
```typescript
|
|
// ❌ ไม่ดี
|
|
array.forEach(async (item) => {
|
|
await processItem(item);
|
|
});
|
|
|
|
// ✅ ดี
|
|
for (const item of array) {
|
|
await processItem(item);
|
|
}
|
|
// หรือ
|
|
await Promise.all(array.map(item => processItem(item)));
|
|
```
|
|
|
|
### 4. Transaction QueryRunner Not Released on Error (3 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `CommandOperatorController.ts`
|
|
- `WorkflowController.ts`
|
|
- `OrgRootController.ts`
|
|
|
|
**ปัญหา:**
|
|
- ใช้ QueryRunner และ Transaction แต่ไม่ release ถ้าเกิด error
|
|
- ทำให้เกิด connection leak
|
|
- อาจทำให้ database connection exhausted
|
|
|
|
**วิธีแก้ไข:**
|
|
```typescript
|
|
const queryRunner = AppDataSource.createQueryRunner();
|
|
try {
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
|
|
try {
|
|
// ... operations
|
|
await queryRunner.commitTransaction();
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
throw error;
|
|
}
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
```
|
|
|
|
### 5. Database Operations Without Transactions (6 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `OrgRootController.ts` (ลบข้อมูล 8 ตารางต่อเนื่อง)
|
|
- `OrgChild1Controller.ts` (ลบข้อมูล 4 ตาราง)
|
|
- `OrgChild2Controller.ts` (ลบข้อมูล 3 ตาราง)
|
|
- `OrgChild3Controller.ts` (ลบข้อมูล 2 ตาราง)
|
|
- `OrgChild4Controller.ts` (ลบข้อมูล 1 ตาราง)
|
|
|
|
**ปัญหา:**
|
|
- ลบข้อมูลหลายตารางต่อเนื่องกันโดยไม่ใช้ transaction
|
|
- ถ้า delete ตัวใดตัวหนึ่งล้มเหลว ข้อมูลจะไม่สมบูรณ์
|
|
- เกิด data inconsistency
|
|
|
|
### 6. Unhandled External API Calls (7 จุด)
|
|
**ไฟล์ที่พบ:**
|
|
- `ChangePositionController.ts`
|
|
- `ProfileEditController.ts`
|
|
- `ProfileEditEmployeeController.ts`
|
|
- `ProfileController.ts`
|
|
- `ExRetirementController.ts`
|
|
|
|
**ปัญหา:**
|
|
- เรียก External API โดยไม่มี error handling
|
|
- หรือมีแต่ใช้ `.catch()` ว่างเปล่า
|
|
- ทำให้ไม่ทราบว่า API call ล้มเหลว
|
|
|
|
**วิธีแก้ไข:**
|
|
```typescript
|
|
try {
|
|
await new CallAPI().PostData(req, "/endpoint", data);
|
|
} catch (error) {
|
|
console.error('External API call failed:', error);
|
|
throw new HttpError(HttpStatus.SERVICE_UNAVAILABLE, "External service unavailable");
|
|
}
|
|
```
|
|
|
|
### 7. UserController - Multiple Unhandled forEach Async Operations (5 จุด)
|
|
**ไฟล์:** `UserController.ts`
|
|
|
|
**Methods ที่มีปัญหา:**
|
|
- `createUserImport()` - Line 977-1032
|
|
- `addroleStaffToUser()` - Line 1169-1227
|
|
- `addroleStaffToUserEmp()` - Line 1249-1307
|
|
- `changeUserPasswordAll()` - Line 1133-1148
|
|
- `createUserImportEmp()` - Line 1066-1118
|
|
|
|
**ปัญหา:**
|
|
- ใช้ `for await` loops และ `forEach()` กับ async Keycloak API operations
|
|
- ไม่มี error handling
|
|
- เมื่อ Keycloak operations fail อาจ crash Node.js process
|
|
|
|
---
|
|
|
|
## Controllers ที่มีปัญหามากที่สุด (Top 10)
|
|
|
|
| อันดับ | Controller | จำนวนปัญหา | ระดับสูงสุด |
|
|
|---------|-----------|-------------|--------------|
|
|
| 1 | UserController.ts | 5 | 🔴 CRITICAL |
|
|
| 2 | PermissionController.ts | 7 | 🔴 CRITICAL |
|
|
| 3 | OrgRootController.ts | 4 | 🔴 CRITICAL |
|
|
| 4 | WorkflowController.ts | 2 | 🔴 CRITICAL |
|
|
| 5 | AuthRoleController.ts | 3 | 🔴 CRITICAL |
|
|
| 6 | ProfileSalaryTempController.ts | 4 | 🔴 CRITICAL |
|
|
| 7 | DevelopmentRequestController.ts | 4 | 🟠 HIGH |
|
|
| 8 | EmployeePositionController.ts | 3 | 🟠 HIGH |
|
|
| 9 | ChangePositionController.ts | 3 | 🟠 HIGH |
|
|
| 10 | ProfileController.ts | 2 | 🔴 CRITICAL |
|
|
|
|
---
|
|
|
|
## ประเภทปัญหาที่พบบ่อยที่สุด
|
|
|
|
### 1. Promise.all Without Error Handling (20+ จุด)
|
|
- ใช้ Promise.all โดยไม่มี try-catch
|
|
- ไม่สามารถ handle error ของ individual promises ได้
|
|
- แนะนำ: ใช้ Promise.allSettled หรือ wrap ด้วย try-catch
|
|
|
|
### 2. Missing Error Handling (30+ จุด)
|
|
- Database operations ไม่มี error handling
|
|
- External API calls ไม่มี error handling
|
|
- แนะนำ: เพิ่ม try-catch รอบ operations ทั้งหมด
|
|
|
|
### 3. Async forEach Without Await (10+ จุด)
|
|
- ใช้ forEach กับ async function
|
|
- forEach ไม่รอให้ async operations ทำงานเสร็จ
|
|
- แนะนำ: ใช้ for...of หรือ Promise.all
|
|
|
|
### 4. Unsafe Array Access (8+ จุด)
|
|
- ใช้ .find() แล้วใช้ ! (non-null assertion)
|
|
- อาจทำให้เกิด TypeError
|
|
- แนะนำ: เช็คค่า null/undefined ก่อน
|
|
|
|
### 5. Wrong HTTP Status Codes (5+ จุด)
|
|
- ใช้ NOT_FOUND (404) แทน CONFLICT (409) สำหรับ duplicate data
|
|
- แนะนำ: ใช้ status code ที่ถูกต้องตามมาตรฐาน REST
|
|
|
|
---
|
|
|
|
## แนวทางการแก้ไขแบบ Global
|
|
|
|
### 1. สร้าง Utility Functions
|
|
|
|
```typescript
|
|
// safePromiseAll.ts
|
|
export async function safePromiseAll<T>(
|
|
items: T[],
|
|
executor: (item: T, index: number) => Promise<any>,
|
|
options: {
|
|
continueOnError?: boolean;
|
|
throwOnError?: boolean;
|
|
} = {}
|
|
) {
|
|
const { continueOnError = false, throwOnError = true } = options;
|
|
|
|
if (continueOnError) {
|
|
const results = await Promise.allSettled(
|
|
items.map((item, index) => executor(item, index))
|
|
);
|
|
|
|
const failures = results.filter(r => r.status === 'rejected');
|
|
if (failures.length > 0 && throwOnError) {
|
|
console.warn(`${failures.length} operations failed`);
|
|
}
|
|
|
|
return results;
|
|
} else {
|
|
return Promise.all(
|
|
items.map((item, index) => executor(item, index))
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. สร้าง Transaction Wrapper
|
|
|
|
```typescript
|
|
// withTransaction.ts
|
|
export async function withTransaction<T>(
|
|
operation: (entityManager: EntityManager) => Promise<T>
|
|
): Promise<T> {
|
|
const queryRunner = AppDataSource.createQueryRunner();
|
|
|
|
try {
|
|
await queryRunner.connect();
|
|
await queryRunner.startTransaction();
|
|
|
|
try {
|
|
const result = await operation(queryRunner.manager);
|
|
await queryRunner.commitTransaction();
|
|
return result;
|
|
} catch (error) {
|
|
await queryRunner.rollbackTransaction();
|
|
throw error;
|
|
}
|
|
} finally {
|
|
await queryRunner.release();
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. สร้าง Redis Client Pool
|
|
|
|
```typescript
|
|
// redisService.ts
|
|
export class RedisService {
|
|
private static client: any = null;
|
|
private static reconnects = 0;
|
|
|
|
static async getClient() {
|
|
if (!this.client || !this.client.connected) {
|
|
this.client = await redis.createClient({
|
|
host: REDIS_HOST,
|
|
port: REDIS_PORT,
|
|
retry_strategy: (options) => {
|
|
if (options.total_retry_time > 1000 * 60 * 60) {
|
|
return new Error('Retry time exhausted');
|
|
}
|
|
if (options.attempt > 10) {
|
|
return undefined;
|
|
}
|
|
return Math.min(options.attempt * 100, 3000);
|
|
}
|
|
});
|
|
}
|
|
return this.client;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Global Error Handler Middleware
|
|
|
|
```typescript
|
|
// errorHandler.ts
|
|
export function globalErrorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
|
|
console.error('Unhandled error:', {
|
|
message: err.message,
|
|
stack: err.stack,
|
|
path: req.path,
|
|
method: req.method
|
|
});
|
|
|
|
if (err instanceof HttpError) {
|
|
return res.status(err.statusCode).json({
|
|
error: err.message,
|
|
statusCode: err.statusCode
|
|
});
|
|
}
|
|
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
statusCode: 500
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## ลำดับความสำคัญในการแก้ไข
|
|
|
|
### P0 - Critical (ต้องแก้ทันที)
|
|
1. Redis Connection Leak
|
|
2. Transaction QueryRunner Not Released
|
|
3. Database Operations Without Transactions
|
|
4. UserController Unhandled forEach Operations
|
|
5. Unhandled External API Calls
|
|
|
|
### P1 - High (ควรแก้โดยเร็ว)
|
|
1. Promise.all Without Error Handling
|
|
2. Async forEach Without Proper Error Handling
|
|
3. Unsafe Array Access (Null Reference)
|
|
4. Keycloak Operations Without Error Handling
|
|
|
|
### P2 - Medium (ควรแก้)
|
|
1. Missing Error Handling in Database Queries
|
|
2. QueryBuilder Without Input Validation
|
|
3. External API Calls Without Timeout
|
|
4. Silent Error Swallowing
|
|
|
|
### P3 - Low (แก้เมื่อว่าง)
|
|
1. Wrong HTTP Status Codes
|
|
2. Hardcoded Data
|
|
3. Code Quality Issues
|
|
4. Typos in Status Values
|
|
|
|
---
|
|
|
|
## ไฟล์รายงานทั้งหมด
|
|
|
|
รายงานรายละเอียดแต่ละ Batch อยู่ในโฟลเดอร์ `reports/`:
|
|
|
|
1. [batch-01-controllers-1-10-analysis.md](batch-01-controllers-1-10-analysis.md)
|
|
2. [batch-02-controllers-11-20-analysis.md](batch-02-controllers-11-20-analysis.md)
|
|
3. [batch-03-controllers-21-30-analysis.md](batch-03-controllers-21-30-analysis.md)
|
|
4. [batch-04-controllers-31-40-analysis.md](batch-04-controllers-31-40-analysis.md)
|
|
5. [batch-05-controllers-41-50-analysis.md](batch-05-controllers-41-50-analysis.md)
|
|
6. [batch-06-controllers-51-60-analysis.md](batch-06-controllers-51-60-analysis.md)
|
|
7. [batch-07-controllers-61-70-analysis.md](batch-07-controllers-61-70-analysis.md)
|
|
8. [batch-08-controllers-71-80-analysis.md](batch-08-controllers-71-80-analysis.md)
|
|
9. [batch-09-controllers-81-90-analysis.md](batch-09-controllers-81-90-analysis.md)
|
|
10. [batch-10-controllers-91-100-analysis.md](batch-10-controllers-91-100-analysis.md)
|
|
11. [batch-11-controllers-101-110-analysis.md](batch-11-controllers-101-110-analysis.md)
|
|
12. [batch-12-controllers-111-120-analysis.md](batch-12-controllers-111-120-analysis.md)
|
|
13. [batch-13-controllers-121-130-analysis.md](batch-13-controllers-121-130-analysis.md)
|
|
14. [batch-14-controllers-131-140-analysis.md](batch-14-controllers-131-140-analysis.md)
|
|
|
|
---
|
|
|
|
## บันทึกเพิ่มเติม
|
|
|
|
- **รายงานนี้ครอบคลุม:** ทุก 140 Controllers ในโปรเจคต์
|
|
- **วันที่สร้างรายงาน:** 8 พฤษภาคม 2568
|
|
- **เครื่องมือที่ใช้:** การวิเคราะห์ Code และ Pattern Recognition
|
|
- **ข้อจำกัด:** บางไฟล์มีขนาดใหญ่มาก (>300KB) ทำให้ตรวจสอบได้เพียงบางส่วน
|
|
|
|
---
|
|
|
|
**รายงานนี้ถูกสร้างโดย AI Code Review System**
|
|
**สำหรับ BMA EHR Organization Project**
|