EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / DirectAdmin / เขียนค่าลง DirectAdmin ตอนที่ API มีแต่ฝั่งอ่าน ต้องขับฟอร์มของแผงเอง
DirectAdmin

เขียนค่าลง DirectAdmin ตอนที่ API มีแต่ฝั่งอ่าน ต้องขับฟอร์มของแผงเอง

โดย Ton-Or ·14 ต.ค. 2026·อ่าน 9 นาที
เขียนค่าลง DirectAdmin ตอนที่ API มีแต่ฝั่งอ่าน ต้องขับฟอร์มของแผงเอง

คำสั่ง API ที่ผู้ผลิตทำมาให้ไม่ได้ครอบคลุมทุกอย่างที่แผงควบคุมทำได้ พอถึงจุดที่ต้องเขียนค่าลงไปแต่ไม่มี endpoint ทางการรองรับ เราต้องขับฟอร์มของแผงตรงๆ ซึ่งเป็นงานที่ทำได้แต่ต้องวางกติกาให้แน่นก่อนกดรันจริง

สรุปใน 1 นาที

อาการ/ปัญหา
DirectAdmin CMD_API มีแต่คำสั่งอ่าน แอปหลังบ้านเลยสร้างหรือแก้แพ็กเกจโฮสติ้งไม่ได้
ใครควรอ่าน
นักพัฒนาที่ต้องเขียนค่าลง DirectAdmin จากแอปของตัวเอง
ทางแก้แบบสั้น
POST เข้าฟอร์มของแผงควบคุมเอง ยืนยันผลโดยไม่ใช้ regex และมีสวิตช์ตัดวงจรสองชั้นที่ปิดไว้เป็นค่าตั้งต้น
ข้ามไปขั้นตอน

พอดีผมทำระบบหลังบ้านตัวหนึ่งที่ต้องให้แอดมินแก้แพ็กเกจโฮสติ้งจากหน้าเว็บของแอปเอง ไม่ต้องสลับไปเปิดแผง DirectAdmin อีกจอ ตอนวางแผนคิดว่าง่าย เพราะ DirectAdmin มี CMD_API_* ให้ครบ พอลงมือจริงถึงรู้ว่าฝั่งแพ็กเกจของผู้ใช้มันมีแต่ คำสั่งอ่าน ส่วนคำสั่งสร้างกับแก้ไม่มี endpoint ทางการให้ ทางเดียวที่เหลือคือ POST เข้าฟอร์มของแผงตรงๆ เหมือนที่เบราว์เซอร์ทำ ซึ่งมันทำได้ครับ แต่ต้องระวังคนละแบบกับการเรียก API ปกติ เพราะเราไม่มีสัญญาจากผู้ผลิตว่าฟิลด์จะไม่เปลี่ยน และไม่มีรหัสตอบกลับที่อ่านง่ายให้เช็ค

โฮสต์ พอร์ต ชื่อแพ็ก ชื่อผู้ใช้ในบทความนี้เป็นค่าสมมุติทั้งหมดนะครับ ส่วนชื่อ endpoint กับชุดฟิลด์คือของจริงที่ผมลองมาแล้ว

แยกให้ออกก่อน: อันไหนอ่านได้ อันไหนต้องขับฟอร์ม

เสียเวลาไปพอสมควรกับการเดาว่ามี endpoint ที่ยังหาไม่เจอ สุดท้ายผมนั่งไล่ทีละตัวแล้วทำตารางไว้เลย

มี endpoint ทางการ

เรียกแล้วได้ข้อมูลที่ parse ได้

รายชื่อแพ็กทั้งหมด ใช้ CMD_API_PACKAGES_USER?action=list&creator=admin ตอบกลับเป็นข้อความ url-encoded แยกบรรทัด แกะง่าย เชื่อรูปแบบได้ เพราะผู้ผลิตประกาศไว้ว่าเป็น API

ไม่มี ต้องขับฟอร์มเอง

เรียกแล้วได้หน้าจอของคน

ดูค่าในแพ็ก ใช้ CMD_SHOW_USER_PACKAGE?package=ชื่อ ได้ HTML มาต้องแกะเอง · สร้างหรือแก้แพ็ก ต้อง POST เข้า CMD_MANAGE_USER_PACKAGES ซึ่งตอบ 302 แล้วตามไปเจอ HTML เต็มหน้า · ลบแพ็ก ใช้ปลายทางเดียวกันแต่ต้องส่งธงยืนยันเพิ่ม

