# รายงานการวิเคราะห์จุดเสี่ยง Unhandled Exception - Controllers ชุดที่ 6 (51-60) ## วันที่วิเคราะห์: 2026-05-08 ## สรุปผลการวิเคราะห์ จากการตรวจสอบ Controllers ทั้ง 10 ไฟล์ (51-60): 1. ProfileAbilityEmployeeController 2. ProfileAbilityEmployeeTempController 3. ProfileAbsentLateController 4. ProfileActpositionController 5. ProfileActpositionEmployeeController 6. ProfileActpositionEmployeeTempController 7. ProfileAddressController 8. ProfileAddressEmployeeController 9. ProfileAddressEmployeeTempController 10. ProfileAssessmentsController พบ **0 จุดเสี่ยงระดับวิกฤต** ที่อาจทำให้เกิด Unhandled Exception และ Crash Loop ในระบบ Microservices --- ## รายละเอียดจุดเสี่ยงที่พบ ### ไม่พบจุดเสี่ยงระดับวิกฤต Controllers ทั้งหมดในชุดนี้มีการจัดการ Error ที่ดี โดย: 1. **ทุก Method ใช้ async/await อย่างถูกต้อง** - ไม่มี Promise ที่ถูกเรียกโดยไม่มี await 2. **มีการ throw HttpError** - เมื่อเกิด Error จะ throw HttpError ที่มี Status Code ที่ชัดเจน 3. **Database Operations ล้วนอยู่ใน try-catch โดยนัย** - TypeORM repositories มีการ handle error ภายใน 4. **ใช้ Promise.all อย่างปลอดภัย** - ใน operations ที่ต้องบันทึกข้อมูลหลายจุดพร้อมกัน --- ## จุดที่ควรปรับปรุง (แนะนำ) แม้จะไม่พบจุดเสี่ยงระดับวิกฤต แต่มีจุดที่ควรปรับปรุงเพื่อเพิ่มความแข็งแกร่งของระบบ: ### 1. File: ProfileAbilityEmployeeController.ts, ProfileAbilityEmployeeTempController.ts, ProfileActpositionEmployeeController.ts, ProfileActpositionEmployeeTempController.ts **Method:** `detailProfileAbilityUser`, `detailProfileActpositionUser` **Problem Type:** 2. Missing Error Handle (Potential Null Reference) **Root Cause:** ```typescript // Lines 42-48 const getProfileAbilityId = await this.profileAbilityRepo.find({ where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAbilityId) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } ``` `find()` method จะ return empty array `[]` เมื่อไม่พบข้อมูล ไม่ใช่ `null` หรือ `undefined` ดังนั้น condition `!getProfileAbilityId` จะไม่เคยเป็น true **Recommended Fix:** ```typescript const getProfileAbilityId = await this.profileAbilityRepo.find({ where: { profileEmployeeId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (getProfileAbilityId.length === 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } // หรือถ้าต้องการให้ return empty array ได้ return new HttpSuccess(getProfileAbilityId); ``` --- ### 2. File: ProfileAbsentLateController.ts **Method:** `newAbsentLateBatch` **Problem Type:** 2. Missing Error Handle (Transaction Safety) **Root Cause:** ```typescript // Lines 159-168 const result = await this.absentLateRepo.save(records, { data: req }); // บันทึก history สำหรับแต่ละ record const historyRecords = result.map((data) => { const history = new ProfileAbsentLateHistory(); Object.assign(history, { ...data, id: undefined }); history.profileAbsentLateId = data.id; return history; }); await this.historyRepo.save(historyRecords, { data: req }); ``` ถ้าการบันทึก history ล้มเหลว ข้อมูลหลัก (records) จะถูกบันทึกไปแล้ว ทำให้เกิด Data Inconsistency **Recommended Fix:** ```typescript // ใช้ Transaction หรือ wrap ด้วย try-catch try { const result = await this.absentLateRepo.save(records, { data: req }); const historyRecords = result.map((data) => { const history = new ProfileAbsentLateHistory(); Object.assign(history, { ...data, id: undefined }); history.profileAbsentLateId = data.id; return history; }); await this.historyRepo.save(historyRecords, { data: req }); return new HttpSuccess({ count: result.length, ids: result.map((r) => r.id) }); } catch (error) { // ถ้าเกิด error ควร rollback หรือลบข้อมูลที่บันทึกไปแล้ว // หรือใช้ Transaction ของ TypeORM throw new HttpError(HttpStatus.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดในการบันทึกข้อมูล"); } ``` --- ### 3. File: ProfileActpositionController.ts **Method:** `getProfileActpositionHistory` **Problem Type:** 2. Missing Error Handle (Potential Null Reference in Relations) **Root Cause:** ```typescript // Lines 95-104 const record = await this.profileActpositionHistoryRepo.find({ relations: ["histories"], where: { profileActpositionId: actpositionId }, order: { createdAt: "DESC" }, }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } const mappedRecords = record.map(history => { const firstHistory = history.histories ?? []; ``` มีการใช้ `relations: ["histories"]` แต่ไม่มีการตรวจสอบว่า relation นี้มีอยู่จริงใน Entity หรือไม่ ถ้า relation ไม่ถูกต้องอาจเกิด error **Recommended Fix:** ```typescript try { const record = await this.profileActpositionHistoryRepo.find({ relations: ["histories"], where: { profileActpositionId: actpositionId }, order: { createdAt: "DESC" }, }); if (record.length === 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } const mappedRecords = record.map(history => { const firstHistory = Array.isArray(history.histories) ? history.histories[0] : null; return { // ... rest of mapping }; }); return new HttpSuccess(mappedRecords); } catch (error) { if (error instanceof HttpError) throw error; throw new HttpError(HttpStatus.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาดในการดึงข้อมูล"); } ``` --- ### 4. All Controllers **Method:** ทุก Method ที่ใช้ `Promise.all` **Problem Type:** 2. Missing Error Handle (Partial Failure) **Root Cause:** ```typescript // Pattern ที่ใช้ในหลาย ๆ Controller await Promise.all([ this.profileRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), this.historyRepo.save(history, { data: req }), ]); ``` ถ้า `setLogDataDiff` หรือ `historyRepo.save` ล้มเหลว แต่ `profileRepo.save` สำเร็จ จะเกิด Data Inconsistency **Recommended Fix:** ```typescript // ใช้ Transaction ของ TypeORM แทน await AppDataSource.transaction(async (transactionalEntityManager) => { await transactionalEntityManager.save(Profile, record); await transactionalEntityManager.save(ProfileHistory, history); // setLogDataDiff ควรอยู่นอก transaction หรือ handle error แยก }); setLogDataDiff(req, { before, after: record }); ``` --- ## สรุปคำแนะนำการแก้ไข ### ระดับความสำคัญ: สูง 1. **ใช้ Transaction สำหรับ Operations ที่ต้องบันทึกข้อมูลหลายตาราง** - เพื่อป้องกัน Data Inconsistency 2. **ตรวจสอบค่าที่ return จาก `find()` อย่างถูกต้อง** - ใช้ `.length === 0` แทน `!result` ### ระดับความสำคัญ: ปานกลาง 1. **เพิ่ม Error Boundary หรือ Global Error Handler** - เพื่อจัดการ error ที่ไม่คาดคิด 2. **Log error ที่เกิดขึ้น** - เพื่อช่วยในการ Debug และ Monitor ### ระดับความสำคัญ: ต่ำ 1. **Refactor code ให้ใช้ Transaction Manager** - เพื่อให้ code สะอาดและปลอดภัยมากขึ้น --- ## การจัดการ Error ที่ดีที่สุดสำหรับ Microservices ```typescript // 1. ใช้ AsyncHandler Wrapper export const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; // 2. ใช้ Global Error Handler app.use((error: Error, req: Request, res: Response, next: NextFunction) => { console.error('Unhandled error:', error); res.status(500).json({ error: 'Internal server error' }); }); // 3. ใช้ Transaction สำหรับ Database Operations await AppDataSource.transaction(async (manager) => { // All database operations here }); ``` --- ## สรุป Controllers ในชุดที่ 6 (51-60) มีความเสี่ยงต่ำต่อการเกิด **Unhandled Exception** ที่จะทำให้ Service Crash แต่มีจุดที่ควรปรับปรุงเพื่อ: 1. **ป้องกัน Data Inconsistency** - โดยการใช้ Transaction 2. **ปรับปรุง Logic การตรวจสอบข้อมูล** - โดยการเช็ค length ของ array ที่ return จาก find() 3. **เพิ่มความแข็งแกร่งของระบบ** - โดยการเพิ่ม Error Handling และ Logging **ไม่มีจุดเสี่ยงระดับวิกฤตที่จะทำให้เกิด Crash Loop ในทันที** แต่ควรปรับปรุงตามคำแนะนำเพื่อเพิ่มความเสถียรของระบบในระยะยาว