Skip to main content

Segment audience — Multi-source filter

ภาพรวม​

Multi-source filter คือโหมดสร้างกลุ่มเป้าหมายจาก หลายเงื่อนไขรวมกัน โครงสร้างข้อมูลคือ audience หนึ่งชุดมีหลาย กลุ่ม (group) และแต่ละกลุ่มมีหลาย แหล่งข้อมูล (source) กำหนดตัวเชื่อม AND/OR ได้สองระดับ — ระหว่าง source ภายในกลุ่มเดียวกัน และระหว่างกลุ่มทั้งหมด เหมาะกับการตัดกลุ่มลูกค้าที่ต้องใช้เงื่อนไขซับซ้อนกว่าโหมด Filter audience แบบเดียว

จุดที่ต้องเข้าใจก่อนใช้งานคือ การรวมเซตทำในหน่วยความจำของ Go ไม่ได้ทำใน SQL — ระบบไปหารายชื่อของแต่ละ source แยกกันจนได้อาเรย์ของ LINE user id แล้วเอาอาเรย์เหล่านั้นมา intersect/union กันในโค้ด พฤติกรรมสำคัญหลายอย่าง (รวมถึงกับดักที่ต้องรู้) เกิดจากวิธีนี้ทั้งหมด (ดูหัวข้อ รายละเอียดสำคัญ)

บทบาทที่ใช้งาน — ทีมการตลาดที่ต้องตัดกลุ่มลูกค้าแบบละเอียด เช่น "คลิกแคมเปญ A และ (เป็นสมาชิกระดับ Gold หรือมีแต้มคงเหลือเกิน 500)"

เส้นทางผู้ใช้งาน​

1. เข้าฟอร์มและตั้งชื่อ​

เปิดเมนู Segment audience > Multi-source filter หรือกดปุ่ม Create segment จากหน้ารายการที่เมนู Filter audience ซึ่งพามาที่ฟอร์มนี้เป็นค่าเริ่มต้น กรอกชื่อ audience (ต้องไม่ซ้ำ และห้ามเป็นช่องว่างล้วน)

ฟอร์มรับพารามิเตอร์ ?preset= เป็น JSON ที่มีชื่อและชุด source มาตั้งต้นให้ได้ — ถ้าเข้ามาทางนั้นจะเห็นแถบเขียวบอกว่า "ใช้ preset ที่บันทึกไว้: {ชื่อ}" และ preset จะถูกเติมให้ครั้งเดียวเท่านั้น ไม่ทับค่าที่แก้ต่อ

2. เพิ่มและตั้งค่า source​

กดปุ่ม Add Source ในกลุ่มใดกลุ่มหนึ่งเพื่อเลือกชนิด — หน้าเว็บเสนอ 7 ชนิด: แคมเปญ, Rich Menu, activity tag, Auto Response, ฟอร์ม, custom attribute, และ Loyalty แต่ละ source แสดงเป็นการ์ดที่มีป้ายสีตามชนิด ปุ่มพับเก็บ ปุ่มลบ และ สวิตช์เปิด/ปิดใช้งานชั่วคราว (การ์ดที่ปิดจะจางลงและถูกตัดออกจากการคำนวณทั้งหมด) หัวกลุ่มบอกจำนวน "{ที่เปิด}/{ทั้งหมด} sources"

