Skip to main content

หน้าขอบคุณและการสลับ 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 ระดับฟอร์ม

  1. Publish payload {lineOaId, type:"setRichMenuMemberByLineUserId", userType:"member", userId} เข้าคิว line_change_richmenu

  2. Error ของการ publish นี้ ถูก propagate ออกไป ทำให้ request ล้มเหลว ซึ่งต่างจากกลไกที่ 2 ที่เป็น best-effort ล้วน

  3. จากนั้นสั่ง 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

  1. อ่าน jsonb form_builder.thank_you อย่างระมัดระวัง — ต้องมี mode == "custom", action.type == "rich_menu" และ action.rich_menu_id เป็นจำนวนเต็มตั้งแต่ 1 ขึ้นไป หากไม่ครบจะบันทึก warning แล้วข้าม เนื่องจากคอลัมน์นี้เป็น raw JSON ที่อาจถูกเขียนก่อนที่ CMS จะมี validator
  2. ต้องมี lineUserId — CMS บังคับ require_line_login ให้แล้วตอนบันทึกฟอร์ม ส่วนขั้นนี้เป็นเข็มขัดนิรภัยตอน runtime
  3. หาก fb.LineOaID น้อยกว่าหรือเท่ากับ 0 จะบันทึก warning แล้วข้าม
  4. ตรวจสอบความเป็นเจ้าของซ้ำในขั้นตอน 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
  5. Publish payload {type:"setRichMenuByTriggerRule", lineOaId, userId, richMenuId} เข้าคิวเดียวกัน — ที่ใช้ setRichMenuByTriggerRule แทน setRichMenuMemberByLineUserId เพราะ handler ตัวหลังจะ ไม่สนใจ id ที่ส่งมา และไปเลือกเมนูชนิด member ของ OA ด้วย query ของตัวเอง
  6. ความล้มเหลวทุกกรณีเป็นเพียง warning เพราะคำตอบถูกบันทึกไปเรียบร้อยแล้ว ผู้ใช้ไม่ได้ทำอะไรผิด และการสลับเมนูถือเป็นของแถม
  7. กลไกนี้ ไม่แตะ 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.gothankYouRichMenuID(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 ผ่าน handler setRichMenuMemberByLineUserId และ setRichMenuByTriggerRule
  • ฐานข้อมูล — ตาราง rich_menu (ตรวจความเป็นเจ้าของ), line_user (คอลัมน์ user_type) และ form_builder (jsonb thank_you, ธง convert_to_member และ require_line_login)
  • ฟีเจอร์ที่เกี่ยวข้อง — ถูกเรียกจากขั้นตอน form submission ส่วนค่า config ทั้งหมดมาจากหน้า Form Builder ใน CMS
  • client-web — ตรงกับฟีเจอร์ form-thank-you และเกี่ยวข้องกับ form-fill