กระดานสนทนา (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 — กระทู้
titleVARCHAR(200) เป็น NOT NULL และ ไม่มีค่าเริ่มต้น โดยเจตนา คอลัมน์นี้ถูกเพิ่มใน migration 005 พร้อมการล้างเนื้อหาเดิม เพราะการใส่ค่าเริ่มต้นจะทำให้ระบบยอมรับกระทู้ไร้หัวข้อไปตลอดbodyและimagesแบบ JSONB- ผู้เขียนใช้รูปแบบ XOR คือ
author_type(line_userหรือadmin) คู่กับauthor_line_user_idหรือauthor_user_idอย่างใดอย่างหนึ่งเท่านั้น บังคับด้วย CHECK constraintchk_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แบบ JSONBbulletin.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 bulletinapps/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