การตั้งค่าตามชนิด

  • แคมเปญ — เลือกแคมเปญจากเมนู Campaign ติ๊ก "Reach (View)" และ/หรือ spot ที่ติดตามคลิกได้ พร้อมตัวเชื่อม AND/OR ของ source นั้นเอง
  • Rich Menu — เลือกชนิดเมนู (guest/member/custom/switch) จากเมนู Rich menu แล้วเลือกเมนูและติ๊ก spot ถ้าเมนูนั้นไม่มี action ที่บันทึกคลิกได้ จะขึ้นข้อความว่า "ไม่มี spot ที่ติดตามได้ มีเพียง action ชนิด Link, Text และ Switch ที่บันทึกการคลิก"
  • Activity tag — ติ๊กช่วงความเคลื่อนไหว 4 แบบเหมือนโหมดแหล่งเดียว พร้อมตัวเชื่อม AND/OR
  • Auto Response — เลือกกฎจากเมนู Auto-response message ได้สูงสุด 5 รายการต่อ source
  • ฟอร์ม — เลือกฟอร์มจากเมนู Form builder แล้วสร้างเงื่อนไขจากคำถามได้ (กลุ่มเดียว แต่ไม่จำกัดจำนวนเงื่อนไข) เลือกคำถาม ตัวดำเนินการ และค่า โดยช่องกรอกค่าจะเปลี่ยนตามชนิดคำถาม (วันที่/ตัวเลข/ตัวเลือก/ข้อความ)
  • Custom attribute — เลือก attribute จากสองหมวด ("User Profile" กับ "Custom Attributes") แล้วเลือกตัวดำเนินการจาก 13 ตัวที่กรองตามชนิดข้อมูลของ attribute นั้น
  • Loyalty — เลือกตัวชี้วัด 1 ตัวต่อ source (แต้มคงเหลือ, ได้รับ, แลกไป, ระดับ, บัตรที่สะสมครบ, จำนวนวันตั้งแต่ได้แต้มล่าสุด) พร้อมตัวดำเนินการ >= / <= / = / ระหว่าง และตัวเลือกจำกัดสาขาและช่วงวันที่

3. กำหนดตัวเชื่อมและดูไดอะแกรม​

  • ระหว่าง source ภายในกลุ่ม — ปุ่มวงกลม AND/OR ที่หัวการ์ดกลุ่ม (ค่าเริ่มต้น AND)
  • ระหว่างกลุ่ม — ปุ่ม "AND (Intersection)" / "OR (Union)" ที่การ์ดบนสุด (ค่าเริ่มต้น OR) กลุ่มจะลบได้เมื่อมีมากกว่า 1 กลุ่มเท่านั้น

แผงด้านขวาวาดไดอะแกรมแบบ Venn สดตามที่แก้ทันทีโดยไม่ต้องกดดูตัวอย่าง กลุ่มที่มี 1–2 source วาดเป็นวงกลมสองวงคลาสสิก 3 source เป็นสามเหลี่ยม และ 4 source ขึ้นไปจัดเป็นตาราง 2 แถว (ไม่ได้ตัดที่ 3 วงแบบไดอะแกรมทั่วไป) แบบ AND จะวาดพื้นที่ทับซ้อนจริงทุกวงและติดป้าย "AND" กลางภาพ แบบ OR จะระบายเต็มวง แต่ละกลุ่มเรียงลงล่างคั่นด้วยสัญลักษณ์ ∩ หรือ ∪ ปิดท้ายด้วยป้าย "= Audience"

4. ดูตัวอย่างผลลัพธ์และสร้าง​

  1. กดปุ่ม Preview Results — ยิง POST /audiences-filter/preview-multi-source-filter ระบบคืน จำนวนรวมทั้งหมด, รายชื่อ 10 รายการแรก และ จำนวนที่แต่ละ source เข้าเงื่อนไข (แถบ "Source breakdown")
  2. ตารางตัวอย่างแสดงลำดับ, ผู้ใช้ (รูป + ชื่อ), User ID (คัดลอกได้), คอลัมน์ Sources ที่บอกว่าคนนั้นตรงกับ source ใดบ้าง, และประเภทผู้ใช้ ถ้ามี source ประเภทฟอร์มที่เปิดใช้งานอยู่ จะมีคอลัมน์ Form data เพิ่มให้กดเปิดดูคำตอบล่าสุดของผู้ใช้คนนั้น
  3. กดสร้าง — ปุ่มสร้างเปิดใช้งานได้ต่อเมื่อกดดูตัวอย่างสำเร็จมาก่อนแล้วเท่านั้น และตอนกดยังตรวจอีกสองข้อคือต้องมีชื่อที่ไม่ใช่ช่องว่าง และต้องมี source ที่เปิดใช้งานอย่างน้อย 1 รายการ
  4. ระบบยิง POST /audiences-filter/create-multi-source-filter แล้วพากลับไปหน้ารายการที่เมนู Filter audience ซึ่งใช้หน้าเดียวกันกับกลุ่มแบบแหล่งเดียว

รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง​

ยืนยันจาก internal/modules/audiencefilter/{controller,service,dto,multi_source_query_builder}.go ของ line-management-cms-api-go:

  • การรวมเซตทำในหน่วยความจำ ไม่ใช่ใน SQL — buildGroupQuery ไปหารายชื่อของแต่ละ source ที่เปิดใช้งานแยกกันจนได้อาเรย์ของ user id แล้ว combineUserIdSets เอามารวมกัน แบบ AND ทำ intersection โดยเริ่มจากเซตที่เล็กที่สุดก่อน (เรียงตามขนาดแบบ stable แล้วไล่ตัดจากเซตนั้น) เพื่อลดงาน และรักษาลำดับตามเซตเล็กสุด ส่วนแบบ OR ทำ union โดยรักษาลำดับที่พบครั้งแรก — จากนั้น buildCombinedQuery เอาผลของแต่ละกลุ่มมารวมด้วยตัวเชื่อมระดับบนอีกชั้นด้วยฟังก์ชันเดียวกัน
  • ⚠️ เซตว่างถูกทิ้งก่อนรวม ทำให้ AND ไม่ให้ผลว่างอย่างที่คาด — นี่คือกับดักที่สำคัญที่สุดของหน้านี้ combineUserIdSets กรองเซตที่ว่างออกทิ้งก่อนเสมอ (nonEmpty) และ buildCombinedQuery ก็กรองกลุ่มที่นับได้ศูนย์ออกก่อนรวมระดับบน (nonEmptyGroupResults) ผลคือ "คลิกแคมเปญ A AND ระดับสมาชิก Gold" ในกรณีที่ไม่มีใครเป็น Gold เลย จะคืนคนที่คลิกแคมเปญ A ทั้งหมด ไม่ใช่ศูนย์คน — เงื่อนไขที่หาใครไม่ได้เลยจะถูกเมินทิ้งแทนที่จะตัดผลลัพธ์ให้เป็นเซตว่าง ต้องดูแถบ "Source breakdown" ในผลตัวอย่างเสมอ ถ้าเห็น source ใดขึ้น 0 ให้ถือว่า source นั้นไม่ได้มีผลต่อผลลัพธ์เลย
  • backend รองรับ source 8 ชนิด แต่หน้าเว็บเสนอแค่ 7 — enum ของ backend มี campaign, rich_menu, activity_tag, auto_response, form / form_builder (สองชื่อชี้ตัวจัดการเดียวกัน), custom_attribute, loyalty และ trigger_rule ซึ่งมีตัวค้นรายชื่อทำงานได้ครบใน getTriggerRuleUserIds แต่ไม่มี UI ให้เลือก — เรียกใช้ได้ทาง API ตรงเท่านั้น
  • source ประเภทฟอร์มถูกกรองเหลือแต่คนที่ยังเป็นเพื่อนอยู่ ต่างจากโหมดแหล่งเดียว — getFormBuilderUserIds เรียก keepFollowers ตัดคนที่ follow != 'yes' ออก โดยคอมเมนต์ระบุเหตุผลว่า "การกรอกฟอร์มพิสูจน์แค่ว่าเคยกรอก ไม่ได้พิสูจน์ว่าวันนี้ยังติดต่อได้" และอธิบายว่าที่ต้องกรองทีหลังแทนที่จะใส่ในคิวรีเลย เพราะ buildQuery ใช้ร่วมกับฟีเจอร์ form-filter แบบแหล่งเดียว การใส่เงื่อนไข follow ลงไปจะเปลี่ยนพฤติกรรมของฟีเจอร์นั้นไปด้วยเงียบ ๆ — ผลคือเงื่อนไขฟอร์มชุดเดียวกันจะได้คนมากกว่าใน Filter audience แบบแหล่งเดียว และน้อยกว่าเมื่อใช้เป็น source ในหน้านี้
  • source ประเภท Rich Menu ไม่รับตัวเชื่อม AND/OR เลย — richMenuSourceConfig ไม่มีฟิลด์ condition และ getRichMenuUserIds ตั้งค่า OR ตายตัวพร้อมคอมเมนต์ "Rich menu typically uses OR" แปลว่าถ้าติ๊กหลาย spot ในเมนูเดียว จะได้ผลเป็น "กดอันใดอันหนึ่ง" เสมอ ไม่มีทางทำให้เป็น "กดครบทุกอัน" ได้ (ต่างจาก source แคมเปญ, activity tag, auto response และ trigger rule ที่ส่งตัวเชื่อมเข้าไปได้จริง)
  • เงื่อนไข custom attribute มีด่านกัน SQL injection ที่ระดับชื่อฟิลด์ — attribute ที่ขึ้นต้นด้วย __fixed. จะถูกแปลงเป็นชื่อคอลัมน์จริงจาก whitelist 10 ตัว (display_name, firstname, lastname, email, mobile_no, language, follow, user_type, registered_date, last_activity_date) ชื่อที่ไม่อยู่ใน whitelist จะถูกปฏิเสธ ส่วน attribute อื่นถูกประกอบเป็น custom_attribute->>'key' โดยชื่อคีย์ต้องผ่าน regex ^[a-zA-Z0-9_.-]+$ ก่อนเท่านั้น
  • หน้านี้เป็นเส้นทางเดียวที่ยอมสร้างกลุ่มที่ไม่มีสมาชิกเลย — CreateMultiSourceFilter ถ้าคำนวณได้ศูนย์รายชื่อ จะยังสร้างแถวและปิดสถานะเป็น completed พร้อมสถิติศูนย์ทั้งหมดและ lineUserIdsPathFilename เป็นค่าว่าง (ไม่มีไฟล์ CSV) ขณะที่ create-filter และ create-form-filter ตอบ 400 ปฏิเสธไปเลย — กลุ่มว่างที่สถานะ "เสร็จแล้ว" ในหน้ารายการจึงมาจากเส้นทางนี้เท่านั้น
  • การสร้างเป็นงานซิงโครนัสเหมือนโหมดแหล่งเดียว ไม่มีคิว — คำนวณ → สร้างแถว → ผูก audience_ids เป็นก้อนละ 5,000 รายชื่อ → เขียนไฟล์ CSV ขึ้น object storage → ปิดสถานะ completed ทั้งหมดในคำขอเดียว
  • filterInfo เก็บทั้งเงื่อนไขและ "ผลการคำนวณครั้งนั้น" ไว้ด้วยกัน — นอกจาก isMultiSource, sourceJoin และ groups แล้ว ยังบันทึก groupResults (จำนวนที่แต่ละกลุ่มได้) และ sourceResults (จำนวนที่แต่ละ source ได้) ติดไปกับกลุ่มด้วย เป็นภาพนิ่งของตอนสร้าง ไม่ได้อัปเดตตามการคำนวณรอบใหม่ — และ โครงสร้างนี้เองที่งาน auto refresh อ่านย้อนกลับมาใช้ประเมินเงื่อนไขใหม่ทุกครั้ง ผ่านเส้นทาง isMultiSource ใน refreshAudience
  • ตัวอย่างผลลัพธ์คืนได้ไม่เกิน 10 รายการเสมอ แต่ยอดรวมเป็นตัวเลขจริง — DTO ตรวจ limit ด้วย @Min(1) @Max(10) ส่วนช่อง total มาจากจำนวนรายชื่อทั้งชุดที่คำนวณได้จริง แถบ Source breakdown ก็เป็นตัวเลขเต็มของแต่ละ source ไม่ได้ถูกตัดตาม limit
  • คอลัมน์ Sources ในตารางตัวอย่างคำนวณจากการไล่เทียบทุก source — ระบบวนดูผลของทุก source แล้วจดว่าผู้ใช้แต่ละคนไปโผล่ที่ source ใด โดยรักษาลำดับที่พบก่อนหลังไว้ (เลียนลำดับของ Set ใน JavaScript) จึงบอกได้ว่าคนคนนั้นเข้ามาด้วยเงื่อนไขข้อไหน
  • source ที่ปิดสวิตช์ถูกตัดออกสองชั้น — getSourceUserIds คืนอาเรย์ว่างทันทีถ้า enabled = false และ buildGroupQuery ก็กรอง source ที่ปิดออกก่อนคำนวณอีกที กลุ่มที่ไม่มี source เปิดอยู่เลยจะคืนศูนย์รายชื่อและถูกทิ้งจากการรวมระดับบน (ตามกฎเซตว่างข้างต้น)
  • ไดอะแกรม Venn ยังไม่ครบสองจุด — (1) ชนิด loyalty ไม่อยู่ในตารางสีของ visualizer จึงถูกวาดเป็นสีเทาและติดป้ายด้วยชื่อ enum ดิบว่า loyalty และ (2) ตัวคอมโพเนนต์รับ prop จำนวนรวมได้ แต่ฟอร์มไม่เคยส่งค่านั้นเข้าไป ป้าย "= Audience" จึงไม่เคยแสดงตัวเลขจำนวนคนเลย
  • ข้อความของ source ประเภท Loyalty ยังไม่ได้แปลเป็นอังกฤษ — คีย์ทั้งชุด sourceEntryCard.loyaltyCfg.* ในไฟล์ messages/en/audience.json ยังเป็นข้อความไทย ผู้ใช้ที่สลับภาษาเป็นอังกฤษจะเห็นการ์ด Loyalty เป็นภาษาไทย

