# report-server-ts เป็น Microservice(Web API) ออกแบบมาเพื่อสร้างเอกสารจาก templste หรือแปลงไฟล์ สามารถใช้ซ้ำได้หลายโปรเจ็ก ## Feature & Change - ใช้ไฟล์ docx/xlsx เป็น template คล้าย Mail Merge ยูสเซอร์ออกแบบเองได้ รวมกับ ข้อมูลใน JSON เพื่อออกเป็น docx/xlsx - JSON + template จะได้ไฟล์ output ฟอร์แม็ตจะเป็นไฟล์ของ template(docx/xlsx) สามารถแปลเป็นฟอร์แม็ต png, pdf, jpeg ฯลฯ ได้ด้วยความสามารถของ Libreoffice(soffice) - ใช้ url ของหน้าเวปแปลงเป็นไฟล์รองรับ pdf, png, jpeg เพื่อออกรายงานจากหน้าเวปได้ รองรับ Query Selector - แปลงไฟล์เป็น ภาพหรือ PDF รองรับการตัดคำไทย (ตัวออกรายงานตัวอื่นๆมักจะมีปัญหาการตัดคำ) - โค้ดมีการ obfuscator เพื่อลดขนาดและกันลูกค้าเอาโค้ดไปใช้ - มี Swagger ไว้ทดสอบ API มี libs/swagger-specs.json เพื่อนำเข้า Postman หรือเครื่องมื่อื่นๆได้ - มีโปรแกรมช่วยทดสอบ template แบบง่ายๆ ให้ทดสอบก่อนเอาเข้าเซิร์ฟเวอร์ - build เป็น Docker Image เพื่อใช้ใน Microservice ในโปรเจ็กอื่นๆ ใช้ prefix /api/v1/report-template/* ลดขนาดแล้วด้วย node:22-bookworm-slim ## development พัฒนาและทดสอบบน node 22 ,Linux AMD x86-64 ให้ clone project ติดตั้ง Fonts และ LibreOffice ตามวิธีใน [Dockerfile](./Dockerfile) ```bash # clone project npm install # add type support for yaqrcode cd node_modules/yaqrcode wget https://raw.githubusercontent.com/zenozeng/node-yaqrcode/master/index.d.ts ``` ## การใช้งาน ดู scripts ใน package.json และ compose.yaml ให้ดูให้เข้าใจ การสร้าง docker อิมเมจ จะต้อง swaggergen,obfuscator ก่อน หลังจากนั้นขึ้น Docker registry บน production ให้ดู compose.yaml จะต้องนำ template ไปเก็บไว้ที่โฟลเดอร์ templates ด้วย ```bash npm run dev npm run build npm run serve npm run obfuscator npm run preview npm run build:docker docker compose up -d npm run push:docker ``` ## ทดสอบ template/unit test ไปที่โฟลเดอร์ test-run มีโปรแกรมเพื่อทดสอบ template ให้ทดสอบที่นี้ก่อนเอา template ไปวางในเซิร์ฟเวอร์ มีค่า default สำหรับการทดสอบที่ใช้ได้เลย สามารถแปลงไปไฟล์แบบต่างๆที่ Libreoffice รองรับ(จำเป็นต้องติดตั้ง ) ควรทดสอบรูปแบบข้อมูล(json) ให้เข้ากับ template(docx,xlsx) ถ้าเกิดปัญหา ถ้าค่าไม่ครบ template แบบ docx จะ error log ที่เซิร์ฟเวอร์ ส่วน xlsx ไม่แจ้งปัญหา แค่ไม่แสดงค่านั้นๆ คู่มือการใช้งานที่สมบูรณ์ให้ไปที่เวปของ library ที่ใช้ [docx-templates](https://www.npmjs.com/package/docx-templates) และ [xlsx-template-next](https://www.npmjs.com/package/xlsx-template-next) ``` bash cd test-run $ npx ts-node docx-template.ts Output extension(docx,pdf,odt): pdf JSON data path(./docx.json): Base path of templates-docx(..): $ npx ts-node xlsx-template.ts Output extension(xlsx,pdf): ods JSON data path(./xlsx.json): Base path of templates-docx(..): $ npx ts-node html-template.ts ``` ## API หลัง npm run dev ไปดูที่ [http://localhost:3001/swagger](http://localhost:3001/swagger) หรือใช้ Rest Client ดูไฟล์ [api.http](./api.http) ตรง http header จะใช้ accept เป็นตัวบอกว่าต้องการผลเป็นไฟล์แบบไหนโดยใช้ [Mime type](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types) เพื่อเป็นมาตรฐาน ให้ดูที่รองรับในฟังก์ชั่น [mimeToExtension](./libs/report-template.ts) การตั้งค่าของ template อยู่ที่ [templateOption](./libs/report-template.ts) ### HTML แปลงจาก URL เป็น pdf,png,jpg รองรับการตัดคำไทย การแปลงเป็น - [templateOption.htmlTemplateOption](./libs/report-template.ts) เป็นการตั้งค่าเฉพาะของโมดุลนี้ - templateOption.htmlTemplateOption.PDFOptions ซึ่งเป็น [PDFOptions](https://pptr.dev/api/puppeteer.pdfoptions) ของ puppeteer ฟีเจอร์ template ยังไม่เสร็จ ``` sh # Grafana dashboard to pdf curl -X 'POST' \ 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: application/pdf' -H 'Content-Type: application/json' \ -d '{ "template": "https://bma-dashboard.frappet.synology.me/d/ANtkJay4z/4Lic4Li54LmJ4Lie4Li04LiB4Liy4Lij?orgId=1&kiosk", "reportName": "html-grafana", "htmlOption": { "querySelector": ".scrollbar-view" } }' -o html-grafana.pdf # url to png curl -X 'POST' 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: image/png' -H 'Content-Type: application/json' \ -d '{"template": "https://www.blognone.com/","reportName": "html-blognone"}' -o html-blognone.png # url to jpeg curl -X 'POST' 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: image/jpeg' -H 'Content-Type: application/json' \ -d '{"template": "https://pantip.com/","reportName": "html-blognone"}' -o html-pantip.jpeg # url to pdf pantip.com very long lazyload you curl -X 'POST' 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: application/pdf' -H 'Content-Type: application/json' \ -d '{"template": "https://pantip.com/","reportName": "html-blognone", "htmlOption": { "preloadWait":500, "preloadScroll":1000, "preloadLoop":25, "pdfOption":{"format":"A4"} } }' -o html-pantip.pdf ``` # pantip.com to pdf curl -X 'POST' \ 'http://localhost:3001/api/v1/report-template/html' \ -H 'accept: application/pdf' -H 'Content-Type: application/json' \ -d '{ "template": "https://pantip.com", "reportName": "html-pantip.com" }' -o html-pantip.com.pdf ### docx แปลงจากเทมเพลทไฟล์ .docx เป็น docx,pdf,png ```sh curl -X 'POST' \ 'https://report-server.frappet.synology.me/api/v1/report-template/docx?folder=command' \ -H 'accept: application/vnd.openxmlformats-officedocument.wordprocessingml.document' \ -H 'Content-Type: application/json' \ -d '{ "template": "C-PM-01_cover", "reportName": "command-C-PM-01_cover", "data": { "issue": "............", "title": "......", "commandNo": "......", "commandYear": "......", "commandTitle": "คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้", "detailHeader": "", "detailBody": "อาศัยอำนาจตามความในมาตรา ๔๔ มาตรา ๕๒ (๔) แห่งพระราชบัญญัติระเบียบข้าราชการกรุงเทพมหานครและบุคลากรกรุงเทพมหานคร พ.ศ.๒๕๕๔ ประกอบกับกฎ ก.ก. ว่าด้วยการทดลองปฏิบัติหน้าที่ราชการและการพัฒนาข้าราชการกรุงเทพมหานครสามัญที่อยู่ระหว่างทดลองปฏิบัติหน้าที่ราชการ พ.ศ. ๒๕๕๕ มติคณะกรรมการข้าราชการกรุงเทพมหานครและบุคลากรกรุงเทพมหานคร ครั้งที่ ๑/๒๕๕๔ เมื่อวันที่ ๒๒ ธันวาคม ๒๕๕๔ มติ อ.ก.ก. วิสามัญเกี่ยวกับระบบราชการ การจัดส่วนราชการและค่าตอบแทน ครั้งที่ ๙/๒๕๕๖ เมื่อ ๑๘ กันยายน ๒๕๕๖ ประกาศสำนักงาน ก.ก. ลงวันที่ ………………………………….. เรื่อง รับสมัครสอบแข่งขันเพื่อบรรจุและแต่งตั้งบุคคลเข้ารับราชการเป็นข้าราชการการกรุงเทพมหานครสามัญ ครั้งที่ ………………………………….. และประกาศสำนักงาน ก.ก. ลงวันที่ ………………………………….. เรื่อง ผลการสอบแข่งขันเพื่อบรรจุและแต่งตั้งบุคคลเข้ารับราชการเป็นข้าราชการกรุงเทพมหานครสามัญ ครั้งที่ ………………………………….. ตำแหน่ง………………………………….. ให้บรรจุผู้สอบแข่งขันได้เข้ารับราชการเป็นข้าราชการกรุงเทพมหานครสามัญ และแต่งตั้งให้ดำรงตำแหน่ง………………………………….. จำนวน ………………………………….. ราย โดยให้ทดลองปฏิบัติหน้าที่ราชการในตำแหน่งที่ได้รับแต่งตั้งดังบัญชีรายละเอียดแนบท้ายคำสั่งนี้", "detailFooter": "", "commandDate": "..................", "commandAffectDate": "..................", "commandExcecuteDate": "..................", "name": "....................................", "position": "ผู้อำนวยการสำนัก/เขต", "authorizedUserFullName": "............", "authorizedPosition": "..................." } }' -o docx-command-C-PM-01_cover.docx curl -X 'POST' \ 'https://report-server.frappet.synology.me/api/v1/report-template/docx?folder=command' \ -H 'accept: application/pdf' \ -H 'Content-Type: application/json' \ -d '{ "template": "C-PM-01_cover", "reportName": "command-C-PM-01_cover", "data": { "issue": "............", "title": "......", "commandNo": "......", "commandYear": "......", "commandTitle": "คำสั่งบรรจุและแต่งตั้งผู้สอบแข่งขันได้", "detailHeader": "", "detailBody": "อาศัยอำนาจตามความในมาตรา ๔๔ มาตรา ๕๒ (๔) แห่งพระราชบัญญัติระเบียบข้าราชการกรุงเทพมหานครและบุคลากรกรุงเทพมหานคร พ.ศ.๒๕๕๔ ประกอบกับกฎ ก.ก. ว่าด้วยการทดลองปฏิบัติหน้าที่ราชการและการพัฒนาข้าราชการกรุงเทพมหานครสามัญที่อยู่ระหว่างทดลองปฏิบัติหน้าที่ราชการ พ.ศ. ๒๕๕๕ มติคณะกรรมการข้าราชการกรุงเทพมหานครและบุคลากรกรุงเทพมหานคร ครั้งที่ ๑/๒๕๕๔ เมื่อวันที่ ๒๒ ธันวาคม ๒๕๕๔ มติ อ.ก.ก. วิสามัญเกี่ยวกับระบบราชการ การจัดส่วนราชการและค่าตอบแทน ครั้งที่ ๙/๒๕๕๖ เมื่อ ๑๘ กันยายน ๒๕๕๖ ประกาศสำนักงาน ก.ก. ลงวันที่ ………………………………….. เรื่อง รับสมัครสอบแข่งขันเพื่อบรรจุและแต่งตั้งบุคคลเข้ารับราชการเป็นข้าราชการการกรุงเทพมหานครสามัญ ครั้งที่ ………………………………….. และประกาศสำนักงาน ก.ก. ลงวันที่ ………………………………….. เรื่อง ผลการสอบแข่งขันเพื่อบรรจุและแต่งตั้งบุคคลเข้ารับราชการเป็นข้าราชการกรุงเทพมหานครสามัญ ครั้งที่ ………………………………….. ตำแหน่ง………………………………….. ให้บรรจุผู้สอบแข่งขันได้เข้ารับราชการเป็นข้าราชการกรุงเทพมหานครสามัญ และแต่งตั้งให้ดำรงตำแหน่ง………………………………….. จำนวน ………………………………….. ราย โดยให้ทดลองปฏิบัติหน้าที่ราชการในตำแหน่งที่ได้รับแต่งตั้งดังบัญชีรายละเอียดแนบท้ายคำสั่งนี้", "detailFooter": "", "commandDate": "..................", "commandAffectDate": "..................", "commandExcecuteDate": "..................", "name": "....................................", "position": "ผู้อำนวยการสำนัก/เขต", "authorizedUserFullName": "............", "authorizedPosition": "..................." } }' -o docx-command-C-PM-01_cover.pdf curl -X 'POST' \ -H 'accept: application/pdf' \ -H 'Content-Type: application/json' \ 'http://localhost:3001/api/v1/report-template/docx' \ -d '{ "template": "hello", "reportName": "docx-report", "data": { "docNo": "๑๒๓๔๕", "me": "กระผม", "prefix": "นาย", "name": "สรวิชญ์", "surname": "พลสิทธิ์", "position": "Chief Technology Officer", "org": { "type": "บริษัท", "name": "เฟรปเป้ที", "url": "https://frappet.com" }, "employees": [ { "name": "ภาวิชญ์", "surname": "พลสิทธิ์" }, { "name": "วิชญาภา", "surname": "พลสิทธิ์" } ] } }' -o docx-report.pdf ``` ### xlsx แปลงจากเทมเพลทไฟล์ .xlsx เป็น xlsx,pdf,png ``` curl -X 'POST' \ 'http://localhost:3001/api/v1/report-template/xlsx' \ -H 'accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' \ -H 'Content-Type: application/json' \ -d '{ "template": "hello", "reportName": "xlsx-report", "data": { "docNo": "๑๒๓๔๕", "me": "กระผม", "prefix": "นาย", "name": "สรวิชญ์", "surname": "พลสิทธิ์", "position": "Chief Technology Officer", "org": { "type": "บริษัท", "name": "เฟรปเป้ที", "url": "https://frappet.com" }, "employees": [ { "id": 1, "name": "ภาวิชญ์", "surname": "พลสิทธิ์", "score": 80 }, { "id": 2, "name": "วิชญาภา", "surname": "พลสิทธิ์", "score": 50 }, { "id": 3, "name": "ฐิตาภา", "surname": "พลสิทธิ์", "score": 90 }, { "id": 4, "name": "สรวิชญ์ พลสิทธิ์", "surname": "พลสิทธิ์", "score": 99 } ] } }' -o xlsx-report.xlsx ``` # Known Issue - soffice แปลงเป็น png หรือ jpeg ได้แค่ 96dpi มันฮาร์ดโค้ดอยู่ สามารถแก้โค้ด compile ใหม่(ยากไปหน่อย) work around(ยังไม่ได้ทำ) ภาพความละเอียดต่ำให้แปลงเป็น pdf แล้ว[แปลงเป็นภาพ](https://ask.libreoffice.org/t/change-default-resolution-in-batch-png-conversion/18464/2) ใช้ Imagemagick ในการแปลงข้อดีคือรองรับฟอร์แม็ตได้หลายแบบ แต่มัน [disable PDF เป็น default](https://stackoverflow.com/questions/52998331/imagemagick-security-policy-pdf-blocking-conversion) ต้องแก้คอนฟิกก่อน ค่อนช้างกิน CPU ถ้ามีการแปลงหลายต่อ วิธีการนี้อาจจะทำเป็น API ในอนาคต คำสั่งด้านล่างลองแล้วใช้ได้ ``` convert -density 300 -background white -alpha remove report-docx.pdf report-docx2.png convert -density 300 -background white -alpha remove report-docx.pdf report-docx2.jpeg ``` - url แปลงเป็น pdf หรือภาพ จะเป็นหน้าเดียวเลย ยังไม่สามารถกำหนดขนาดทำเป็นหลายๆหน้าได้ อาจจะเป็นไปได้ยังหาวิธีอยู่เลยทำหน้าเดียวไปก่อน ยังมีปัญหากับการโหลดที่เป็น lazy load ยังไม่รองรับ Authentication header ## Note - ยังไม่สามารถใช้ bun runtime มีปัญหากับ libreoffice-file-converter และ docker-template เลยไม่ได้เอามาใช้ รุ่นใหม่อาจจะแก้ปัญหานี้แล้วต้องลองอีกที - Playwright ยังตรวจสอบความสูงของหน้าไม่ถูกต้องเลยใช้ puppeteer แทน - น่าจะทำให้รองรับ Authentication Header เพื่อให้ยูสเซอร์ในระบบใช้งานได้เท่านั้น - หาทางสร้างเอกสารจาก text เช่น Markdown เป็นเอกสาร MS Office - น่าจะทำ license key เผื่อขายให้ลูกค้าติดตั้งใช้งานต่อ (โค้ดที่ผ่าน obfuscator แล้วย้อนกลับมาได้ง่ายหรือเปล่า ?) - อาจจะลอง inkscape รองรับ command line อาจจะเอามาทำอะไรได้ "inkscape -z -e out.png -w 1024 in.svg" - การแปลงภาพสามารใช้ image magick ได้ยังไม่ได้ลอง - ถ้าใช้อิมเมจแบบ Distroless น่าจะเล็กลงแต่ตอนนี้ยังไม่มีรายการ library ที่จำเป็นทั้งหมดอีกทั้งอาจจะต้องใช้ shell เพื่อรัน soffice ใช้แบบ slim น่าจะเหมาะกับการนี้ - docker image น่าจะทำให้ติดตั้ง font เพิ่มได้ - ควรจำกัด domain ที่เรียกใช้ได้