ข้อสังเกตที่ทำให้ไม่ต้องหลงทาง

ชื่อที่ขึ้นต้นด้วย CMD_API_ คือสัญญากับเครื่อง ตอบกลับเป็นคู่คีย์ค่าที่ parse ได้ ส่วนชื่อที่ขึ้นต้นด้วย CMD_ เฉยๆ คือหน้าจอสำหรับคน ตอบกลับเป็น HTML ถ้า endpoint ที่คุณต้องใช้ไม่มีคำว่า API อยู่ในชื่อ แปลว่าคุณกำลังจะขับหน้าจอของคน ไม่ใช่เรียก API เตรียมใจไว้ตั้งแต่ตรงนั้น

ลองยิงด้วย curl ดูก่อนเขียนโค้ดจริงเสมอ จะได้เห็นหน้าตาคำตอบกับมั่นใจว่าสิทธิ์พอ

# ฝั่งอ่าน - ตอบกลับเป็น url-encoded ใช้ได้เลย
curl -sS -u "admin:<DA-LOGIN-KEY>" \
  "https://panel.example.com:2222/CMD_API_PACKAGES_USER?action=list&creator=admin"

# ฝั่งเขียน - ตอบกลับเป็น HTML ทั้งหน้า ไม่มีรหัสสถานะให้เช็คง่ายๆ
curl -sS -u "admin:<DA-LOGIN-KEY>" -L \
  -d "packagename=demo-basic" -d "add=Save" -d "skin=enhanced" -d "language=en" \
  "https://panel.example.com:2222/CMD_MANAGE_USER_PACKAGES" | head -40

ผมใช้ login key ที่สร้างจากแผงแทนรหัสผ่านแอดมินจริง แล้วจำกัดสิทธิ์ของคีย์ให้เหลือเฉพาะคำสั่งที่ต้องใช้ ถ้าโค้ดหลุดหรือ log รั่ว ความเสียหายจำกัดอยู่แค่นั้น ค่าคีย์อ่านจาก environment ล้วน ไม่มีวางในซอร์สสักบรรทัด

ฟอร์มต้องส่งครบชุด ไม่ใช่ส่งแค่ฟิลด์ที่อยากแก้

จุดที่เสียเวลามากที่สุดคือตอนแรกผมส่งเฉพาะฟิลด์ที่เปลี่ยน แล้วงงว่าทำไมค่าอื่นหายเกลี้ยง ฟอร์มของแผงมันไม่ได้ทำงานแบบ PATCH มันคือ "ส่งทั้งฟอร์มมาแล้วเขียนทับ" ฟิลด์ไหนไม่ส่งมาคือถือว่าปิดหรือเป็นศูนย์

กติกาของฟอร์มอ่านค่าเดิมSHOW_USER_PACKAGE->รวมกับค่าใหม่merge ในแอป->POST ทั้งชุดทุกฟิลด์->อ่านกลับยืนยันre-list

ชุดฟิลด์บังคับที่ขาดไม่ได้มีสี่ตัว ขาดตัวใดตัวหนึ่งฟอร์มจะเงียบหรือทำงานครึ่งๆ กลางๆ

  1. packagename ชื่อแพ็ก ส่งทุกครั้งทั้งตอนสร้างและตอนแก้
  2. add=Save คือปุ่มที่แผงคาดว่าจะถูกกด ไม่ส่งมาแผงไม่รู้ว่าจะทำอะไร
  3. skin กับ language แผงเอาไปเลือกหน้าจอที่จะตอบกลับ ไม่ส่งมาบางเวอร์ชันตอบ 500
  4. package=<ชื่อ> ใส่เพิ่มเฉพาะตอน "แก้" เพื่อบอกว่าให้เขียนทับแถวเดิม ไม่ใส่คือสร้างใหม่

ตัวประกอบฟอร์มฝั่งแอปผมเขียนแบบนี้ ให้ค่าตัวเลขกับสวิตช์ไม่จำกัดเดินคู่กันเสมอ