เมนูที่เกี่ยวข้องกันในเส้นทางนี้​

  • Filter audience — ใช้หน้ารายการและหน้ารายละเอียดร่วมกันทั้งหมด ต่างกันแค่ตอนสร้าง และเป็นหน้าที่ระบบพากลับไปหลังสร้างเสร็จ กลุ่มจากทั้งสองโหมดถูกบันทึกด้วย dataSource = 'filter' เหมือนกัน จึงเข้าร่วมรอบ auto refresh ได้เหมือนกัน (รายละเอียดรอบคำนวณอยู่ที่หน้านั้น)
  • Upload audience — โหมดที่ใช้ไฟล์แทนเงื่อนไข ไม่รองรับการรวมเซตแบบนี้และไม่รองรับ auto refresh
  • Campaign — แหล่งข้อมูลของ source ประเภทแคมเปญ ดึง tracking spot ทั้งหมดมาให้ติ๊ก
  • Rich menu — แหล่งข้อมูลของ source ประเภท Rich Menu (และเป็นชนิดเดียวที่ตัวเชื่อม AND/OR ไม่มีผล)
  • Auto-response message — แหล่งข้อมูลของ source ประเภท Auto Response เลือกได้สูงสุด 5 กฎต่อ source
  • Form builder — แหล่งข้อมูลของ source ประเภทฟอร์ม ทั้งรายการฟอร์ม รายการคำถาม และคำตอบล่าสุดที่แสดงในกล่อง Form data ของตารางตัวอย่าง

