Layanan QR Code
Service di Wails adalah struct Go yang berisi logika bisnis yang ingin Anda sediakan ke frontend. Service menjaga kode tetap terorganisir dengan mengelompokkan fungsionalitas terkait.
Anggap service sebagai kumpulan metode yang dapat dipanggil oleh kode JavaScript Anda. Setiap metode publik pada service menjadi dapat dipanggil dari frontend setelah binding dibuat.
Dalam tutorial ini, kita akan membuat layanan generator QR code untuk mendemonstrasikan konsep-konsep ini. Di akhir tutorial, Anda akan memahami cara membuat service, mengelola dependensi, dan menghubungkan kode Go ke frontend.
-
Buat file QR Service
Section titled “Buat file QR Service”Buat file baru bernama
qrservice.godi direktori aplikasi Anda:qrservice.go package mainimport ("github.com/skip2/go-qrcode")// QRService handles QR code generationtype QRService struct {// We can add state here if needed}// NewQRService creates a new QR servicefunc NewQRService() *QRService {return &QRService{}}// Generate creates a QR code from the given textfunc (s *QRService) Generate(text string, size int) ([]byte, error) {// Generate the QR codeqr, err := qrcode.New(text, qrcode.Medium)if err != nil {return nil, err}// Convert to PNGpng, err := qr.PNG(size)if err != nil {return nil, err}return png, nil}Apa yang terjadi di sini:
QRServiceadalah struct kosong yang akan menampung metode pembuatan QR code kitaNewQRService()adalah fungsi konstruktor yang membuat instance baru dari service kitaGenerate()adalah metode yang menerima teks dan ukuran, lalu mengembalikan QR code sebagai array byte PNG- Metode mengembalikan
([]byte, error)mengikuti konvensi Go yang mengembalikan error sebagai nilai terakhir - Kita menggunakan paket
github.com/skip2/go-qrcodeuntuk menangani pembuatan QR code
-
Daftarkan Service
Section titled “Daftarkan Service”Membuat service saja tidak cukup — kita perlu mendaftarkannya ke aplikasi Wails agar aplikasi mengetahui service tersebut ada dan dapat membuat binding untuknya.
Pendaftaran dilakukan di
main.gosaat Anda membuat aplikasi. Anda meneruskan instance service ke opsiServices:main.go func main() {app := application.New(application.Options{Name: "myproject",Description: "A demo of using raw HTML & CSS",LogLevel: slog.LevelDebug,Services: []application.Service{application.NewService(NewQRService()),},Assets: application.AssetOptions{Handler: application.AssetFileServerFS(assets),},Mac: application.MacOptions{ApplicationShouldTerminateAfterLastWindowClosed: true,},})app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "myproject",Width: 600,Height: 400,})// Run the application. This blocks until the application has been exited.err := app.Run()// If an error occurred while running the application, log it and exit.if err != nil {log.Fatal(err)}}Apa yang terjadi di sini:
application.NewService()membungkus service Anda agar Wails dapat mengelolanya- Kita memanggil
NewQRService()untuk membuat instance service kita - Service ditambahkan ke slice
Servicesdalam opsi aplikasi - Wails akan memindai service ini untuk metode publik yang tersedia di frontend
-
Instal Dependensi
Section titled “Instal Dependensi”Kita mereferensikan paket
github.com/skip2/go-qrcodedi kode kita, tetapi belum mengunduhnya. Go perlu mengetahui dependensi ini dan mengunduhnya ke proyek Anda.Jalankan perintah ini di terminal dari direktori proyek Anda:
Terminal window go mod tidyApa yang terjadi di sini:
go mod tidymemindai file Go Anda untuk pernyataan import- Perintah ini mengunduh paket yang hilang (seperti
go-qrcode) dan menambahkannya kego.mod - Juga menghapus dependensi yang tidak lagi digunakan
- Ini memastikan proyek Anda memiliki semua kode yang diperlukan untuk kompilasi berhasil
Anda seharusnya melihat output yang menunjukkan paket QR code telah diunduh dan ditambahkan ke proyek.
-
Buat Binding
Section titled “Buat Binding”Untuk memanggil metode ini dari frontend, kita perlu membuat binding. Anda dapat melakukannya dengan menjalankan
wails generate bindingsdi direktori root proyek.Setelah dijalankan, Anda seharusnya melihat output serupa berikut di terminal:
Terminal window % wails3 generate bindingsINFO Processed: 337 Packages, 1 Service, 1 Method, 0 Enums, 0 Models in 740.196125ms.INFO Output directory: /Users/leaanthony/myproject/frontend/bindingsPerhatikan bahwa di direktori frontend, ada direktori baru bernama
bindings:Terminal window frontend/└── bindings└── changeme├── index.js└── qrservice.js
-
Memahami Binding
Section titled “Memahami Binding”Mari lihat binding yang dihasilkan di
bindings/changeme/qrservice.js:bindings/changeme/qrservice.js // @ts-check// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL// This file is automatically generated. DO NOT EDIT/*** QRService handles QR code generation* @module*/// eslint-disable-next-line @typescript-eslint/ban-ts-comment// @ts-ignore: Unused importsimport {Call as $Call, Create as $Create} from "@wailsio/runtime";/*** Generate creates a QR code from the given text* @param {string} text* @param {number} size* @returns {Promise<string> & { cancel(): void }}*/export function Generate(text, size) {let $resultPromise = /** @type {any} */($Call.ByID(3576998831, text, size));let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {return $Create.ByteSlice($result);}));$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);return $typingPromise;}Kita dapat melihat binding dibuat untuk metode
Generate. Nama parameter dipertahankan, begitu pula komentar. JSDoc juga dihasilkan untuk metode guna memberikan informasi tipe ke IDE Anda.Binding menyediakan:
- Fungsi yang setara dengan metode Go Anda
- Konversi otomatis antara tipe Go dan JavaScript
- Operasi async berbasis Promise
- Informasi tipe sebagai komentar JSDoc
Service yang dihasilkan diekspor ulang oleh file
index.js:bindings/changeme/index.js // @ts-check// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL// This file is automatically generated. DO NOT EDITimport * as QRService from "./qrservice.js";export {QRService};Anda kemudian dapat mengaksesnya melalui path import yang disederhanakan
./bindings/changemeyang hanya terdiri dari path paket Go Anda, tanpa perlu menentukan nama file.
-
Gunakan Binding di Frontend
Section titled “Gunakan Binding di Frontend”Sekarang kita dapat memanggil service Go dari JavaScript! Binding yang dihasilkan membuat ini mudah dan type-safe.
Perbarui
frontend/src/main.jsuntuk menggunakan binding baru:frontend/src/main.js import { QRService } from './bindings/changeme';async function generateQR() {const text = document.getElementById('text').value;if (!text) {alert('Please enter some text');return;}try {// Generate QR code as base64const qrCodeBase64 = await QRService.Generate(text, 256);// Display the QR codeconst qrDiv = document.getElementById('qrcode');qrDiv.src = `data:image/png;base64,${qrCodeBase64}`;} catch (err) {console.error('Failed to generate QR code:', err);alert('Failed to generate QR code: ' + err);}}export function initializeQRGenerator() {const button = document.getElementById('generateButton');button.addEventListener('click', generateQR);}Apa yang terjadi di sini:
- Kita mengimpor
QRServicedari binding yang dihasilkan QRService.Generate()memanggil metode Go kita — mengembalikan Promise, jadi kita gunakanawait- Metode Go mengembalikan
[]byte, yang Wails otomatis konversi ke string base64 untuk JavaScript - Kita membuat data URL dengan string base64 untuk menampilkan gambar PNG
- Blok
try/catchmenangani error dari sisi Go (seperti input tidak valid) - Jika kode Go kita mengembalikan error, Promise ditolak dan kita tangkap di sini
Sekarang perbarui
index.htmluntuk menggunakan binding baru di fungsiinitializeQRGenerator:frontend/src/index.html <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>QR Code Generator</title><style>body {font-family: Arial, sans-serif;display: flex;flex-direction: column;align-items: center;justify-content: center;height: 100vh;margin: 0;}#qrcode {margin-bottom: 20px;width: 256px;height: 256px;display: flex;align-items: center;justify-content: center;}#controls {display: flex;gap: 10px;}#text {padding: 5px;}#generateButton {padding: 5px 10px;cursor: pointer;}</style></head><body><img id="qrcode"/><div id="controls"><input type="text" id="text" placeholder="Enter text"><button id="generateButton">Generate QR Code</button></div><script type="module">import { initializeQRGenerator } from './main.js';document.addEventListener('DOMContentLoaded', initializeQRGenerator);</script></body></html>Jalankan
wails3 devuntuk memulai dev server. Setelah beberapa detik, aplikasi seharusnya terbuka.Ketik teks dan klik tombol “Generate QR Code”. Anda seharusnya melihat QR code di tengah halaman:

- Kita mengimpor
-
Pendekatan Alternatif: HTTP Handler
Section titled “Pendekatan Alternatif: HTTP Handler”Sejauh ini, kita telah membahas area berikut:
- Membuat Service baru
- Membuat Binding
- Menggunakan Binding di kode Frontend kita
Mengapa menggunakan HTTP handler?
Method binding bekerja dengan baik untuk operasi data, tetapi ada pendekatan alternatif untuk menyajikan file, gambar, atau media lainnya. Alih-alih mengonversi semuanya ke base64 dan mengirimkannya melalui binding, Anda dapat membuat service bertindak seperti mini web server.
Ini berguna ketika:
- Anda menyajikan gambar, video, atau file besar
- Anda ingin menggunakan tag HTML standar
<img>atau<video>dengan atributsrc - Anda memerlukan akses URL langsung ke resource
Jika service Anda mengimplementasikan metode standar Go
ServeHTTP(w http.ResponseWriter, r *http.Request), Wails dapat menjadikannya dapat diakses sebagai endpoint HTTP. Mari perluas layanan QR code kita untuk mendukung ini:qrservice.go package mainimport ("net/http""strconv""github.com/skip2/go-qrcode")// QRService handles QR code generationtype QRService struct {// We can add state here if needed}// NewQRService creates a new QR servicefunc NewQRService() *QRService {return &QRService{}}// Generate creates a QR code from the given textfunc (s *QRService) Generate(text string, size int) ([]byte, error) {// Generate the QR codeqr, err := qrcode.New(text, qrcode.Medium)if err != nil {return nil, err}// Convert to PNGpng, err := qr.PNG(size)if err != nil {return nil, err}return png, nil}func (s *QRService) ServeHTTP(w http.ResponseWriter, r *http.Request) {// Extract the text parameter from the requesttext := r.URL.Query().Get("text")if text == "" {http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)return}// Extract Size parameter from the requestsizeText := r.URL.Query().Get("size")if sizeText == "" {sizeText = "256"}size, err := strconv.Atoi(sizeText)if err != nil {http.Error(w, "Invalid 'size' parameter", http.StatusBadRequest)return}// Generate the QR codeqrCodeData, err := s.Generate(text, size)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// Write the QR code data to the responsew.Header().Set("Content-Type", "image/png")w.Write(qrCodeData)}Apa yang terjadi di sini:
ServeHTTPadalah interface standar Go untuk menangani permintaan HTTP- Kita mem-parsing parameter query dari URL (
?text=hello&size=256) - Kita memanggil metode
Generate()yang sudah ada untuk membuat QR code - Kita set content type ke
image/pngagar browser tahu ini gambar - Kita menulis byte PNG mentah langsung ke response — tidak perlu base64!
Sekarang perbarui
main.gountuk menentukan route tempat layanan QR code dapat diakses:main.go func main() {app := application.New(application.Options{Name: "myproject",Description: "A demo of using raw HTML & CSS",LogLevel: slog.LevelDebug,Services: []application.Service{application.NewService(NewQRService(), application.ServiceOptions{Route: "/qrservice",}),},Assets: application.AssetOptions{Handler: application.AssetFileServerFS(assets),},Mac: application.MacOptions{ApplicationShouldTerminateAfterLastWindowClosed: true,},})app.Window.NewWithOptions(application.WebviewWindowOptions{Title: "myproject",Width: 600,Height: 400,})// Run the application. This blocks until the application has been exited.err := app.Run()// If an error occurred while running the application, log it and exit.if err != nil {log.Fatal(err)}}Apa yang terjadi di sini:
- Kita menambahkan
application.ServiceOptionsuntuk mengonfigurasi cara service diekspos Route: "/qrservice"membuat HTTP handler dapat diakses di/qrservice- Sekarang permintaan ke
/qrservice?text=helloakan memanggil metodeServeHTTPkita - Tanpa mengatur
Route, fungsionalitas HTTP handler dinonaktifkan
Terakhir, perbarui
main.jsuntuk menggunakansrcgambar sederhana alih-alih encoding base64:frontend/src/main.js async function generateQR() {const text = document.getElementById('text').value;if (!text) {alert('Please enter some text');return;}const img = document.getElementById('qrcode');// Make the image source the path to the QR code service, passing the textimg.src = `/qrservice?text=${encodeURIComponent(text)}`}export function initializeQRGenerator() {const button = document.getElementById('generateButton');if (button) {button.addEventListener('click', generateQR);} else {console.error('Generate button not found');}}Apa yang terjadi di sini:
- Kita menghapus import dan pemanggilan
await QRService.Generate() - Sebagai gantinya, kita cukup set
img.srcke endpoint HTTP kita encodeURIComponent()meng-escape karakter khusus dengan aman di URL- Browser otomatis membuat permintaan HTTP GET saat kita set
src - Ini lebih sederhana dan efisien untuk gambar — tidak perlu konversi base64!
Menjalankan aplikasi lagi seharusnya menghasilkan QR code yang sama:

-
Mendukung Konfigurasi Dinamis
Section titled “Mendukung Konfigurasi Dinamis”Masalah dengan route hardcoded:
Pada contoh di atas kita menggunakan route hardcoded
/qrservicedi kode JavaScript. Ini menciptakan coupling ketat antara konfigurasi Go dan kode frontend Anda.Jika Anda mengedit
main.godan mengubah opsiRoutetanpa memperbaruimain.js, aplikasi akan rusak:main.go // ...application.NewService(NewQRService(), application.ServiceOptions{Route: "/services/qr",}),// ...Route hardcoded bekerja untuk aplikasi sederhana, tetapi membuat kode rapuh dan sulit dirawat.
Solusinya: Konfigurasi dinamis
Method binding dan HTTP handler dapat bekerja bersama! Kita dapat menggunakan binding untuk memberi tahu frontend route mana yang digunakan, sehingga konfigurasi menjadi dinamis dan menghilangkan path hardcoded.
Cara kerjanya:
- Metode lifecycle
ServiceStartupberjalan saat aplikasi dimulai - Kita menyimpan route yang dikonfigurasi dari opsi
- Kita menambahkan metode
URL()yang dapat dipanggil frontend untuk mendapatkan route yang benar - Sekarang frontend meminta route ke service Go alih-alih menebak
Pertama, implementasikan interface
ServiceStartupdan tambahkan metodeURLbaru:qrservice.go package mainimport ("context""net/http""net/url""strconv""github.com/skip2/go-qrcode""github.com/wailsapp/wails/v3/pkg/application")// QRService handles QR code generationtype QRService struct {route string}// NewQRService creates a new QR servicefunc NewQRService() *QRService {return &QRService{}}// ServiceStartup runs at application startup.func (s *QRService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {s.route = options.Routereturn nil}// Generate creates a QR code from the given textfunc (s *QRService) Generate(text string, size int) ([]byte, error) {// Generate the QR codeqr, err := qrcode.New(text, qrcode.Medium)if err != nil {return nil, err}// Convert to PNGpng, err := qr.PNG(size)if err != nil {return nil, err}return png, nil}// URL returns an URL that may be used to fetch// a QR code with the given text and size.// It returns an error if the HTTP handler is not available.func (s *QRService) URL(text string, size int) (string, error) {if s.route == "" {return "", errors.New("http handler unavailable")}return fmt.Sprintf("%s?text=%s&size=%d", s.route, url.QueryEscape(text), size), nil}func (s *QRService) ServeHTTP(w http.ResponseWriter, r *http.Request) {// Extract the text parameter from the requesttext := r.URL.Query().Get("text")if text == "" {http.Error(w, "Missing 'text' parameter", http.StatusBadRequest)return}// Extract Size parameter from the requestsizeText := r.URL.Query().Get("size")if sizeText == "" {sizeText = "256"}size, err := strconv.Atoi(sizeText)if err != nil {http.Error(w, "Invalid 'size' parameter", http.StatusBadRequest)return}// Generate the QR codeqrCodeData, err := s.Generate(text, size)if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}// Write the QR code data to the responsew.Header().Set("Content-Type", "image/png")w.Write(qrCodeData)}Apa yang terjadi di sini:
- Kita menambahkan field
routeuntuk menyimpan route yang dikonfigurasi dariServiceStartup ServiceStartup(ctx, options)dipanggil saat aplikasi dimulai — kita simpan route di sini- Metode
URL()membangun URL lengkap dengan parameter query - Jika tidak ada route yang dikonfigurasi (route kosong), kita kembalikan error
url.QueryEscape()meng-encode teks dengan aman untuk digunakan di URL- Metode ini akan tersedia di frontend melalui binding
Sekarang perbarui
main.jsuntuk menggunakan metodeURLalih-alih path hardcoded:frontend/src/main.js import { QRService } from "./bindings/changeme";async function generateQR() {const text = document.getElementById('text').value;if (!text) {alert('Please enter some text');return;}const img = document.getElementById('qrcode');// Invoke the URL method to obtain an URL for the given text.img.src = await QRService.URL(text, 256);}export function initializeQRGenerator() {const button = document.getElementById('generateButton');if (button) {button.addEventListener('click', generateQR);} else {console.error('Generate button not found');}}Apa yang terjadi di sini:
- Kita mengimpor
QRServiceuntuk menggunakan binding lagi - Alih-alih hardcode
/qrservice, kita panggilawait QRService.URL(text, 256) - Service Go membangun URL dengan route dan parameter yang benar
- Sekarang jika Anda mengubah route di
main.go, frontend otomatis menggunakan route baru - Tidak perlu sinkronisasi manual antara konfigurasi Go dan kode frontend!
Seharusnya bekerja seperti contoh sebelumnya, tetapi mengubah route service di
main.gotidak akan merusak frontend lagi.
- Metode lifecycle