Skip to main content

โครงหน้าจอ เมนู และ Infrastructure ฝั่ง Frontend

ภาพรวม​

เอกสารนี้เป็นเรื่องของ โครงสร้างที่ตัดขวางทุกฟีเจอร์ อธิบายส่วนที่ทุกหน้าใน CMS ใช้ร่วมกัน หากอ่านเอกสารของฟีเจอร์ใดแล้วพบคำว่า container, breadcrumb, ส่วนหัวหน้า, ชั้นจัดการคำขอ หรือสไตล์มาตรฐานของแถบตัวกรอง ให้กลับมาดูรายละเอียดที่นี่

ตัวเว็บสร้างด้วย Next.js App Router ที่ตั้งค่าให้ส่งออกเป็นไฟล์สแตติก โดยทุกหน้าเป็น client component ทั้งหมด ไม่มี server component หรือ server action เลย

องค์ประกอบหลักของโครงมี 7 ส่วน

  • โครงหน้าจอเดียวที่แตกออกเป็น 4 โหมด ตามเส้นทางที่ผู้ใช้เปิด
  • ชั้น provider 2 ระดับ สำหรับการยืนยันตัวตนกับธีม และสำหรับการแปลภาษา
  • เมนูด้านข้างที่สร้างจากสิทธิ์ของผู้ใช้ ไม่มีไฟล์ตั้งค่าเมนูแบบตายตัวทั้งชุด แต่รายการถูกใส่เข้าไปทีละอันตามสิทธิ์
  • คลังสถานะกลาง 3 ชุด สำหรับสถานะของแอป ภาษา และผังงานของ Workflow
  • ระบบ 2 ภาษาแบบทำงานที่เบราว์เซอร์ โดยไม่มีการแยกเส้นทางตามภาษา
  • รูปแบบ container กับ presenter ที่แยกความรับผิดชอบระหว่างการตรวจสิทธิ์ การจัดการสถานะ และการแสดงผล
  • ชั้นข้อมูลกลาง ที่ห่อไลบรารีจัดการคำขอไว้ชั้นหนึ่ง และใช้ตัวเรียก API ตัวเดียวสำหรับทุกบริการ

รายละเอียดเรื่อง token, การต่ออายุ token, การกันเส้นทางเมื่อยังไม่ล็อกอิน และการจัดการเมื่อได้รับสถานะปฏิเสธจากเซิร์ฟเวอร์ อยู่ในเอกสารเรื่องการเข้าสู่ระบบ เอกสารนี้อ้างอิงถึงเท่านั้น

Business Flow​

ลำดับการทำงานเมื่อผู้ใช้เปิดหน้าหนึ่งของ CMS มีดังนี้

1. โครงหน้าจอหลัก​

  1. โหลดสไตล์ทั้งหมดของระบบ ฟอนต์ และไอคอน โดยฟอนต์ถูกโหลด 2 ชุดด้วยวิธีที่ต่างกัน คือชุดที่รองรับภาษาไทยโหลดผ่านแท็กลิงก์ ส่วนชุดที่ใช้ในธีมโหลดผ่านกลไกของเฟรมเวิร์ก
  2. ตั้งชื่อหน้าเบราว์เซอร์ตามเส้นทางที่เปิดอยู่ จากตารางที่กำหนดไว้ในโค้ด
  3. แยกโหมดของโครงหน้าจอจากเส้นทาง 4 แบบ
    • หน้าสาธารณะ ใต้กลุ่ม /p แสดงเนื้อหาเปล่าโดยไม่มีองค์ประกอบใดของ CMS
    • หน้าที่อยู่นอกระบบ เช่น หน้าเข้าสู่ระบบ ลืมรหัสผ่าน ตั้งรหัสผ่านใหม่ สมัครสมาชิก และยืนยันอีเมล ใช้โครงแบบจำกัด
    • หน้าระดับบัญชี เช่น การจัดการ LINE OA และการจัดการผู้ใช้ ใช้ส่วนหัวโหมดจำกัดโดยไม่มีเมนูด้านข้าง
    • หน้า CMS ทั่วไป ประกอบด้วยแถบคำสั่งด่วน ส่วนหัว เมนูด้านข้าง และพื้นที่เนื้อหาที่มี breadcrumb อยู่ด้านบน
  4. รายการเส้นทางที่ยกเว้นต้องกำหนดไว้ทั้งแบบมีและไม่มีเครื่องหมายทับปิดท้าย เพราะระบบตั้งค่าให้ทุกเส้นทางลงท้ายด้วยเครื่องหมายทับ