private function buildForm(array $resources, bool $isUpdate, string $name): array
{
    $limits   = $resources['limits']    ?? [];
    $unlim    = $resources['unlimited'] ?? [];
    $features = $resources['features']  ?? [];

    $form = [
        'packagename' => $name,
        'skin'        => 'enhanced',
        'language'    => 'en',
        'add'         => 'Save',
    ];

    // มี package= อยู่ในฟอร์ม = แผงตีความว่าแก้ของเดิม ไม่มี = สร้างใหม่
    if ($isUpdate) {
        $form['package'] = $name;
    }

    // [ชื่อฟิลด์ในฟอร์ม, คีย์ค่าตัวเลข, คีย์สวิตช์ไม่จำกัด]
    $pairs = [
        ['bandwidth', 'bandwidth_mb', 'bandwidth'],
        ['quota',     'quota_mb',     'quota'],
        ['inode',     'inode',        'inode'],
        ['vdomains',  'vdomains',     'vdomains'],
        ['nemails',   'nemails',      'nemails'],
        ['mysql',     'mysql',        'mysql'],
        ['ftp',       'ftp',          'ftp'],
    ];

    foreach ($pairs as [$field, $limitKey, $unlimKey]) {
        // ส่งค่าตัวเลขเสมอ แม้จะตั้งไม่จำกัด เพื่อให้สแนปช็อตในฐานเราตรงกับที่ส่งไป
        $form[$field] = (string) (int) ($limits[$limitKey] ?? 0);
        if (! empty($unlim[$unlimKey])) {
            $form['u' . $field] = 'ON';    // u + ชื่อฟิลด์ = ไม่จำกัด
        }
    }

    // สวิตช์เปิด/ปิดฟีเจอร์: ส่งเฉพาะตอนเปิด ค่าเป็น ON เท่านั้น
    foreach (['cgi', 'php', 'ssl', 'ssh', 'cron', 'catchall', 'spam'] as $f) {
        if (! empty($features[$f])) {
            $form[$f] = 'ON';
        }
    }

    return $form;
}
สวิตช์ส่งได้ค่าเดียวคือ ON

อย่าส่ง ssl=OFF หรือ ssl=0 หรือ ssl=false เพราะแผงไม่ได้อ่านค่า มันอ่านแค่ว่า "มีคีย์นี้ในฟอร์มไหม" ตามธรรมเนียมของ checkbox ใน HTML ส่งค่าอะไรไปที่ไม่ใช่ ON ก็อาจถูกตีว่าเปิดอยู่ดี ถ้าจะปิดให้ตัดคีย์นั้นออกจากฟอร์มไปเลย ส่วนค่าไม่จำกัดใช้คำนำหน้า u เช่น uquota=ON แล้วค่าตัวเลขที่ส่งคู่กันแผงจะเมิน แต่ผมยังส่งไปด้วยเพื่อให้สแนปช็อตฝั่งเราไม่เพี้ยน

ห้ามตัดสินผลจาก response เด็ดขาด

อันนี้คือกฎที่ผมอยากเน้นที่สุดในบทความนี้ ตอนแรกผมทำเหมือนที่ทุกคนทำคือเขียน regex ไปจับคำว่า saved หรือ class ที่ชื่อ info ในหน้า HTML ที่ตอบกลับมา แล้วสรุปว่าสำเร็จ ปัญหาคือฟอร์มของแผงตอบ 302 แล้วตัว HTTP client ตามต่อไปเอง สิ่งที่เราได้กลับมาคือหน้าถัดไปทั้งหน้า ซึ่งมีคำว่า saved มีคลาสชื่อ info มีทุกอย่างที่ regex จะจับได้ แม้ในกรณีที่คำสั่งของเราถูกปฏิเสธเงียบๆ

