2way-batch — Core Pipeline
บทนี้อธิบาย lifecycle ของ cron ใน 2way-batch (branch: master) และโค้ด business ที่ยังอยู่แต่ไม่ได้ถูกเรียก
ดูแผนที่ module ที่ 01-repository-map และ persistence ที่ 03-persistence-integrations
Cron lifecycle ภาพรวม
NestJS process boot (main.ts)
│
▼
ScheduleModule.forRoot() (app.module.ts:43)
│
▼
SurveyTaskService constructed
│ inject EntityManager('main'), LineService, SurveysService, SurveysRepository
│ survey.task.service.ts:24-30
│
│ (side effect) SurveysService ctor → SurveysUtil.initializer
│ surveys.service.ts:21-27
▼
Every 10 seconds:
@Cron(CronExpression.EVERY_10_SECONDS)
handleCron()
│ survey.task.service.ts:32-37
├─► fetchCreateInScope() ← เรียกจริง (stub log only)
├─► // fetchCreateOutScope() ← comment ปิด
└─► // fetchApproval() ← comment ปิด@Cron(CronExpression.EVERY_10_SECONDS)
handleCron() {
this.fetchCreateInScope();
// this.fetchCreateOutScope();
// this.fetchApproval();
}แหล่ง: survey.task.service.ts:32-37
Path ที่ live — fetchCreateInScope (stub)
private async fetchCreateInScope() {
this.logger.log('fetchCreateInScope.....Test print out for monitor logging.');
}แหล่ง: survey.task.service.ts:39-41
ไม่มี query DB, ไม่มี LINE push, ไม่มีอัปเดตสถานะ — เป็น monitor heartbeat ที่พิมพ์ log ทุก 10 วินาที
ผลกระทบ ops: log volume สูง (~8640 บรรทัด/วันต่อ instance ถ้าไม่ aggregate) โดยไม่มีงานธุรกิจ — PERF-2WAYBATCH-01
ชื่อ method สื่อว่าเคยตั้งใจทำ "create survey in-scope" notification (status short name ACS ใน SurveysConstant.SHORT_NAME_IN_SCOPE, surveys.constant.ts:5) แต่ implementation ถูกแทนด้วย stub
Path ที่ตาย — fetchApproval (logic ครบแต่ไม่ถูกเรียก)
ตั้งใจ: หาแบบสำรวจที่ HR admin อนุมัติแล้ว → push LINE หาผู้ถูกมอบหมาย → อัปเดต action เป็น assignee received
Query
- Subquery:
MAX(h2.seq_no)จากTContentFlowHistoryjoinMContentFlowที่short_name = HAP(SHORT_NAME_HR_ADMIN_APPROVED) (survey.task.service.ts:44-50,69) - Main query: distinct content id + topic + start/end date จาก
TContentjoin type/assign/status/history (survey.task.service.ts:56-71) - Filter
t.name_en = 'survey'และ status code = HR approved action code (survey.task.service.ts:65-68)
Action code มาจาก SurveysUtil.getActionCodeByName(HR_ADMIN_APPROVED) ซึ่งอ่านจาก in-memory map ที่ initializer โหลดจากตาราง m_status (surveys.util.ts:25-42,125-128) — ถ้า map ยังว่างจะได้ runtime error เมื่อเปิด method นี้
Per-row side effects
for each survey row:
findAllContentAssignBySurveyId(id)
for each assignee:
findLineIDAsEmpCode(empCode)
line.pushMassage({ lineId, messages })
surveysService.updateSurveys({
surveyId, action: ASSIGNEE_RECEIVED,
contentFlowHis: { empCode: '2wayadm', userName: 'Batch Job2', ... }
})แหล่ง: survey.task.service.ts:73-110
ข้อความ LINE template:
ทีม VTRC Support ขออนุญาตแจ้งเตือน การสร้างแบบสำรวจใหม่ในระบบ หัวข้อ $1 เลขงาน $2แหล่ง: survey.task.service.ts:76 — แทน $1 = topicName, $2 = 'S' + id
จุดอ่อนในโค้ดที่ยังไม่รัน (แต่จะเป็นปัญหาถ้าเปิดกลับ)
| ประเด็น | รายละเอียด | บรรทัด |
|---|---|---|
data.map(async ...) ไม่ await | fire-and-forget promises | 73 |
if (!userLine) return ใน for-of | ออกจาก iteration ของ assignee เมื่อไม่มี LINE id | 81 |
this.line.pushMassage(dto) ไม่ await | คืน Observable จาก HttpService — ไม่ subscribe ใน caller | 89 |
updateSurveys ไม่ await | race กับรอบ cron ถัดไป | 109 |
| Hardcoded org metadata | empCode: '2wayadm', org/divi/dept ชื่อ "ประมวลผล เพื่อส่ง line..." | 97-106 |
Path ที่ตาย — fetchCreateOutScope (logic ครบแต่ไม่ถูกเรียก)
ตั้งใจ: หาแบบสำรวจที่สร้างแบบ out-of-scope → แจ้ง HR admin ผ่าน LINE → อัปเดตเป็น HR admin received
Query
คล้าย fetchApproval แต่:
- flow short_name =
ACD(SHORT_NAME_OUT_SCOPE) (survey.task.service.ts:120,135) - status code =
CREATE_CODE(S00) (survey.task.service.ts:134,surveys.constant.ts:8)
Side effects
dao.findEmployeeCodeOfHrAdmin()→ config keylist.empCode.hrAdmin(surveys.constant.ts:10, ใช้ที่survey.task.service.ts:138, reposurveys.repository.ts:587-589)- แยก empCode ด้วย comma →
findLineIDAsEmpCode→line.pushMassageด้วยmessages: item.topicNameเท่านั้น (survey.task.service.ts:143-151) — ไม่มี template ประโยคเต็มแบบ fetchApproval updateSurveysactionHR_ADMIN_RECEIVED, userName'Batch Job1'(survey.task.service.ts:154-170)
ปัญหาเดียวกันเรื่อง map / ไม่ await ชั้นนอก — แต่ชั้น arrEmpCodeHrAdmin.map มี await this.line.pushMassage ภายใน callback (151) ซึ่งยังไม่ทำให้ outer map รอครบ
LINE push จาก batch — hardcode IP chain
เมื่อ (ถ้า) เรียก LineService.pushMassage:
const data = {
replyToken: body.lineId,
messages: body.messages,
};
return this.http
.post('http://10.188.128.126:9340/2way-batch/api/v1/line', data)แหล่ง: line.service.ts:20-26
SurveyTaskService
│ LinePushMsgDto { lineId, messages }
▼
LineService.pushMassage
│ remap → { replyToken: lineId, messages }
│ POST http://10.188.128.126:9340/2way-batch/api/v1/line
▼
(?) process ที่ฟัง :9340 path /2way-batch/api/v1/line
│ ตาม nginx: location /2way-batch/ → :9340
│ แต่ AppModule ปัจจุบันไม่ mount LineController
▼
คาดหวังต่อ: gateway ไป LINE Messaging APIห้ามสรุป ว่าปลายทางคือ 2way-line-service โดยอัตโนมัติ — 2way-line-service ฟังพอร์ต 8200 ตาม .env.example และ path เป็น /api/v1/Line (ตัว L ใหญ่ใน controller) ไม่ใช่ /2way-batch/api/v1/line บน 9340
การคาดสถานะ HTTP success ใน batch เป็น 201 (line.service.ts:29) — ต้องตรงกับสิ่งที่ปลายทางคืนจริง
pushMassage คืน RxJS Observable จาก this.http.post(...).pipe(...) (line.service.ts:25-41) — caller ใน cron ไม่ได้ subscribe / ไม่ได้แปลงเป็น Promise → แม้ uncomment ก็อาจไม่ยิง request จริงถ้าไม่มี subscriber
updateSurveys ที่ cron จะเรียก (ถ้าเปิด)
SurveysService.updateSurveys (surveys.service.ts:222-291):
- ตรวจ action code ผ่าน
SurveysUtil.isActionCodeExisting - ถ้าเป็น assignee read → อัปเดต
read_dateบนt_content_assign - ไม่ใช่ → ตรวจ flow step, กัน duplicate short_name, อัปเดตผ่าน
dao.updateSurvey
Batch ส่ง action จาก ASSIGNEE_RECEIVED / HR_ADMIN_RECEIVED (survey.task.service.ts:95,156) — ต้องมี mapping ใน m_status ที่ initializer โหลดไว้แล้ว
HTTP surface ที่ live จริง
| Surface | สถานะ |
|---|---|
AppController | ว่าง — ไม่มี route |
| Controllers ใน announces/employees/surveys/line/upload | ไม่ mount (module comment) |
| Swagger UI | mount ที่ ${BASE_PATH}docs ถ้า process รัน |
| Global JwtAuthGuard | active แต่ไม่มี @Public controller live ให้เรียกธุรกิจ |
| Cron | active ทุก 10 วินาที |
สรุป: attack surface ผ่าน HTTP สำหรับ business API ใกล้ศูนย์ในสถานะ wiring ปัจจุบัน — ความเสี่ยงหลักอยู่ที่ resource waste + ถ้ามีคน uncomment modules โดยไม่ review @Public()
เปรียบเทียบกับ cron ใน 2way-api
2way-api มี SurveyTaskModule / AnnounceTaskModule / ComplaintTaskModule ที่ wire ใน AppModule และ schedule เวลาอื่น (ดูเอกสาร 2way-api) — 2way-batch เป็น repo แยกที่ตั้งใจรัน survey batch แต่ปัจจุบันลดเหลือ stub
ห้ามสมมติว่าการเปิด fetchApproval ใน batch จะซ้ำหรือไม่ซ้ำกับ cron ใน 2way-api โดยไม่เทียบ status codes และ environment DB
ลำดับการเปิด logic กลับ (ถ้าทีมต้องการ)
- ยืนยันว่า
2way-apicron ไม่ได้ทำงานเดียวกันอยู่แล้วบน DB เดียวกัน - แก้
LineServiceให้ชี้ env URL ของ LINE gateway ที่ถูกต้อง (และ mount หรือเรียก2way-line-service) - แปลง
pushMassageให้ await ได้จริง (Promise/firstValueFrom) แล้วใส่awaitใน cron - ใส่
await/ กัน overlapping cron (mutex หรือ schedule ที่ห่างขึ้น) - Uncomment ทีละ method ใน
handleCronพร้อมลดความถี่จาก 10 วินาที - ทดสอบบน UAT ด้วย survey ตัวอย่างหนึ่งรายการ
สิ่งที่อยู่นอกขอบเขตบทนี้
- รายการ entity เต็ม / nginx → 01-repository-map
- ตาราง + config + LINE URL → 03-persistence-integrations
- Debt IDs → 04-scorecard