2. ชั้น provider​

  1. ชั้น provider เรียงจากนอกเข้าใน ตั้งแต่ตัวจัดการการ hydrate, ตัวแปลภาษา, ตัวจัดการคำขอข้อมูล, ตัวตั้งค่าไลบรารี UI ไปจนถึงกล่องครอบธีม
  2. ตัวจัดการคำขอข้อมูลถูกสร้างเพียงครั้งเดียวตลอดอายุของแอป และตั้งค่าไม่ให้โหลดข้อมูลใหม่เมื่อผู้ใช้กลับมาที่หน้าต่างเบราว์เซอร์
  3. ไลบรารี UI ได้รับทั้งชุดธีมและภาษาที่ตรงกับภาษาที่ผู้ใช้เลือกอยู่
  4. การครอบด้วยตัวจัดการของไลบรารี UI ทำให้ทุกหน้าสามารถเรียกใช้ระบบข้อความแจ้งเตือนที่ผูกกับธีมได้
  5. เมื่อสถานะกำลังโหลดระดับแอปถูกเปิด ระบบจะแสดงตัวหมุนคลุมทั้งหน้าจอ

3. การแปลภาษา​

  1. ระบบอ่านภาษาที่ผู้ใช้เลือกจากคลังสถานะที่บันทึกไว้ในเบราว์เซอร์
  2. เมื่อภาษาเป็นภาษาไทย ระบบจะรวมชุดข้อความภาษาไทยทับลงบนชุดภาษาอังกฤษ เพื่อให้คีย์ที่ยังไม่ได้แปลตกกลับไปใช้ข้อความภาษาอังกฤษแทนที่จะแสดงเป็นค่าว่าง
  3. เมื่อภาษาเปลี่ยน ระบบจะอัปเดตทั้งค่าภาษาของเอกสารและภาษาของไลบรารีจัดการวันที่ให้สอดคล้องกัน
  4. เขตเวลาถูกตั้งไว้ที่กรุงเทพฯ และคีย์ที่หาไม่พบจะถูกแสดงเป็นข้อความที่ระบุชื่อ namespace กับคีย์อย่างชัดเจน เพื่อให้เห็นปัญหาได้ทันทีบนหน้าจอ

4. เมนูด้านข้าง​

  1. ตัวควบคุมเมนูอ่านสิทธิ์ของผู้ใช้จากคลังสถานะ แล้วสร้างรายการเมนูทีละอันโดยตรวจสิทธิ์ก่อนใส่แต่ละรายการ
  2. เมนูที่มีรายการลูก เช่น กลุ่มผู้ชม ริชเมนู จัดการเนื้อหา แอป และการตั้งค่า จะตรวจสิทธิ์ในระดับลูกอีกชั้นหนึ่ง โดยเมนูริชเมนูจะเปิดเมนูแม่หากลูกรายการใดรายการหนึ่งผ่านเงื่อนไข
  3. เมนูการตั้งค่าถูกใส่เข้าไปเสมอโดยไม่ตรวจสิทธิ์ และรายการลูกส่วนใหญ่ก็ไม่ตรวจเช่นกัน มีเพียงบางรายการที่ตรวจ
  4. เมนูแอปเป็นแบบไดนามิก โดยเรียกทะเบียนแอปแล้วสร้างรายการลูกเฉพาะแอปที่เปิดใช้งาน หากไม่มีแอปใดเปิดอยู่เลยจะซ่อนเมนูแม่ทั้งอัน ส่วนข้อผิดพลาดจากการเรียกทะเบียนจะถูกกลืนเงียบ
  5. แอปกระดานข่าวมีป้ายตัวเลขจำนวนรายการที่รอตรวจ ซึ่งถูกเรียกเฉพาะเมื่อทะเบียนยืนยันว่าแอปเปิดอยู่ เพราะ endpoint นั้นจะปฏิเสธคำขอเมื่อแอปถูกปิด
  6. ลำดับของเมนูกำหนดด้วยรายการที่เขียนไว้ต่างหาก โดยเมนูที่ไม่ปรากฏในรายการนั้นจะถูกดันไปอยู่ท้ายสุด
  7. ตัวแสดงผลเมนูอ่านค่าเมนูที่ active จากคลังสถานะ แล้วกางเมนูแม่ที่มีลูกกำลัง active ให้อัตโนมัติ พร้อมสลับไอคอนของรายการที่ active
  8. สถานะการพับเมนูถูกบันทึกไว้ในเบราว์เซอร์ และเมื่อพับ เมนูแม่จะถูกคลี่ออกเป็นไอคอนของรายการปลายทาง เพราะไลบรารีไม่แสดงคำอธิบายให้เมนูแม่ในโหมดแถบแคบ การคลิกจึงจะกางแถบกลับพร้อมเปิดเมนูย่อยนั้น

