report: Controllers
This commit is contained in:
parent
7104ce4f34
commit
85e9be08f6
15 changed files with 10752 additions and 0 deletions
248
reports/batch-07-controllers-61-70-analysis.md
Normal file
248
reports/batch-07-controllers-61-70-analysis.md
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# รายงานการวิเคราะห์จุดเสี่ยง 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 ในทันที** แต่ควรปรับปรุงตามคำแนะนำเพื่อเพิ่มความเสถียรของระบบในระยะยาว
|
||||
Loading…
Add table
Add a link
Reference in a new issue