เครื่องมือกลางใน internal/platform
แพ็กเกจเล็ก ๆ ที่ handler ของโมดูล (market, tms, wms, procure, autocare, etax) ใช้ร่วมกัน: สร้างเลขที่เอกสารตามเวลา, แปลงค่า Go เป็น literal ของ PostgreSQL, ตัดคำไทยเพื่อค้นหาแบบ ILIKE, และตั้ง header ความปลอดภัย. ส่วนอื่นของ platform อยู่ในโน้ตเฉพาะ: apperr → รูปแบบ error และ response ของ API (apperr + response helper), config → ตัวแปรสภาพแวดล้อมและการโหลด config ของ backend (Environment config), db → ตัวจัดการ connection pool ต่อฐานข้อมูล (PoolManager), httpserver/logger → วงจรชีวิตเซอร์วิสและการรัน binary เดียวหลาย OS (Service lifecycle).
ทำงานอย่างไร
docno — เลขที่เอกสารตามเวลา (docno/docno.go)
Stamp(layout)(:23) = เวลาปัจจุบันตาม layout + มิลลิวินาที 3 หลัก +-+ แท็กสุ่ม 4 ฐานสิบหกตัวพิมพ์ใหญ่ (จากcrypto/rand), เช่น layout150405→153045123-9F2C.- เหตุผลที่เขียนไว้ในแพ็กเกจ:
time.Now().Format("150405")ซ้ำกันทุกคำขอในวินาทีเดียว ทำให้สองออเดอร์ได้order_no/trip_no/move_doc_noเดียวกัน (docno.go:1-9). - ใช้ใน handler ของ
autocare,etax,market,procure,tms,wms(เช่นdocno.Stamp("0405")ที่autocare/handler.go:997). เป็นเลขของโมดูล dashboard ไม่ใช่เลขเอกสารบัญชี — เลขเอกสารหลัก (ic_transฯลฯ) มาจากรูปแบบdoc_formatในชั้นprocess(ภาพรวมกลุ่ม process ของ Backend (Process overview)).
sqllit — literal ของ PostgreSQL (sqllit/sqllit.go)
- มีไว้ให้ handler ที่ส่งหลายคำสั่งเข้า
ExecBatchUseCase(รับ SQL สตริงล้วน ไม่มี bind parameter, ทำใน transaction เดียว — Query Gateway: /query/list, limit, batch, exec, execBatch). Escape(:14) ซ้ำเครื่องหมาย';Literal(:20):nil→NULL,string→ใส่'…'พร้อม escape,bool→TRUE/FALSE,int/int64→ตัวเลขเปล่า,float64→ตัวเลขเปล่า (NaN/Inf→NULL), ชนิดอื่นทั้งหมด→fmt.Sprintแล้วครอบเป็นสตริง.Insert(table, columns, values)(:49) ประกอบINSERT INTO t (cols) VALUES (…)รูปแบบเดียวกับappblob.InsertInput; ชื่อตาราง/คอลัมน์ไม่ถูก quote ต้องเป็นค่าที่เชื่อถือได้ (คอมเมนต์:44-48).- ผู้ใช้:
autocare,market,procure,tms,wms(ประมาณ 100 จุดเรียกในadapter/inbound/http).
search — ค้นหาไทย/อังกฤษแบบ AND ILIKE (search/tokenizer.go)
SegmentSearchTokens(:290): แบ่งข้อความเป็นช่วงตามชนิดอักษร THAI / LATIN / DIGIT (classifyRune,:275) — อักขระอื่นทั้งหมดเป็นตัวคั่น ไม่ถูกส่งต่อ; ช่วงไทยตัดเป็นพยางค์ด้วยSegmentThaiChunk(:164) โดยเทียบรายการพยางค์/รากคำเรียงยาวไปสั้น (thaiAtomicSyllables,:12) เช่นลังกระดาษ→ลัง,กระ,ดาษ; token ซ้ำ (ไม่สนตัวพิมพ์) ถูกตัดทิ้ง.ExpandTokenAliases(:96) ขยาย token เป็นคำพ้อง B2B จากthaiB2BSynonymMap(:59-87: หน่วย/บรรจุภัณฑ์ เช่น ลัง/กล่อง/พาเลท, กก/กิโล, ขวด/แกลลอน และคำพ้องสินค้ายอดนิยมบางคำ) ส่วนcompoundExclusions(:90) กันคำสั้นนม/ชาไปจับกลางคำอื่น (เช่นขนม,วิชา).- ฝั่ง SQL:
BuildAndILikeWhere(raw, exprs)(:349) =((e ILIKE '%t1%' OR e ILIKE '%alias%') AND (...))— ทุก token ต้องเจอ;BuildPartialOrILikeWhere(:376) = OR ทุก token/alias ใช้ดึงผู้สมัครเมื่อ AND ไม่ได้ผล. การป้องกัน SQL:escapeSQLQuoteซ้ำ'อย่างเดียว (:343) แต่เพราะ token มีแค่ตัวอักษรไทย/ละติน/ตัวเลข ตัว',%,_จึงไม่เข้ามาในคำค้น. - ฝั่งหน่วยความจำ (กรอง/จัดอันดับหลังดึงข้อมูล):
MatchAndLike(:402),CountMatchedTokens(:418),MinPartialThreshold(:434; token 1 → 1, 2-3 → N-1, ≥4 → น้อยกว่าระหว่าง N-1 กับ 65%),ScoreSearchMatch(:451) ให้คะแนน: รหัสสินค้าตรงทั้งหมด 2000 / ขึ้นต้น 1200, ชื่อขึ้นต้นด้วยคำค้นทั้งประโยค 1000 / มีอยู่ 650, ขึ้นต้นด้วย token แรก 350, ต่อ token ในชื่อ 150 (ตรงคำเดิม) หรือ 95 (ตรงคำพ้อง), ต่อ token ในช่องอื่น 70/45, ครบทุก token +500. - ผู้ใช้เดียวที่พบ:
market/handler.go(:856,867,1035-1054).
security — header ความปลอดภัย (security/headers.go)
SetSecureHeaders(:9) ตั้งX-Content-Type-Options: nosniff,X-Frame-Options: DENY,Referrer-Policy: no-referrer; ถูกเรียกจาก middlewareSecurityHeaders(middleware/middleware.go:17-22) ที่ router ใส่ทุกคำขอ (router.go:65) (Router, middleware และแผนผัง route ทั้งหมดของ backend (chi router)).- คอมเมนต์ในแพ็กเกจบอกว่าจะเพิ่ม XSS sanitize และตัวช่วย AES สำหรับ provider-config "ภายหลัง" — ในโค้ดปัจจุบันยังไม่มี (ไฟล์ 13 บรรทัด) ตรงกับที่รหัสผ่าน Provider ยังเป็น base64 (ADR-0002 รหัสผ่าน DB ของผู้เช่าเก็บเป็น base64 ไม่ได้เข้ารหัส).
ข้อควรระวัง
sqllit.Literalกับเงิน: เคสfloat64พิมพ์ด้วยstrconv.FormatFloat(v,'f',-1,64)ซึ่งเป็นทศนิยมฐานสอง — ขัดกฎ ACCOUNTING NUMBER ถ้าใช้กับยอดเงิน; ควรส่งเป็นสตริงทศนิยม (จะถูกใส่เป็น'125.50'แล้ว PostgreSQL แปลงเป็น NUMERIC ตอน INSERT). ชนิดอย่างint32/uint/decimal ตกเข้ากรณี default กลายเป็นสตริงเสมอ.Escapeซ้ำเฉพาะ'— ปลอดภัยเมื่อเซิร์ฟเวอร์ใช้standard_conforming_strings = on(ค่าเริ่มต้นของ PostgreSQL; ยังไม่ได้ตรวจการตั้งค่าของเซิร์ฟเวอร์จริง). ห้ามใช้Insertกับชื่อตาราง/คอลัมน์ที่มาจากผู้ใช้ (นโยบายล็อกการเขียนผ่าน Query Gateway (Query write policy / lockdown) ไม่คุมเส้นทางนี้เพราะเป็นผู้เรียกภายใน).ILIKE '%…%'แบบ leading wildcard ใช้ index ปกติไม่ได้ — รูปแบบนี้เหมาะกับตารางไม่ใหญ่ (ยังไม่ได้ตรวจ index ของตารางสินค้า).- รายการคำพ้องเป็นข้อมูลที่คนคัดเอง และบางรายการกว้าง (เช่นให้
น้ำจับซอส/เหลว) ทำให้ผลค้นหากว้างกว่าที่ผู้ใช้พิมพ์ — แก้ที่thaiB2BSynonymMap. docno.Stampลดโอกาสชนแต่ ไม่รับประกันความไม่ซ้ำ (มิลลิวินาที + 16 บิตสุ่ม) และไม่ต่อเนื่อง — ห้ามใช้เป็นเลขที่เอกสารที่กฎหมายกำหนดให้รันต่อเนื่อง.
ที่มาในโค้ด
backend/internal/platform/docno/docno.go:1-33—Stamp,stampAt.backend/internal/platform/sqllit/sqllit.go:1-55—Escape,Literal,Insert.backend/internal/platform/search/tokenizer.go:12-130— พยางค์, คำพ้อง, ข้อยกเว้น, alias.backend/internal/platform/search/tokenizer.go:164-345— ตัดคำไทยและ token.backend/internal/platform/search/tokenizer.go:343-509— SQL builder, จับคู่, คะแนน.backend/internal/platform/security/headers.go:1-13— header.backend/internal/adapter/inbound/http/middleware/middleware.go:17-22—SecurityHeaders.
ดูเพิ่ม
ภาพรวม Backend Go ของ SML Ai Account (Backend overview) · Query Gateway: /query/list, limit, batch, exec, execBatch · รูปแบบ error และ response ของ API (apperr + response helper) · Router, middleware และแผนผัง route ทั้งหมดของ backend (chi router) · สถาปัตยกรรม Hexagonal และ composition root ของ Go backend (Hexagonal Architecture + DI) · สารบัญ ADR ของ backend (Architecture Decision Records)