5. ส่วนหัวและ breadcrumb​

  1. ส่วนหัวรับโหมดการทำงานเข้ามาแล้วแสดงองค์ประกอบต่างกัน โดยโหมด CMS แสดงโลโก้ ปุ่มค้นหา ตัวสลับภาษา ชื่อและรูปของ LINE OA ปัจจุบัน และเมนูโปรไฟล์
  2. โหมดระดับบัญชีจะซ่อนโลโก้ ปุ่มค้นหา และข้อมูล OA แล้วแสดงปุ่มย้อนกลับไปยังหน้าจัดการ LINE OA พร้อมชื่อหน้าที่กำหนดตามเส้นทาง
  3. ปุ่มค้นหาไม่ได้ใช้สถานะร่วม แต่จำลองการกดคีย์ลัดเพื่อกระตุ้นตัวรับฟังของแถบคำสั่งด่วน ซึ่งเป็นการตัดสินใจที่มีคำอธิบายกำกับไว้ในโค้ด
  4. เมนูโปรไฟล์ประกอบด้วยการจัดการ LINE OA การเปลี่ยนรหัสผ่าน และการออกจากระบบ
  5. ทุกหน้าเป็นผู้ตั้ง breadcrumb และเมนูที่ active ด้วยตัวเอง โดยตัวแสดงผลจะไม่แสดงอะไรเลยหากไม่มีรายการ breadcrumb

6. การตรวจสิทธิ์และการเข้าสู่ container ของฟีเจอร์​

  1. ตัวตรวจสิทธิ์เป็นคอมโพเนนต์ระดับสูงที่ห่อหน้าเป้าหมายพร้อมระบุการกระทำและ subject ที่ต้องการ
  2. หากสิทธิ์ยังโหลดไม่เสร็จ หน้าจะว่างไว้ชั่วคราว และหากผู้ใช้ไม่มีสิทธิ์ ระบบจะแสดงหน้าแจ้งไม่มีสิทธิ์แทน
  3. เมื่อผ่านแล้วจึงเข้าสู่ container ของฟีเจอร์ ซึ่งเป็นผู้เรียก API และจัดการสถานะทั้งหมด

7. ชั้นข้อมูล​

  1. ชั้นจัดการคำขอกลางคืนเครื่องมือ 3 อย่าง คือการอ่านข้อมูล การเปลี่ยนแปลงข้อมูล และตัวจัดการแคช โดยการอ่านตั้งค่าให้ลองใหม่หนึ่งครั้งเป็นค่าเริ่มต้น และการเปลี่ยนแปลงจะสั่งให้ข้อมูลชุดเดียวกันถูกโหลดใหม่โดยอัตโนมัติเมื่อทำงานเสร็จ
  2. ทุกบริการเรียกผ่านตัวเรียก API ตัวเดียวที่ตั้ง base URL จากตัวแปรสภาพแวดล้อม
  3. ไม่มีตัวจัดการข้อผิดพลาดกลาง รูปแบบที่พบทั่วไปในแต่ละหน้าคือการอ่านข้อความจากผลลัพธ์ของเซิร์ฟเวอร์ก่อน แล้วจึงตกกลับไปใช้ข้อความที่แปลไว้ ส่วนรหัสข้อผิดพลาดที่ประกาศเป็นค่าคงที่มีเพียงไม่กี่ตัวและใช้เฉพาะบางจุด

