Skip to main content

กระดานสนทนา (Forum)

ภาพรวม

แอปเสริม กระดานข่าวและเว็บบอร์ด ที่ทำงานใน LIFF ให้เพื่อน LINE ตั้งกระทู้ ตอบกลับ กดอิโมจิ และรายงานเนื้อหาได้ พร้อมเครื่องมือ moderation ฝั่ง CMS สำหรับอนุมัติ ซ่อนโพสต์ และบล็อกผู้ใช้

ตารางทั้งหมดอยู่ใน schema bulletin แยกจาก public และไม่มีนิยามใน Prisma โดย apply ด้วย psql migration ล่าสุด (หมายเลข 005) เปลี่ยนความหมายของแอปจาก "ฟีดโซเชียล" มาเป็น "ฟอรัม" คือแต่ละกระทู้ต้องมีหัวข้อ และรายการเรียงตามการตอบล่าสุดแทนวันที่สร้าง

โครงสร้างข้อมูลหลัก

bulletin.board — บอร์ด

หนึ่งบอร์ดต่อหนึ่ง LINE OA บังคับด้วย line_oa_id แบบ UNIQUE

  • name, description และ settings แบบ JSONB ซึ่งรวมทุกการตั้งค่าไว้ที่เดียว เช่น จำนวนรูปสูงสุดต่อโพสต์ และสิทธิ์การเขียน

bulletin.category — หมวดหมู่

หมวดหมู่มี แกนสิทธิ์สองแกนที่แยกจากกันเด็ดขาด

แกนคอลัมน์ความหมาย
แกนการดูaccess_mode (ค่าเริ่มต้น inherit) + audience_idsใครมองเห็นหมวดนี้
แกนการโพสต์post_access (member หรือ admin, ค่าเริ่มต้น member)ใครโพสต์ในหมวดนี้ได้

การผสมที่ใช้บ่อยที่สุดคือ inherit คู่กับ admin ซึ่งได้หมวด "ประกาศ" ที่ทุกคนอ่านได้ แต่มีเพียง CMS เท่านั้นที่เขียนได้ การบังคับสิทธิ์จริงเกิดที่ฝั่ง server (client-api ตอบ 403 พร้อมรหัส BULLETIN_CATEGORY_ADMIN_ONLY) ส่วน CHECK constraint ในฐานข้อมูลเป็นด่านสุดท้าย

bulletin.post — กระทู้

  • title VARCHAR(200) เป็น NOT NULL และ ไม่มีค่าเริ่มต้น โดยเจตนา คอลัมน์นี้ถูกเพิ่มใน migration 005 พร้อมการล้างเนื้อหาเดิม เพราะการใส่ค่าเริ่มต้นจะทำให้ระบบยอมรับกระทู้ไร้หัวข้อไปตลอด
  • body และ images แบบ JSONB
  • ผู้เขียนใช้รูปแบบ XOR คือ author_type (line_user หรือ admin) คู่กับ author_line_user_id หรือ author_user_id อย่างใดอย่างหนึ่งเท่านั้น บังคับด้วย CHECK constraint chk_post_author
  • สถานะและการจัดวาง: status, allow_comments, is_pinned และ pinned_at
  • ตัวนับที่ trigger เป็นเจ้าของ และแอปพลิเคชันห้ามเขียนเอง ได้แก่ comment_count, reaction_count และ report_count
  • last_activity_date คำนวณโดย trigger จากคอมเมนต์ล่าสุด ใช้เป็นคีย์เรียงลำดับของฟอรัม
  • มี keyset index สองชุดเพราะสองหน้าจอเรียงคนละแบบ คือ (board_id, status, created_date DESC, id DESC) สำหรับ CMS และ (board_id, status, last_activity_date DESC, id DESC) สำหรับ LIFF

ตารางประกอบ

  • bulletin.comment — ความคิดเห็น ใช้รูปแบบผู้เขียนแบบ XOR เหมือนกระทู้ มี quote_comment_id อ้างอิงกลับมาที่ตัวเองสำหรับการยกข้อความ และ images แบบ JSONB
  • bulletin.reaction — unique (target_type, target_id, line_user_id) บังคับว่าหนึ่งคนกดอิโมจิได้หนึ่งครั้งต่อหนึ่งเป้าหมาย
  • bulletin.report — unique (target_type, target_id, reporter_line_user_id) พร้อม status (ค่าเริ่มต้น open)
  • bulletin.block — การบล็อกผู้ใช้ ใช้ partial unique (board_id, line_user_id) WHERE revoked_date IS NULL ข้อควรระวังที่ระบุไว้ในโค้ดคือ การบล็อกที่หมดอายุแล้วยังกินสล็อตนี้อยู่ การบล็อกใหม่จึงต้อง revoke ของเดิมภายใน transaction เดียวกัน
  • bulletin.audit_log — บันทึกทุกการกระทำของ moderator พร้อมสถานะ before และ after แบบ JSONB

Trigger นับจำนวน

Trigger ที่ดูแลตัวนับใช้ pg_advisory_xact_lock (lock space 4201 สำหรับกระทู้ และ 4202 สำหรับคอมเมนต์) แทนการใช้ SELECT ... FOR UPDATE โดยมีผลการวัดจริงกำกับไว้ในคอมเมนต์ของ migration ว่าเมื่อทดสอบ insert พร้อมกัน 30 รายการ กรณีไม่ล็อกจะได้ตัวเลขผิดถาวร กรณีใช้ FOR UPDATE เกิด deadlock 25 จาก 30 ครั้ง ส่วน advisory lock ให้ผลถูกต้องครบ 30 จาก 30

ไฟล์ที่เกี่ยวข้อง

  • apps/bulletin/migrations/001_create_schema.sql — คำสั่ง CREATE SCHEMA bulletin
  • apps/bulletin/migrations/002_create_tables.sql — ตารางหลักทั้ง 8 ตาราง
  • apps/bulletin/migrations/003_create_counter_triggers.sql — trigger นับคอมเมนต์ รีแอ็กชัน และรายงาน
  • apps/bulletin/migrations/004_add_category_post_access.sql — แกนสิทธิ์การโพสต์
  • apps/bulletin/migrations/005_forum_semantics.sql — หัวข้อกระทู้ รูปในคอมเมนต์ และ last_activity_date พร้อมเหตุผลของการล้างข้อมูลเดิมที่บันทึกไว้อย่างละเอียด

จุดเชื่อมต่อกับ Service อื่น

  • client-api-go ให้บริการฟีด กระทู้ คอมเมนต์ รีแอ็กชัน และการรายงานทั้งหมดจาก LIFF พร้อมตรวจสิทธิ์ทั้งสองแกนและด่านตรวจการบล็อก
  • cms-api-go ดูแล moderation queue การโพสต์ในนามผู้ดูแล การจัดการหมวดหมู่และการบล็อก โดยฟิลด์ที่ trigger เป็นเจ้าของถูกทำเครื่องหมายเป็น read-only ใน GORM ด้วยแท็ก ->
  • ใช้ audience_ids จาก กลุ่มเป้าหมาย อ้างอิงผู้ใช้ผ่าน LINE userId ของ เพื่อน LINE และเปิดปิดแอปด้วย line_oa_app
  • หมายเหตุ: schema bulletin ยังไม่ปรากฏใน schema-dumps/2026-07-24/schema.sql เนื่องจาก dump ชุดนั้นครอบคลุมเฉพาะ public และ appointment