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 ไม่แสดงความผิดพลาด