แอปหลังบ้านแผง DirectAdminPOST CMD_MANAGE_USER_PACKAGES302 แล้วตามไปเจอ HTML เต็มหน้าไม่ตัดสินจากหน้านี้ ซึ่งมีคำว่า saved ได้ทั้งที่ล้มเหลวGET CMD_API_PACKAGES_USER?action=listรายชื่อแพ็กแบบ url-encodedชื่อแพ็กอยู่ในรายการ หรือค่าที่อ่านกลับตรงกับที่ส่ง = สำเร็จregex จับข้อความใช้ได้แค่ตอนเขียนสาเหตุให้แอดมินอ่านเวลาล้มเหลว
ขากลับของ POST เป็นหน้า HTML เต็มหน้าที่ตัดสินผลไม่ได้ ผลจริงต้องมาจากการเรียกฝั่งอ่านซ้ำอีกรอบ
วิธีที่ผมทำตอนแรก

เดาผลจากข้อความในหน้า

จับคำว่า saved หรือคลาส info ในบอดี้ที่ตอบกลับ ผลคือได้ "สำเร็จ" ปลอมเป็นระยะ เพราะหน้าที่เปลี่ยนทางไปมันมีคำพวกนั้นอยู่แล้วตามปกติ แล้วฐานข้อมูลฝั่งเราก็บันทึกว่าซิงก์แล้วทั้งที่ของบนแผงไม่ขยับ

วิธีที่ใช้จริงตอนนี้

ยิงเสร็จแล้วไปอ่านของจริงซ้ำ

POST เสร็จ โยนบอดี้ทิ้ง แล้วเรียก endpoint ฝั่งอ่านใหม่ ถ้าชื่อแพ็กโผล่ในรายการ (กรณีสร้าง) หรือค่าที่อ่านกลับมาตรงกับที่ส่งไป (กรณีแก้) ถึงจะนับว่าสำเร็จ regex ใช้แค่ตอนจะเขียนข้อความบอกสาเหตุให้แอดมินอ่านเวลาล้มเหลว

$body = $this->da->postRawForm('CMD_MANAGE_USER_PACKAGES', $form);

// ตัวตัดสินคือการอ่านกลับ ไม่ใช่เนื้อหาใน $body
if ($this->existsOnDa($name)) {
    $product->forceFill([
        'da_sync_status' => 'synced',
        'da_synced_at'   => now(),
        'da_sync_error'  => null,
    ])->save();

    return ['ok' => true, 'message' => "แพ็ก {$name} ซิงก์แล้ว (ยืนยันด้วยการอ่านกลับ)"];
}

// ถึงตรงนี้คือล้มจริง ค่อยแกะ $body มาทำข้อความบอกสาเหตุ
$parsed = $this->parseResponse($body);
Log::warning('da package sync failed', [
    'package'  => $name,
    'response' => mb_substr($body, 0, 2000),   // เก็บไว้ให้คนไล่ต่อได้
]);

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

ดึงค่ากลับมาก่อน อย่าเขียนทับฟิลด์ที่เราไม่รู้จัก

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

public function pull(Product $product): array
{
    $this->assertSyncAllowed();
    $name = $this->requirePackageName($product);

    $body      = $this->da->getRaw('CMD_SHOW_USER_PACKAGE', ['package' => $name]);
    $resources = $this->parseShowPackage($body);

    if ($resources === null) {
        // แกะไม่ออก = อย่าเดา อย่าเขียนค่าว่างทับของเดิม
        Log::warning('da package pull parse failed', [
            'package' => $name,
            'body'    => mb_substr($body, 0, 2000),
        ]);
        return ['ok' => false, 'message' => "แกะหน้า CMD_SHOW_USER_PACKAGE ของ {$name} ไม่ออก ดู log"];
    }

    $product->forceFill([
        'da_resources'   => $resources,
        'da_sync_status' => 'synced',
        'da_synced_at'   => now(),
    ])->save();

    return ['ok' => true, 'resources' => $resources];
}

ตัวแกะหน้า HTML ของผมทำงานด้วย regex ล้วน ซึ่งเปราะแน่นอน กติกาที่ผมวางไว้คือ ฟิลด์ไหนแกะไม่ได้ ให้ตกกลับไปใช้ค่าที่มีอยู่ในฐานเรา ไม่ใช่เขียนศูนย์ทับ และถ้าแกะทั้งหน้าไม่ออกเลย (เช่นแผงตอบหน้า login เพราะคีย์หมดอายุ) ให้ถือว่าล้มเหลวทั้งก้อน ห้ามบันทึกอะไรลงฐาน เพราะการเขียนค่าว่างลงไปแล้วกดส่งกลับรอบถัดไป เท่ากับล้างค่าของลูกค้าด้วยมือเราเอง

