# รายงานการวิเคราะห์จุดเสี่ยง Unhandled Exception - Controllers ชุดที่ 7 (61-70) ## วันที่วิเคราะห์: 2026-05-08 ## สรุปผลการวิเคราะห์ จากการตรวจสอบ Controllers ทั้ง 10 ไฟล์ (61-70): 1. ProfileAssistanceController 2. ProfileAssistanceEmployeeController 3. ProfileAssistanceEmployeeTempController 4. ProfileCertificateController 5. ProfileCertificateEmployeeController 6. ProfileCertificateEmployeeTempController 7. ProfileChildrenController 8. ProfileChildrenEmployeeController 9. ProfileChildrenEmployeeTempController 10. ProfileDisciplineController พบ **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: ProfileAssistanceController.ts, ProfileAssistanceEmployeeController.ts, ProfileAssistanceEmployeeTempController.ts **Method:** `detailProfileAssistanceUser`, `detailProfileAssistance`, `getProfileAssistanceHistory`, `getProfileAdminAssistanceHistory` **Problem Type:** 2. Missing Error Handle (Logic Issue) **Root Cause:** ```typescript // Lines 42-48 (ProfileAssistanceController.ts) const getProfileAssistanceId = await this.profileAssistanceRepo.find({ where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (!getProfileAssistanceId) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } ``` `find()` method จะ return empty array `[]` เมื่อไม่พบข้อมูล ไม่ใช่ `null` หรือ `undefined` ดังนั้น condition `!getProfileAssistanceId` จะไม่เคยเป็น true **Recommended Fix:** ```typescript const getProfileAssistanceId = await this.profileAssistanceRepo.find({ where: { profileId: profile.id, isDeleted: false }, order: { createdAt: "ASC" }, }); if (getProfileAssistanceId.length === 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } // หรือถ้าต้องการให้ return empty array ได้ return new HttpSuccess(getProfileAssistanceId); ``` --- ### 2. File: ProfileCertificateController.ts, ProfileCertificateEmployeeController.ts **Method:** `deleteCertificate` **Problem Type:** 2. Missing Error Handle (Logic Error) **Root Cause:** ```typescript // Lines 226-228 (ProfileCertificateController.ts) if (certificateResult.affected && certificateResult.affected <= 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } ``` Logic ผิด เพราะ `certificateResult.affected && certificateResult.affected <= 0` จะเป็น false เมื่อ affected = 0 (เนื่องจาก 0 ถือเป็น falsy value) ทำให้ไม่เคย throw error **Recommended Fix:** ```typescript if (certificateResult.affected === undefined || certificateResult.affected <= 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } ``` --- ### 3. All Controllers **Method:** ทุก Method ที่ใช้ `Promise.all` **Problem Type:** 2. Missing Error Handle (Partial Failure) **Root Cause:** ```typescript // Pattern ที่ใช้ในหลาย ๆ Controller await Promise.all([ this.profileAssistanceRepo.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), this.profileAssistanceHistoryRepo.save(history, { data: req }), ]); ``` ถ้า `setLogDataDiff` หรือ `historyRepo.save` ล้มเหลว แต่ `profileAssistanceRepo.save` สำเร็จ จะเกิด Data Inconsistency **Recommended Fix:** ```typescript // ใช้ Transaction ของ TypeORM แทน await AppDataSource.transaction(async (transactionalEntityManager) => { await transactionalEntityManager.save(ProfileAssistance, record); await transactionalEntityManager.save(ProfileAssistanceHistory, history); }); setLogDataDiff(req, { before, after: record }); ``` --- ### 4. File: ProfileChildrenController.ts, ProfileChildrenEmployeeController.ts, ProfileChildrenEmployeeTempController.ts **Method:** `newChildren`, `editChildren` **Problem Type:** 2. Missing Error Handle (Unhandled Extension Function) **Root Cause:** ```typescript // Lines 96, 125 (ProfileChildrenController.ts) data.childrenCitizenId = Extension.CheckCitizen(String(data.childrenCitizenId)); ``` ถ้า `Extension.CheckCitizen()` มีการ throw error จะทำให้เกิด Unhandled Exception **Recommended Fix:** ```typescript try { data.childrenCitizenId = Extension.CheckCitizen(String(data.childrenCitizenId)); } catch (error) { throw new HttpError(HttpStatus.BAD_REQUEST, "รูปแบบเลขบัตรประชาชนไม่ถูกต้อง"); } ``` --- ### 5. File: ProfileDisciplineController.ts **Method:** `editDiscipline` **Problem Type:** 2. Missing Error Handle (Inconsistent Code Pattern) **Root Cause:** ```typescript // Lines 166-173 (ProfileDisciplineController.ts) // await Promise.all( this.disciplineRepository.save(record, { data: req }); setLogDataDiff(req, { before, after: record }); if (!(Object.keys(body).length === 1 && body.isUpload)) { this.disciplineHistoryRepository.save(history, { data: req }); // setLogDataDiff(req, { before, after: history }); } // ); ``` มีการ comment out `Promise.all` แต่ยังคงเรียก `save()` โดยไม่มี await ในบางจุด ซึ่งอาจทำให้เกิด race condition **Recommended Fix:** ```typescript await Promise.all([ this.disciplineRepository.save(record, { data: req }), setLogDataDiff(req, { before, after: record }), ...(Object.keys(body).length === 1 && body.isUpload ? [] : [this.disciplineHistoryRepository.save(history, { data: req })]), ]); ``` --- ## สรุปคำแนะนำการแก้ไข ### ระดับความสำคัญ: สูง 1. **แก้ไข Logic การตรวจสอบผลลัพธ์จาก `find()`** - ใช้ `.length === 0` แทน `!result` 2. **แก้ไข Logic การตรวจสอบ `affected`** - ใช้ `=== undefined || <= 0` แทน `&& <= 0` 3. **ใช้ Transaction สำหรับ Operations ที่ต้องบันทึกข้อมูลหลายตาราง** - เพื่อป้องกัน Data Inconsistency ### ระดับความสำคัญ: ปานกลาง 1. **เพิ่ม Error Handling รอบ ๆ Extension Functions** - เพื่อป้องกัน Unhandled Exception 2. **ทำให้ Pattern การใช้ Promise/await สอดคล้องกัน** - หลีกเลี่ยงการเรียก save() โดยไม่มี await ### ระดับความสำคัญ: ต่ำ 1. **Refactor code ให้ใช้ Transaction Manager** - เพื่อให้ code สะอาดและปลอดภัยมากขึ้น 2. **เพิ่ม Error Boundary หรือ Global Error Handler** - เพื่อจัดการ error ที่ไม่คาดคิด --- ## การจัดการ 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 }); // 4. ตรวจสอบผลลัพธ์จาก find() อย่างถูกต้อง const results = await repo.find({ where: condition }); if (results.length === 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } // 5. ตรวจสอบ affected อย่างถูกต้อง const result = await repo.delete({ id }); if (result.affected === undefined || result.affected <= 0) { throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล"); } ``` --- ## สรุป Controllers ในชุดที่ 7 (61-70) มีความเสี่ยงต่ำต่อการเกิด **Unhandled Exception** ที่จะทำให้ Service Crash แต่มีจุดที่ควรปรับปรุงเพื่อ: 1. **ป้องกัน Logic Errors** - โดยการตรวจสอบผลลัพธ์จาก `find()` และ `affected` อย่างถูกต้อง 2. **ป้องกัน Data Inconsistency** - โดยการใช้ Transaction 3. **เพิ่มความแข็งแกร่งของระบบ** - โดยการเพิ่ม Error Handling รอบ ๆ Extension Functions **ไม่มีจุดเสี่ยงระดับวิกฤตที่จะทำให้เกิด Crash Loop ในทันที** แต่ควรปรับปรุงตามคำแนะนำเพื่อเพิ่มความเสถียรของระบบในระยะยาว