หน้าขอบคุณและการสลับ Rich Menu หลังส่งฟอร์ม
ภาพรวม
Side effect ที่เกิดขึ้น หลังบันทึกคำตอบสำเร็จ และเป็นกลไกที่ทำให้ฟอร์มกลายเป็นเครื่องมือแปลง "คนที่เข้ามาดูเฉย ๆ" ให้เป็น "สมาชิก" ได้แก่ การสลับ rich menu ของผู้ใช้ และ/หรือการเปลี่ยน line_user.user_type จาก guest เป็น member
จุดออกแบบที่สำคัญคือ การสลับ rich menu ถูก trigger จากฝั่ง server เมื่อ submit สำเร็จ ไม่ใช่จากการกดปุ่มบนหน้าขอบคุณ เพราะหากปล่อยให้ client เรียกเอง ใครก็ตามที่มี URL ของฟอร์มจะสามารถ POST เพื่อเปลี่ยน rich menu ของตัวเองได้
Business Flow
ระบบมี 2 กลไกที่ เป็นอิสระต่อกัน และสามารถทำงานพร้อมกันได้ในการ submit ครั้งเดียว ผลคือจะมี 2 message ในคิวเดียวกัน โดยข้อความที่ถูกประมวลผลหลังสุดจะเป็นผู้กำหนดเมนูของผู้ใช้ ลำดับนี้ ไม่มีการรับประกัน และ CMS ระบุข้อจำกัดนี้ไว้ให้ผู้ตั้งค่าทราบแล้ว
กลไกที่ 1 — ธง convert_to_member ระดับฟอร์ม
-
Publish payload
{lineOaId, type:"setRichMenuMemberByLineUserId", userType:"member", userId}เข้าคิวline_change_richmenu -
Error ของการ publish นี้ ถูก propagate ออกไป ทำให้ request ล้มเหลว ซึ่งต่างจากกลไกที่ 2 ที่เป็น best-effort ล้วน
-
จากนั้นสั่ง
UPDATE line_user SET user_type='member'เฉพาะเมื่อผู้ใช้ยังไม่ได้เป็น member โดยเป็น best-effort หากล้มเหลวจะบันทึก log เท่านั้นเหตุผลที่ต้องมี UPDATE นี้: field
userTypeใน payload ไม่เคยมี worker ตัวใดอ่าน ทั้งฝั่ง NestJS และ Go ผลลัพธ์เดิมคือ rich menu เปลี่ยนแต่user_typeยังเป็นguestซึ่งเป็น QA defect ที่แก้ด้วยการอัปเดตตรงจุดนี้
กลไกที่ 2 — thank_you.action ชนิด rich_menu
ตั้งค่าได้ต่อฟอร์มในหน้า thank-you ของ CMS
- อ่าน jsonb
form_builder.thank_youอย่างระมัดระวัง — ต้องมีmode == "custom",action.type == "rich_menu"และaction.rich_menu_idเป็นจำนวนเต็มตั้งแต่ 1 ขึ้นไป หากไม่ครบจะบันทึก warning แล้วข้าม เนื่องจากคอลัมน์นี้เป็น raw JSON ที่อาจถูกเขียนก่อนที่ CMS จะมี validator - ต้องมี
lineUserId— CMS บังคับrequire_line_loginให้แล้วตอนบันทึกฟอร์ม ส่วนขั้นนี้เป็นเข็มขัดนิรภัยตอน runtime - หาก
fb.LineOaIDน้อยกว่าหรือเท่ากับ 0 จะบันทึก warning แล้วข้าม - ตรวจสอบความเป็นเจ้าของซ้ำในขั้นตอน publish — ขั้นนี้เป็นด่านเดียวบนเส้นทางนี้ เพราะ handler ฝั่ง worker ค้น rich menu โดยไม่กรอง
line_oa_idแล้วผูกด้วย token ของ OA ที่ระบุใน payload ระบบจึงตรวจด้วยSELECT 1 FROM rich_menu WHERE id=$1 AND line_oa_id=$2 AND deleted_date IS NULL AND status='active'โดย$2มาจาก OA ของ ตัวฟอร์มเอง ไม่ใช่จาก request - Publish payload
{type:"setRichMenuByTriggerRule", lineOaId, userId, richMenuId}เข้าคิวเดียวกัน — ที่ใช้setRichMenuByTriggerRuleแทนsetRichMenuMemberByLineUserIdเพราะ handler ตัวหลังจะ ไม่สนใจ id ที่ส่งมา และไปเลือกเมนูชนิด member ของ OA ด้วย query ของตัวเอง - ความล้มเหลวทุกกรณีเป็นเพียง warning เพราะคำตอบถูกบันทึกไปเรียบร้อยแล้ว ผู้ใช้ไม่ได้ทำอะไรผิด และการสลับเมนูถือเป็นของแถม
- กลไกนี้ ไม่แตะ
line_user.user_typeและต้องไม่แตะ เพราะหน้าที่ของมันคือเปลี่ยนเมนู ไม่ใช่เปลี่ยนสถานะสมาชิก
ข้อมูลที่หน้าขอบคุณฝั่งเว็บใช้
ค่า thank_you ทั้งก้อนถูกส่งกลับมาใน formBuilderInfo ของ response จาก Submit และจาก GET /form-builder/:hash อยู่แล้ว หน้าเว็บจึง render ปุ่มชนิด close, oa_chat, url และ countdown ปิดหน้าอัตโนมัติได้เอง โดย botBasicId สำหรับปุ่ม oa_chat มาจากขั้นตอน LINE OA lookup
ไฟล์และฟังก์ชันหลัก
ฟีเจอร์นี้ไม่มี route ของตัวเอง — เป็นขั้นตอนที่ 10 ของกระบวนการ Submit
| ไฟล์ | ฟังก์ชัน |
|---|---|
internal/formsubmission/thankyou.go | thankYouRichMenuID(raw), (*Service).publishThankYouRichMenu, (*Service).warn |
internal/formsubmission/service.go | บล็อก convertToMember(fb) ภายใน Submit (publish และ UPDATE line_user), helper convertToMember, ptrStringOrNil |
internal/formsubmission/register.go | กำหนดชื่อคิว richMenuQueue ค่าเริ่มต้นคือ line_change_richmenu |
จุดเชื่อมต่อกับ Service อื่น
- RabbitMQ — คิว
line_change_richmenuโดยผู้บริโภคคือ line-management-worker-go ผ่าน handlersetRichMenuMemberByLineUserIdและsetRichMenuByTriggerRule - ฐานข้อมูล — ตาราง
rich_menu(ตรวจความเป็นเจ้าของ),line_user(คอลัมน์user_type) และform_builder(jsonbthank_you, ธงconvert_to_memberและrequire_line_login) - ฟีเจอร์ที่เกี่ยวข้อง — ถูกเรียกจากขั้นตอน form submission ส่วนค่า config ทั้งหมดมาจากหน้า Form Builder ใน CMS
- client-web — ตรงกับฟีเจอร์
form-thank-youและเกี่ยวข้องกับform-fill