จุดที่ควรระวัง​

  • ต้องกดดูตัวอย่างผลลัพธ์สำเร็จก่อนเสมอ ปุ่มสร้างจะไม่เปิดใช้งานถ้าข้ามขั้นตอนนี้ และการกดดูตัวอย่างไม่มีการตรวจความครบถ้วนฝั่งหน้าเว็บก่อนยิง — ถ้า backend คืน error จะเห็นแค่ข้อความ "ดูตัวอย่างผลลัพธ์ไม่สำเร็จ" แบบกว้าง ๆ แล้วสถานะถูกรีเซ็ต
  • ระวังกฎ "เซตว่างถูกเมิน" ให้มากที่สุด — เงื่อนไข AND ที่ไม่มีใครเข้าเลยจะไม่ทำให้ผลลัพธ์เป็นศูนย์ แต่จะหายไปจากสมการเฉย ๆ ทำให้ได้กลุ่มที่กว้างกว่าที่ตั้งใจ ตรวจแถบ Source breakdown ทุกครั้งว่าไม่มี source ใดขึ้นเลข 0
  • ผลลัพธ์เป็นภาพนิ่งของช่วงเวลาที่กดสร้าง — ถ้าไม่เปิด auto refresh ที่หน้ารายการ กลุ่มจะไม่คำนวณตัวเองอีกเลย
  • เงื่อนไขฟอร์มให้ผลไม่เท่ากันระหว่างสองหน้า — ที่นี่ตัดคนที่เลิกเป็นเพื่อนออก ส่วนโหมดแหล่งเดียวไม่ตัด ถ้าตัวเลขไม่ตรงกันนี่คือสาเหตุ
  • ผู้ใช้ภาษาอังกฤษจะเห็นการ์ด Loyalty เป็นภาษาไทย และไดอะแกรมจะวาด source ชนิด Loyalty เป็นวงสีเทาที่ติดป้ายว่า loyalty
  • ปุ่ม Copy ที่หน้ารายการพามาที่ฟอร์มนี้เสมอ แต่จะดึงค่าเดิมมาให้เฉพาะกลุ่มที่เป็นแบบหลายแหล่ง — กด Copy ที่กลุ่มแบบแหล่งเดียวจะได้ฟอร์มเปล่า
  • ตัวเชื่อมค่าเริ่มต้นของสองระดับไม่เหมือนกัน — ระหว่าง source ในกลุ่มเป็น AND แต่ระหว่างกลุ่มเป็น OR ถ้าเพิ่มกลุ่มที่สองโดยไม่แตะตัวเชื่อม จะได้ผลเป็นการรวม (union) ไม่ใช่การตัด (intersection)

สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)​

หน้านี้ทำงานผ่านโมดูล backend audiencefilter เกือบทั้งหมด ซึ่ง มีการบังคับสิทธิ์น้อยกว่าโมดูล audience อย่างชัดเจน — ตรวจแยกทีละไฟล์แล้ว

  • โมดูล audiencefilter (เส้นทาง /api/audiences-filter/*) ไม่มี modulegate.ModuleGate เลยแม้แต่บรรทัดเดียว — controller.go ลงทะเบียนทั้ง 11 เส้นทางที่ต้องยืนยันตัวตนตรงบน authed group โดยมีแต่ auth.CheckPolicies(auth.Can(...)) ที่เป็น no-op ดังนั้น POST /audiences-filter/create-multi-source-filter และ POST /audiences-filter/preview-multi-source-filter ที่หน้านี้ใช้ ยังเรียกได้ปกติแม้ผู้ดูแลระดับแพลตฟอร์มจะปิดโมดูล audiences ให้องค์กรนั้นแล้ว
  • โมดูล audience (เส้นทาง /api/audiences/*) ตรงกันข้าม คือมี modulegate.ModuleGate(d, "audiences") ครอบทั้งกลุ่ม — ผลที่ตามมาคือถ้าโมดูลถูกปิด ผู้ใช้จะ สร้างกลุ่มใหม่จากหน้านี้ได้ แต่ดูรายการกลุ่มที่สร้างไปแล้วไม่ได้ (GET /audiences คืน 403) และเปิด/ปิด auto refresh กับกดคำนวณใหม่ก็ไม่ได้ เป็นความไม่สอดคล้องที่เกิดจากด่านที่ขาดไปในโมดูลหนึ่ง ไม่ใช่การออกแบบ
  • เส้นทางเดียวในโมดูลนี้ที่มีการบังคับจริงคือ POST /audiences-filter/internal/refresh — อยู่บน public group ไม่ผ่าน JWT แต่ครอบด้วย guard เทียบ header x-internal-key กับ env INTERNAL_API_KEY ถ้า env ว่างจะปฏิเสธทุกคำขอด้วย 401 Invalid internal API key
  • GET /audiences-filter/form-response (กล่อง Form data ในตารางตัวอย่าง) เป็นเส้นทางที่บางกว่าที่อื่น — ตรวจแค่ว่ามี lineUserId และ formIds ส่งมา ไม่ได้ตรวจว่าผู้เรียกมีสิทธิ์ดูคำตอบของผู้ใช้คนนั้นเป็นรายบุคคล ความปลอดภัยพึ่งการจำกัดขอบเขต line_oa_id จาก CLS ใน query ที่ดึงฟอร์มเท่านั้น และคืน error เป็น 500 ล้วนเมื่อ query ล้มเหลว (ไม่มีการห่อเป็น AppError)
  • ฝั่งหน้าเว็บไม่มี PermissionGuard บนหน้า audience ใด ๆ รวมถึงหน้านี้ ควบคุมได้แค่การซ่อนเมนูข้างด้วย ability.can('view', 'audience') (คีย์เมนูย่อยของหน้านี้คือ audience-multi-source) พิมพ์ URL เข้าตรงไม่ถูกกัน
  • ทุก query ยังถูกจำกัดขอบเขตด้วย line_oa_id จาก CLS context ทั้งการหารายชื่อของทุก source, การตรวจชื่อซ้ำ และการดึงคำตอบฟอร์ม — ข้อมูลข้าม OA จึงไม่รั่วถึงกัน สิ่งที่ขาดไปคือด่านระดับโมดูล ไม่ใช่ด่าน tenant

ไฟล์อ้างอิงจากซอร์สโค้ด​

Frontend (cms-web-2026)

  • src/app/audience/filter/multi-source/form/page.tsx — จุดเข้าฟอร์ม (รับ ?id=, ?action=copy, ?preset=)
  • src/components/audience/filter/multi-source-filter-form.container.tsx — สถานะ hasPreviewed, การตรวจก่อนส่ง, การเติมค่าจาก preset และโหมดคัดลอก
  • src/components/audience/filter/multi-source-filter-form.tsx — ชนิด source 7 แบบ, ตัวเชื่อมสองระดับ, ปุ่ม Preview Results
  • src/components/audience/filter/components/source-entry-card.tsx — การ์ด source ทุกชนิดและช่องตั้งค่าทั้งหมด
  • src/components/audience/filter/components/multi-source-visualizer.tsx — ไดอะแกรม Venn (เรขาคณิตตามจำนวน source, ตารางสีต่อชนิด)
  • src/components/audience/filter/multi-source-filter-table.tsx — ตารางตัวอย่างผลลัพธ์, แถบ Source breakdown, กล่อง Form data
  • src/components/audience/interfaces/multi-source-filter.interface.ts — โครงสร้าง MultiSourceFilterFormValues / SourceGroup / SourceFilterEntry
  • src/services/audience-filter.service.ts

Backend (line-management-cms-api-go)

  • internal/modules/audiencefilter/controller.go — เส้นทาง create/preview multi-source, ไม่มี ModuleGate, formResponsePreview, internalApiKeyGuard
  • internal/modules/audiencefilter/multi_source_query_builder.go — getSourceUserIds (8 ชนิด), combineUserIdSets (กฎ AND/OR และการทิ้งเซตว่าง), keepFollowers, getCustomAttributeUserIds, getLoyaltyUserIds
  • internal/modules/audiencefilter/service.go — CreateMultiSourceFilter, PreviewMultiSourceFilter, GetFormResponsePreview, การอ่าน filterInfo กลับมาใช้ตอน refresh
  • internal/modules/audiencefilter/dto.go — กฎ validation ของ groups/sources แบบซ้อนชั้น และเพดาน limit ที่ 10
  • internal/modules/audiencefilter/enums.go — enum ชนิด source ทั้ง 9 ค่า (รวม trigger_rule ที่ไม่มี UI)
  • internal/modules/audiencefilter/form_filter_query_builder.go — ตัวประกอบคิวรีเงื่อนไขฟอร์มที่ใช้ร่วมกับโหมดแหล่งเดียว

ดูรายละเอียดเชิงเทคนิคเพิ่มเติมที่ จัดการกลุ่มเป้าหมาย (Audience Management)