ลบต้องยืนยันสองชั้น แล้วอย่าลบอัตโนมัติ

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

$body = $this->da->postRawForm('CMD_MANAGE_USER_PACKAGES', [
    'delete'    => 'yes',
    'confirmed' => 'Confirm',   // ขาดตัวนี้ แผงจะเด้งหน้าถามยืนยันแทนที่จะลบ
    'delete0'   => $name,       // ลำดับเริ่มที่ศูนย์ ลบทีละรายการ
]);

// ลบก็เหมือนเดิม: ยืนยันด้วยการอ่านกลับ ไม่ใช่ดูข้อความในหน้า
if (! $this->existsOnDa($name)) {
    $product->forceFill(['da_sync_status' => 'unsynced', 'da_synced_at' => null])->save();
    return ['ok' => true, 'message' => "ลบแพ็ก {$name} ออกจากแผงแล้ว"];
}
return ['ok' => false, 'message' => "แพ็ก {$name} ยังอยู่บนแผง ลบไม่สำเร็จ"];
ลบแพ็กไม่ได้ลบบัญชีที่ใช้แพ็กนั้นอยู่

บัญชีที่สร้างไปแล้วจะถือค่าที่ก๊อปมาตอนสร้าง ลบแพ็กทีหลังไม่ได้ทำให้บัญชีเหล่านั้นเปลี่ยน แต่ความสับสนจะมาทีหลังตอนมีคนถามว่าลูกค้ารายนี้อยู่แพ็กไหน แล้วหาไม่เจอเพราะแพ็กถูกลบไปแล้ว ผมเลยเลือกวิธีเปลี่ยนชื่อแพ็กเป็นคำว่าเลิกใช้แล้วนำหน้าแทนการลบจริง เว้นแต่เป็นแพ็กที่สร้างผิดและไม่เคยมีบัญชีไหนใช้

สวิตช์ตัดวงจรสองตัว แยกตามขนาดความเสียหาย

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

แพ็กกระทบค่าตั้งต้น
บัญชีกระทบลูกค้าตรง
DENYค่าตั้งต้นทั้งคู่
2 ชั้นเปิดแยกกันได้

สวิตช์ตัวแรกคุมงานระดับตั้งค่า เช่น สร้างหรือแก้แพ็ก ผิดพลาดแล้วกระทบ "ค่าตั้งต้นของบัญชีที่จะสร้างต่อจากนี้" ยังพอมีเวลาแก้ ส่วนสวิตช์ตัวที่สองคุมงานที่แตะบัญชีผู้ใช้จริง เช่น ระงับบัญชี เปลี่ยนรหัส ลบ ผิดพลาดแล้วลูกค้าเว็บล่มทันที ผมแยกคนละตัวแปรและ ปิดทั้งคู่เป็นค่าตั้งต้น

// config/services.php
'directadmin' => [
    'base_url'  => env('DA_BASE_URL'),
    'user'      => env('DA_USER'),
    'key'       => env('DA_LOGIN_KEY'),

    // ชั้นที่ 1 - ห้ามเขียนอะไรที่กระทบบัญชีลูกค้า (ปิดไว้ก่อน)
    'read_only' => env('DA_READ_ONLY', true),

    // ชั้นที่ 2 - อนุญาตให้ซิงก์แพ็ก แยกอิสระจากชั้นแรก (ปิดไว้ก่อน)
    'allow_package_sync' => env('DA_ALLOW_PACKAGE_SYNC', false),
],
private function assertSyncAllowed(): void
{
    if (! (bool) config('services.directadmin.allow_package_sync', false)) {
        throw new DirectAdminException(
            'การซิงก์แพ็กถูกปิดอยู่ ตั้ง DA_ALLOW_PACKAGE_SYNC=true ใน .env '
            . '(คนละตัวกับ DA_READ_ONLY) แล้วล้าง config cache ก่อนลองใหม่',
            'CMD_MANAGE_USER_PACKAGES',
        );
    }
}

