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