8. การจำค่าตัวกรองข้ามการเปลี่ยนหน้า​

  1. หน้ารายการรุ่นเก่าราว 10 หน้าเก็บค่าตัวกรองไว้เพื่อให้ผู้ใช้กลับมาเจอค่าเดิม โดยเก็บลงพื้นที่จัดเก็บชั่วคราวของเบราว์เซอร์แยกตามชื่อหน้า
  2. รูปแบบการใช้งานคืออ่านค่าที่เก็บไว้มาเป็นค่าตั้งต้นตอนเปิดหน้า บันทึกค่าใหม่ตอนกดค้นหา และล้างค่าตอนกดล้างตัวกรอง
  3. การล้างค่าจะลบค่าของทุกหน้าไม่ใช่เฉพาะหน้าตัวเอง และการล้างทั้งหมดยังถูกเรียกตอนออกจากระบบและตอนสลับ LINE OA ด้วย
  4. หน้ารายการรุ่นใหม่ไม่ได้ใช้กลไกนี้ แต่เก็บพารามิเตอร์ไว้ในสถานะภายในหน้าเท่านั้น

หน้าจอและองค์ประกอบหลัก​

โครงหน้าจอ provider และการนำทาง​

  • src/app/layout.tsx — เลือกโหมดของโครงหน้าจอ โหลดสไตล์และฟอนต์ และตั้งชื่อหน้าเบราว์เซอร์
  • src/providers/app.provider.tsx — ชั้น provider ทั้งหมด ตัวจัดการคำขอ ธีมและภาษาของไลบรารี UI ตัวหมุนระดับแอป และงานด้านการยืนยันตัวตน
  • src/providers/i18n.provider.tsx — ตัวแปลภาษา การรวมชุดข้อความ และการซิงก์ภาษาของเอกสารกับไลบรารีจัดการวันที่
  • src/app/permission.guard.tsx — คอมโพเนนต์ระดับสูงสำหรับตรวจสิทธิ์ต่อหน้า
  • src/components/layout/sidemenu/ — ตัวควบคุมที่สร้างรายการเมนูจากสิทธิ์และทะเบียนแอป กับตัวแสดงผลที่ดูแลการกางเมนูแม่และการพับแถบ
  • src/components/layout/app-header/ — ส่วนหัว 2 โหมด พร้อมปุ่มค้นหา ตัวสลับภาษา และเมนูโปรไฟล์
  • src/components/layout/app-breadcrumb/ — อ่าน breadcrumb จากคลังสถานะแล้วแสดงผล
  • src/components/layout/quick-access/quick-access.tsx — แถบคำสั่งด่วนที่เปิดด้วยคีย์ลัด
  • src/components/ability/ — การสร้างชุดสิทธิ์จากข้อมูลที่เซิร์ฟเวอร์ส่งมา และการแม็ปโมดูลฝั่งหลังบ้านเข้ากับ subject ฝั่งหน้าเว็บ

รายละเอียดของแถบคำสั่งด่วน — เป็นทะเบียนรายการแบบตายตัวที่สะท้อนเมนูด้านข้างทีละรายการ จึงเป็น แหล่งข้อมูลชุดที่สองที่ต้องแก้ตามกันทุกครั้งที่เมนูเปลี่ยน แอปที่เป็นไดนามิกจะถูกแทรกเข้าไปในตำแหน่งที่กำหนดโดยเรียกทะเบียนแอปแบบชะลอเมื่อเปิดแถบครั้งแรก และ การค้นหาทำงานสองภาษาพร้อมกัน โดยรวมข้อความทั้งภาษาอังกฤษและภาษาไทยเข้าเป็นข้อความค้นหาชุดเดียว แล้วตรวจว่าคำค้นทุกคำปรากฏครบ ส่วนป้ายที่แสดงจะใช้ภาษาที่ผู้ใช้เลือกอยู่ นอกจากนี้ยังจำรายการที่ใช้ล่าสุด 3 รายการไว้ในเบราว์เซอร์

คลังสถานะ​

  • src/store/app.store.ts — เก็บธีม โปรไฟล์ สิทธิ์ ชุดความสามารถ breadcrumb สถานะกำลังโหลด และเมนูที่ active โดยบันทึกลงเบราว์เซอร์เฉพาะธีม โปรไฟล์ และสิทธิ์ ส่วนชุดความสามารถกับ breadcrumb ต้องตั้งใหม่ทุกครั้งที่โหลดหน้า
  • src/store/locale.store.ts — เก็บภาษาที่เลือก แยกออกจากคลังหลักโดยเจตนา เพราะภาษาต้องรอดจากการล้างสถานะตอนออกจากระบบ
  • src/store/workflow.store.ts — เก็บผังงานของ Workflow โดยไม่บันทึกลงเบราว์เซอร์

ระบบภาษา​

  • src/i18n/config.ts — กำหนดรายการภาษา ภาษาเริ่มต้น คีย์ที่ใช้บันทึก และรายการ namespace ทั้งหมด ซึ่งเป็นทั้งจุดลงทะเบียนและแหล่งอ้างอิงของชุดทดสอบความครบถ้วนของคำแปล
  • src/i18n/messages.ts — การอ่านและรวมชุดข้อความ
  • src/i18n/rich-tags.tsx — ชุดแท็กสำหรับข้อความที่มีการจัดรูปแบบ ซึ่งต้องส่งให้ทุกจุดที่เรียกใช้เพราะไลบรารีไม่มีที่ลงทะเบียนแบบรวมศูนย์
  • messages/{en,th}/*.json — ไฟล์คำแปลแยกตาม namespace โดยชุด common เป็นชุดที่ทุกหน้าใช้ร่วมกัน ครอบคลุมทั้งชื่อเมนู ปุ่มมาตรฐาน หัวตาราง สถานะ และข้อความตรวจสอบความถูกต้อง

ค่าคงที่กลาง​

  • app-config.constant.tsx — ชื่อแอป โลโก้ ความกว้างเมนู รายการเส้นทางระดับบัญชี และรายการเส้นทางที่ยกเว้นจากโครง CMS
  • cms-list-filter.constant.ts — สไตล์มาตรฐานของ control ในแถบตัวกรองของทุกหน้ารายการ
  • cms-table-layout.constant.ts และ cms-page-classes.constant.ts — สไตล์การเลื่อนตารางและชื่อคลาสกลางของหน้า
  • theme.constant.ts — ชุดธีมของไลบรารี UI ทั้งฟอนต์ ชุดสี ขนาดตัวอักษร และการปรับแต่งคอมโพเนนต์
  • date.constant.ts — รูปแบบวันเวลาทั้งชุด ขนาดหน้าตาราง และค่าเริ่มต้นของการแบ่งหน้า
  • src/enums/common.enum.tsx — รายการ subject ของสิทธิ์ทั้งหมด การกระทำที่รองรับ สถานะมาตรฐาน โหมดของหน้า และโหมดของเว็บไซต์

คอมโพเนนต์ร่วมที่ใช้หลายหน้า​

  • ตัวห่อลิงก์ที่ ปิดการโหลดล่วงหน้าเป็นค่าเริ่มต้น เพราะเฟรมเวิร์กจะโหลดล่วงหน้าให้ทุกลิงก์ที่อยู่ในหน้าจอ ทำให้เมนูด้านข้างและตารางยิงคำขอนับสิบครั้งต่อการโหลดหนึ่งหน้า
  • ส่วนหัวของหน้าพร้อมปุ่มสร้างมาตรฐาน
  • พื้นที่ครอบตารางให้เลื่อนแนวนอนได้
  • ชุดปุ่มคำสั่งประจำแถวของตาราง พร้อม hook ที่ต่อสายปุ่มลบเข้ากับกล่องยืนยันและจัดการสถานะกำลังลบให้เอง
  • กล่องยืนยันกลาง กล่องแจ้งผลสำเร็จ และกล่องแจ้งผลล้มเหลว พร้อมชุด helper สำหรับเรียกใช้
  • ตัวหมุนแสดงสถานะกำลังโหลดทั้งแบบระดับแอปและแบบควบคุมด้วย prop
  • หน้าต่างพรีวิวไฟล์ที่แยกการแสดงผลตามชนิดไฟล์
  • หน้าต่างคู่มือการใช้งานที่แสดงทีละขั้น
  • หน้าแจ้งไม่มีสิทธิ์และหน้าแจ้งไม่มีข้อมูล

Hook และ utility​

  • usePageTitle — ตั้งชื่อหน้าเบราว์เซอร์ตามเส้นทาง
  • storage.util.ts — อ่าน เขียน และล้างค่าตัวกรองที่เก็บไว้
  • dayjs.util.ts และ date.ts — การตั้งค่าไลบรารีจัดการวันที่ การแปลงเขตเวลา และการจัดรูปแบบวันที่สำหรับตารางและตัวกรอง
  • form.util.ts — การแม็ปข้อผิดพลาดรายฟิลด์จากเซิร์ฟเวอร์ลงในฟอร์ม พร้อมเลื่อนหน้าจอไปยังฟิลด์แรกที่ผิด
  • line.util.ts และ url.util.ts — ตัวตรวจความถูกต้องของข้อมูลฝั่ง LINE และการประกอบ URL ของบริการต่าง ๆ
  • slugify.ts — การถอดเสียงภาษาไทยและสร้าง slug
  • donwload-file.util.ts — การดาวน์โหลดไฟล์ โดยเติมเครื่องหมายกำกับให้ไฟล์ CSV เพื่อให้เปิดในโปรแกรมตารางแล้วแสดงภาษาไทยถูกต้อง

จุดเชื่อมต่อกับฟีเจอร์อื่น​

  • ระบบสิทธิ์ — รายการ subject ทั้งหมดเป็นค่าคงที่ชุดเดียวกัน โดยการกระทำที่รองรับมีเพียงการดูและการส่งออก ต้นทางของสิทธิ์คือคำขออ่านสิทธิ์ของผู้ใช้ ซึ่งถูกแปลงเป็นชุดความสามารถแล้วเก็บไว้ในคลังสถานะ
  • ทะเบียนแอป — เป็นแหล่งข้อมูลที่ทั้งเมนูด้านข้างและแถบคำสั่งด่วนเรียกใช้ เพื่อกำหนดว่าจะแสดงแอปเสริมใดบ้าง
  • การเข้าสู่ระบบ — เรื่องของ token การกันเส้นทาง และตัวจับเวลาการไม่ใช้งาน อยู่ในเอกสารการเข้าสู่ระบบ ซึ่งใช้ provider ตัวเดียวกับที่อธิบายไว้ที่นี่
  • การเปิดปิดโมดูลระดับองค์กร — เป็นตัวกำหนดว่าโมดูลใดจะถูกปลดล็อกให้กับองค์กรนั้น ซึ่งส่งผลต่อสิทธิ์ที่ผู้ใช้จะได้รับ
  • สิ่งที่ต้องแก้ตามกันเมื่อเพิ่มหน้าใหม่ — เริ่มจากเพิ่ม subject ในรายการค่าคงที่ จากนั้นเพิ่มการแม็ปจากโมดูลฝั่งหลังบ้านหากเป็นโมดูลใหม่ แล้วเพิ่มรายการในเมนูด้านข้างพร้อมกำหนดลำดับ ตามด้วยเพิ่มรายการในแถบคำสั่งด่วน และสุดท้ายลงทะเบียน namespace ของคำแปลพร้อมเพิ่มไฟล์คำแปลทั้งสองภาษาและคีย์ชื่อเมนูในชุดข้อความกลาง

รายละเอียดฝั่ง Backend (CMS API)​

โครงหน้าจอและเมนูของ CMS ทำงานอยู่บนพื้นฐาน 2 อย่างของฝั่งเซิร์ฟเวอร์ คือชั้นแกนกลาง HTTP ที่กำหนดรูปของทุก response และระบบสิทธิ์ 4 ชั้นที่กำหนดว่าใครเห็นและทำอะไรได้

ระบบสิทธิ์มี 4 ชั้น และทำงานต่างกันมาก​

นี่เป็นจุดที่สับสนง่ายที่สุดของทั้งระบบ จึงต้องแยกให้ชัด

  1. สิทธิ์ระดับการกระทำ (policy) — ประกาศไว้ครบทุก route แต่ยังปิดการบังคับใช้อยู่ ตัวตรวจ policy ปล่อยผ่านทุกคำขอ ยกพฤติกรรมมาจากระบบเดิมที่ก็ปล่อยผ่านเช่นกัน ผลคือข้อมูลสิทธิ์ในตาราง system_module และ system_role_module ถูกใช้เพื่อซ่อน/แสดงเมนูใน CMS เท่านั้น ไม่ได้ปิดกั้น API จริง ผู้ใช้ที่ล็อกอินแล้วและรู้ path ยังเรียก endpoint ที่เมนูซ่อนไว้ได้
  2. ตัวตรวจผู้ดูแลสูงสุด (SuperAdmin) — บังคับใช้จริง ตรวจจากบทบาทของผู้ใช้
  3. ModuleGate — บังคับใช้จริง ให้ผู้ดูแลระดับแพลตฟอร์มปิดโมดูลของแต่ละองค์กรได้ ถ้าโมดูลถูกปิด ผู้ใช้ฝั่งลูกค้าที่เรียก API ตรงก็ได้ 403
  4. AppEnabledGuard — บังคับใช้จริง แอปเสริม (นัดหมาย / สะสมแต้ม / กระดานประกาศ) ต้องถูกเปิดให้องค์กรก่อน

สรุปที่ควรจำ: สิ่งที่หน้าเว็บอ่านไปตัดสินใจแสดงเมนู กับสิ่งที่ปิดกั้นได้จริงฝั่งเซิร์ฟเวอร์ ไม่ใช่กลไกเดียวกัน การซ่อนเมนูไม่ใช่การควบคุมความปลอดภัย และตัวที่ปิดกั้นจริงมีเพียง 3 ชั้นหลัง

คำขออ่านสิทธิ์ที่โครงหน้าจอเรียก​

GET /api/user/:id/permission คือแหล่งของชุดความสามารถที่เมนูและแถบคำสั่งด่วนใช้ พฤติกรรมฝั่ง backend:

  • อ่านได้เฉพาะสิทธิ์ของตัวเองเท่านั้น ถ้าส่งรหัสผู้ใช้คนอื่นมาจะถูกปฏิเสธ endpoint นี้ไม่ได้ประกาศ policy ใด ๆ แต่จำกัดขอบเขตด้วยตรรกะภายในบริการแทน
  • backend โหลดสิทธิ์พื้นฐานตามบทบาทของผู้ใช้ แล้วประกอบกับรายชื่อโมดูลทั้งหมดในระบบ
  • ถ้าผู้ใช้เป็นฝั่งลูกค้าและองค์กรนั้นมีการตั้งค่าโมดูลไว้เอง ระบบจะใช้ค่าที่ผู้ดูแลแพลตฟอร์มกำหนดแทนค่าพื้นฐานของบทบาท นี่คือเหตุผลที่ผู้ใช้บทบาทเดียวกันคนละองค์กรอาจเห็นเมนูไม่เหมือนกัน
  • ถ้าผู้ใช้เป็นฝั่งผู้ให้บริการแพลตฟอร์ม จะใช้ค่าพื้นฐานตามบทบาทเสมอ ไม่ถูกทับด้วยการตั้งค่าขององค์กร
  • ผลที่คืนกลับเป็นรายการคู่ของชื่อโมดูลกับการกระทำที่ทำได้ ให้หน้าเว็บนำไปตัดสินใจแสดงเมนู

ModuleGate ปล่อยผ่านเมื่อไม่แน่ใจ แต่การเปิดแอปปฏิเสธไว้ก่อน​

  • ModuleGate อ่านชนิดของผู้เรียกจาก context ถ้าเป็นผู้ให้บริการแพลตฟอร์ม (หรือระบุตัวตนไม่ได้) จะปล่อยผ่านไปเลย ส่วนผู้ใช้ฝั่งลูกค้าจะถูกตรวจว่าองค์กรเปิดโมดูลนั้นไว้หรือไม่ ด้วยตรรกะค่าพื้นฐาน-ทับด้วยค่าองค์กรชุดเดียวกับคำขออ่านสิทธิ์
  • ถ้าการตรวจสอบเกิดข้อผิดพลาด ModuleGate จะปล่อยผ่านพร้อมบันทึก log เจตนาคือไม่ให้ปัญหาชั่วคราวของฐานข้อมูลไปล็อกทั้งองค์กรออกจากโมดูลที่จ่ายเงินใช้อยู่
  • ตรงกันข้าม การเปิด/ปิดแอปเสริมจะปฏิเสธไว้ก่อนเมื่อไม่แน่ใจ เพราะความเสี่ยงตรงนั้นคือการยกระดับสิทธิ์ตัวเอง ไม่ใช่แค่การใช้งานสะดุด
  • โมดูลที่ถูกครอบด้วย ModuleGate จริงในปัจจุบัน ได้แก่ กุญแจ API, ตั้งค่าคุณสมบัติผู้ใช้, กลุ่มผู้ชม, ตอบกลับอัตโนมัติ, แคมเปญ, ฐานข้อมูลลูกค้า, ตัวสร้างฟอร์ม, ติดตามที่มาของเพื่อน, นำเข้าแบบจับคู่คอลัมน์, รายงานเพื่อนทั้งหมด, ริชเมนู, ริชเมสเสจ, ข้อความเทมเพลต, กฎอัตโนมัติ และ workflow — โมดูลอื่นนอกรายการนี้ไม่มีการปิดกั้นระดับโมดูลฝั่งเซิร์ฟเวอร์

ชั้นแกนกลาง HTTP ที่ทำให้ทุกหน้าจอทำงานเหมือนกัน​

  • ทุกคำขอวิ่งผ่านลำดับ middleware คงที่ เริ่มจากการสร้าง context ต่อคำขอ ตามด้วยส่วนหัวความปลอดภัย, CORS, การบันทึก log, ตัวแปลงข้อผิดพลาด และการจำกัดอัตราระดับทั้งระบบ
  • context ต่อคำขอเก็บรหัสผู้ใช้ องค์กร LINE OA บทบาท และภาษา โดย JWT guard เป็นผู้เติมค่า และทุกบริการอ่านจากที่นี่เพื่อจำกัดขอบเขตข้อมูลตามองค์กร นี่คือเหตุผลที่ไม่มี endpoint ใดรับรหัสองค์กรจาก payload
  • ไม่มีโมดูลใดเขียน JSON ตอบกลับเอง ทุกโมดูลเรียกผ่านตัวช่วยกลาง ทำให้ endpoint ทั้งหมด (มากกว่า 280 เส้น) มีรูป response เดียวกัน มีรหัสข้อผิดพลาดชุดเดียวกัน และมีข้อความทั้งภาษาไทยและอังกฤษ
  • ตัวแปลงข้อผิดพลาดแปลงทุกความผิดพลาดเป็นรูปเดียวกันที่มีรหัส ข้อความ และข้อมูลประกอบ โดยแม็ปสถานะเป็นรหัส เช่น สถานะ 401 เป็นรหัสหนึ่ง สถานะ 500 เป็นอีกรหัสหนึ่ง และข้อผิดพลาดจากการตรวจข้อมูลที่มาเป็นรายการก็มีรหัสของตัวเอง — นี่คือที่มาของรูปข้อผิดพลาด 3 แบบที่หน้าเว็บต้องรองรับ
  • นอกจากการจำกัดอัตราระดับทั้งระบบ ยังมีการจำกัดเฉพาะบาง endpoint เช่นการรีเซ็ตรหัสผ่านที่จำกัดไว้เข้มกว่ามาก
  • รูปของการแบ่งหน้าถูกกำหนดไว้ที่ชั้นกลางเช่นกัน มีทั้งแบบที่คืนเฉพาะข้อมูลกับจำนวนรวม และแบบเต็มที่มีหน้าปัจจุบัน จำนวนต่อหน้า และจำนวนหน้าทั้งหมด

ข้อสังเกตเรื่องความเข้ากันได้กับระบบเดิม​

บริการนี้ถูกแปลงมาจากระบบเดิมแบบตรงตัวทีละเมท็อด และ จงใจรักษาพฤติกรรมเดิมไว้ทุกอย่าง รวมถึงข้อบกพร่องบางตัวที่มีหมายเหตุกำกับในโค้ดว่าห้ามแก้ เหตุผลคือหน้าเว็บ CMS พึ่งพารูป response เดิมอยู่ การ "แก้ให้ถูกต้อง" ฝั่งเดียวจะทำให้หน้าจอพัง ผู้ที่จะปรับพฤติกรรม API จึงต้องแก้ทั้งสองฝั่งพร้อมกันเสมอ

พฤติกรรมตอนเริ่มบริการ​

ถ้าเชื่อมต่อฐานข้อมูลหลักไม่ได้ บริการจะพยายามซ้ำแล้วหยุดทำงาน ส่วนระบบเสริมอย่างแคชและคิวข้อความจะลดระดับการทำงานลงเงียบ ๆ ไม่ขัดขวางการเริ่มบริการ ผลคือ บริการอาจขึ้นได้ตามปกติทั้งที่คิวข้อความใช้ไม่ได้ ซึ่งอธิบายว่าทำไมงานเบื้องหลังบางอย่างจึงเงียบหายโดยที่หน้า CMS ไม่แสดงความผิดพลาด