ทุกเมธอดสาธารณะเรียกตัวนี้เป็นบรรทัดแรกเสมอ ไม่ใช่เช็คใน controller เพราะ controller มีหลายทาง แต่ service มีทางเดียว ส่วนงานที่แตะบัญชีลูกค้าจะเรียกตัวกันอีกตัวที่ดู read_only แยกกัน แปลว่าเปิดให้แอดมินจัดการแพ็กได้โดยที่ฝั่งบัญชีลูกค้ายังล็อกอยู่ ซึ่งเป็นสถานะที่ผมรันจริงอยู่ตอนนี้

ค่าตั้งต้นต้องเป็นปฏิเสธ ไม่ใช่อนุญาต

ถ้าเขียน env('DA_READ_ONLY', false) แล้ววันหนึ่งมีคนก๊อป .env ไปขึ้นเครื่องใหม่โดยลืมบรรทัดนั้น ระบบจะเปิดให้เขียนเต็มที่ทันทีโดยไม่มีใครสั่ง กลับกันถ้าค่าตั้งต้นเป็นปฏิเสธ สิ่งที่เกิดคือมีคนบ่นว่าปุ่มกดไม่ได้ ซึ่งแก้ได้ในสามสิบวินาที ความผิดพลาดสองแบบนี้ราคาต่างกันมาก เลือกทางที่พังแล้วเงียบไว้ก่อน

สถานะระหว่างทางกับร่องรอยที่ต้องเก็บ

งานที่ยิงข้ามเครื่องแบบนี้มีช่วงที่ "ส่งไปแล้วแต่ยังไม่รู้ผล" ผมเลยเขียนสถานะลงฐานก่อนยิง HTTP ไม่ใช่หลังยิง ถ้าคำขอค้างหรือมีคนกด stop กลางทาง แอดมินจะเห็นคำว่ากำลังซิงก์ค้างอยู่ แล้วรู้ว่าต้องไปตรวจของจริงบนแผง ดีกว่าเห็นสถานะเก่าค้างแล้วเข้าใจว่าไม่มีอะไรเกิดขึ้น

// ทำเครื่องหมายก่อนยิง ไม่ใช่หลังยิง
$product->forceFill([
    'da_sync_status' => 'syncing',
    'da_sync_error'  => null,
])->save();

ส่วน audit log ผมเก็บทั้งตอนสำเร็จและตอนล้ม โดยเก็บ "ค่าหลังเปลี่ยน" ไว้ด้วย ไม่ใช่แค่คำว่าสำเร็จ เพราะเวลาลูกค้าทักมาว่าโควตาตัวเองเปลี่ยนไปเอง สิ่งที่ตอบได้จริงคือบันทึกว่าใครกด เมื่อไหร่ ค่าเดิมเท่าไหร่ ค่าใหม่เท่าไหร่ ไม่ใช่แค่เดา

# คำสั่งสำหรับไล่ตรวจจากบรรทัดคำสั่ง ก่อนเปิดสวิตช์ให้แอดมินใช้จริง
php artisan cb:da:list-packages          # อ่านอย่างเดียว ดูว่าเชื่อมต่อได้ไหม
php artisan cb:da:pull demo-basic        # ดึงค่าจริงลงฐาน ไม่เขียนกลับ
php artisan config:clear                 # แก้ .env แล้วต้องล้าง cache เสมอ
สรุปกฎที่ผมใช้กับงานขับฟอร์มทุกตัว

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

ส่วนตัวผมมองว่าการขับฟอร์มแบบนี้เป็นทางออกที่ยอมรับได้ แต่ต้องรู้ตัวตลอดว่าเรากำลังใช้ของที่ผู้ผลิตไม่ได้สัญญาว่าจะคงรูปแบบไว้ วันที่เขาอัปเดตแผงแล้วเปลี่ยนชื่อฟิลด์ ระบบเราจะพังเงียบ ตัวยืนยันด้วยการอ่านกลับกับ log ที่เก็บบอดี้ไว้คือสองอย่างที่ทำให้รู้ตัวเร็วแทนที่จะรู้ตอนลูกค้าโทรมา ใครเอาไปใช้กับ endpoint อื่นของแผงเดียวกันก็ยึดกติกาชุดนี้ได้เหมือนกันครับ ตรงไหนที่เวอร์ชันแผงของใครฟิลด์ไม่ตรงกับที่ผมเขียนไว้ เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย