Rumah >pembangunan bahagian belakang >Golang >Apl desktop pengurus kata laluan minimalis: pencerobohan ke dalam rangka kerja Golang's Wails (Bahagian 1)
Ini ialah soalan yang ditanya oleh semua pembangun kepada diri mereka sendiri, terutamanya jika mereka berasal dari dunia webdev: "Jika saya boleh menjalankan hampir apa sahaja yang akan dipaparkan dalam penyemak imbas dan memenuhi hampir semua tujuan yang saya mahu, siapa yang perlu memuat turun aplikasi kami dan menjalankannya pada komputer mereka?". Tetapi selain daripada keperluan jelas kerja yang kami lakukan (untuk diri kami sendiri atau untuk syarikat, contohnya dapat menggunakan semua ciri OS, prestasi yang lebih baik, keupayaan luar talian, keselamatan dan integrasi yang lebih baik, dll.), terdapat pengalaman yang kami sebagai pembangun perolehi daripada menyentuh aspek pengaturcaraan baharu yang akan sentiasa memperkayakan kami.
Jika anda berminat dengan Golang, seperti saya, dan anda telah membangunkan bahagian belakang dalam bahasa ini, tetapi anda juga telah melakukan bahagian hadapan dengan HTML, CSS dan JavaScript (atau beberapa rangka kerjanya) siaran ini adalah untuk anda, kerana tanpa perlu untuk mempelajari teknologi baharu anda lebih mampu mencipta aplikasi desktop.
Kemungkinan anda sudah tahu Elektron atau Tauri. Kedua-duanya menggunakan teknologi web untuk bahagian hadapan; yang pertama menggunakan JavaScript (atau lebih tepatnya, NodeJs) di bahagian belakangnya, dan yang kedua menggunakan Rust. Tetapi kedua-duanya mempunyai kelemahan yang lebih kurang ketara. Apl elektron mempunyai perduaan yang sangat besar (kerana ia membungkus keseluruhan penyemak imbas Chromium) dan menggunakan banyak memori. Apl Tauri menambah baik aspek ini (antara lain, kerana ia menggunakan WebView2 [Windows]/WebKit [macOS & Linux] bukannya Chromium), tetapi binari masih agak besar dan masa penyusunannya adalah… adakah Rust ? (apatah lagi keluk pembelajaran mereka, walaupun saya suka Rust, saya benar-benar bersungguh-sungguh?).
Dengan menggunakan Wails, anda mendapat yang terbaik daripada semua dunia pembangunan aplikasi desktop ini dengan teknologi web yang baru saya terangkan, serta semua kelebihan yang datang dengan menggunakan Go:
Ya, jika saya mahu menggunakan Go untuk mencipta aplikasi desktop terdapat kemungkinan lain (asli atau tidak). Saya akan menyebut Fyne dan go-gtk. Fyne ialah rangka kerja GUI yang membolehkan penciptaan apl asli dengan mudah dan walaupun ia mungkin mempunyai reka bentuk yang elegan, keupayaan rangka kerja itu agak terhad atau memerlukan usaha yang hebat daripada pembangun untuk mencapai perkara yang sama yang alat dan/atau bahasa lain akan membolehkan anda melakukannya dengan mudah. Saya boleh mengatakan perkara yang sama tentang go-gtk, yang merupakan pengikatan Go untuk GTK: ya, memang benar anda akan mendapat aplikasi asli yang hadnya bergantung pada keupayaan anda sendiri, tetapi masuk ke perpustakaan GTK adalah seperti melakukan ekspedisi melalui hutan ?…
Pertama sekali, bagi mereka yang tertanya-tanya apakah maksud Nu-i uita: dalam bahasa Romania ia secara kasar bermaksud "jangan lupakan mereka". Saya fikir ia adalah nama asal...
Anda boleh melihat keseluruhan kod aplikasi dalam repositori GitHub ini. Jika anda ingin mencubanya dengan segera, anda boleh memuat turun boleh laku dari sini (untuk Windows & Linux).
Saya akan menerangkan secara ringkas cara aplikasi berfungsi: pengguna log masuk buat kali pertama dan tetingkap log masuk memintanya memasukkan kata laluan induk. Ini disimpan disulitkan menggunakan kata laluan itu sendiri sebagai kunci penyulitan. Tetingkap log masuk ini membawa kepada antara muka lain di mana pengguna boleh menyenaraikan kata laluan yang disimpan untuk tapak web dan nama pengguna yang sepadan yang digunakan (anda juga boleh mencari dalam senarai ini mengikut nama pengguna atau tapak web). Anda boleh mengklik pada setiap item dalam senarai dan melihat butirannya, menyalinnya ke papan keratan, mengeditnya atau memadamkannya. Selain itu, apabila menambah item baharu ia akan menyulitkan kata laluan anda menggunakan kata laluan induk sebagai kunci. Dalam tetingkap konfigurasi anda boleh memilih bahasa pilihan anda (pada masa ini hanya bahasa Inggeris dan Sepanyol), padam semua data yang disimpan, eksport atau importnya daripada fail sandaran. Apabila mengimport data, pengguna akan diminta untuk kata laluan induk yang digunakan semasa eksport dilakukan, dan data yang diimport kini akan disimpan dan disulitkan dengan kata laluan induk semasa. Selepas itu, setiap kali pengguna log masuk semula ke dalam aplikasi, dia akan digesa untuk memasukkan kata laluan induk semasa.
Saya tidak akan memikirkan keperluan yang anda perlukan untuk menggunakan Wails kerana ia dijelaskan dengan baik dalam dokumentasinya yang sangat baik. Walau apa pun, adalah penting untuk anda memasang CLI yang berkuasa (pergi install github.com/wailsapp/wails/v2/cmd/wails@latest), yang membolehkan anda menjana perancah untuk aplikasi, muat semula panas semasa mengedit kod , dan bina boleh laku (termasuk kompilasi silang).
Wails CLI membolehkan anda menjana projek dengan pelbagai rangka kerja bahagian hadapan, tetapi atas sebab tertentu pencipta Wails nampaknya lebih suka Svelte... kerana ia adalah pilihan pertama yang mereka sebutkan. Apabila anda menggunakan perintah waiils init -n myproject -t svelte-ts, anda menjana projek dengan Svelte3 dan TypeScript.
Jika atas sebab tertentu anda lebih suka menggunakan Svelte5 dengan ciri runes baharunya, saya telah mencipta skrip bash yang mengautomasikan penjanaan projek dengan Svelte5. Dalam kes ini, anda juga perlu memasang Wails CLI.
Ciri-ciri aplikasi yang saya nyatakan di atas membentuk keperluan mana-mana todoapp (yang sentiasa merupakan cara yang baik untuk mempelajari sesuatu yang baharu dalam pengaturcaraan), tetapi di sini kami menambah tambah ciri (mis. kedua-duanya di bahagian belakang, penggunaan penyulitan simetri, dan di bahagian hadapan, penggunaan Pengantarabangsaan) yang menjadikannya lebih berguna dan memberi pengajaran daripada aplikasi todo yang mudah.
Ok, cukup pengenalan, jadi mari kita mula berniaga ?.
Jika anda memilih untuk mencipta projek Wails dengan Svelte Typescript dengan CLI dengan menjalankan perintah wails init -n myproject -t svelte-ts (atau dengan skrip bash yang saya buat dan yang telah saya beritahu anda sebelum ini, yang menjana Raungan projek dengan Svelte5) anda akan mempunyai struktur direktori yang hampir sama dengan yang ini:
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
Apa yang baru anda lihat ialah struktur aplikasi yang telah siap. Satu-satunya perbezaan dengan yang dihasilkan oleh Wails CLI ialah dengannya anda akan mendapat perancah aplikasi Wails dengan bahagian hadapan TypeScript Svelte3, dan dengan skrip saya, selain mempunyai Svelte5, Tailwindcss Daisyui ialah bersepadu.
Tetapi mari kita lihat bagaimana aplikasi Wails berfungsi secara umum dan pada masa yang sama mengkhususkan penjelasan untuk kes kami:
Seperti yang dinyatakan oleh dokumentasi Wails: "Aplikasi Wails ialah aplikasi Go standard, dengan kit web bahagian hadapan. Bahagian Go dalam aplikasi terdiri daripada kod aplikasi dan pustaka masa jalan yang menyediakan beberapa operasi berguna, seperti mengawal tetingkap aplikasi Bahagian hadapan ialah tetingkap kit web yang akan memaparkan bahagian hadapan aset". Ringkasnya, dan seperti yang kita mungkin sudah tahu jika kita telah mencipta aplikasi desktop dengan teknologi web, diterangkan secara ringkas, aplikasi itu terdiri daripada bahagian belakang (dalam kes kami ditulis dalam Go) dan bahagian hadapan yang asetnya diuruskan oleh tetingkap Webkit (dalam kes OS Windows, Webview2), sesuatu seperti intipati pelayan web/penyemak imbas yang menyediakan/memaparkan aset bahagian hadapan.
Aplikasi utama dalam kes khusus kami di mana kami mahu aplikasi itu dapat dijalankan pada kedua-dua Windows dan Linux terdiri daripada kod berikut:
/* main.go */ package main import ( "embed" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" ) //go:embed all:frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ // Title: "Nu-i uita • minimalist password manager", Width: 450, Height: 300, DisableResize: true, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, OnBeforeClose: app.beforeClose, Bind: []interface{}{ app, }, // Linux platform specific options Linux: &linux.Options{ Icon: icon, // WindowIsTranslucent: true, WebviewGpuPolicy: linux.WebviewGpuPolicyNever, // ProgramName: "wails", }, }) if err != nil { println("Error:", err.Error()) } }
Perkara pertama yang perlu kami lakukan ialah membuat instantiat struct (dengan fungsi NewApp), yang kami bersetuju untuk memanggil App, yang mesti mempunyai medan dengan Go Konteks. Kemudian, kaedah Run wails ialah kaedah yang memulakan aplikasi. Kita perlu lulus satu siri pilihan. Salah satu pilihan wajib ini ialah aset. Sebaik sahaja Wails menyusun bahagian hadapan, ia menjananya dalam folder "frontend/dist". Menggunakan arahan //go:embed all:frontend/dist (ciri ajaib Go) kita boleh membenamkan keseluruhan frontend kita dalam executable akhir. Dalam kes Linux, jika kita ingin membenamkan ikon aplikasi, kita juga mesti menggunakan arahan //go:embed.
Saya tidak akan pergi ke selebihnya pilihan, yang boleh anda semak dalam dokumentasi. Saya hanya akan mengatakan dua perkara yang berkaitan dengan pilihan. Yang pertama ialah tajuk yang muncul dalam bar tajuk aplikasi boleh ditetapkan di sini sebagai pilihan, tetapi dalam aplikasi kami, di mana pengguna boleh memilih bahasa yang mereka mahu, kami akan menetapkannya (menggunakan waktu jalan Raungan) apabila kami menerima peristiwa perubahan bahasa yang mungkin dilakukan oleh pengguna. Kita lihat nanti.
Isu berkaitan pilihan kedua yang penting ialah pilihan Ikat. Dokumentasi menerangkan maksudnya dengan sangat baik: "Pilihan Bind ialah salah satu pilihan yang paling penting dalam aplikasi Wails. Ia menentukan kaedah struct yang akan didedahkan kepada bahagian hadapan. Fikirkan struct seperti pengawal dalam web tradisional permohonan." Sesungguhnya: kaedah awam struktur Apl, iaitu kaedah yang mendedahkan bahagian belakang kepada bahagian hadapan, melaksanakan keajaiban "menyambung" Go dengan JavaScript. Kaedah awam struct tersebut ditukar kepada fungsi JavaScript yang mengembalikan janji melalui kompilasi yang dilakukan oleh Wails.
Bentuk komunikasi penting lain antara hujung belakang dan hujung hadapan (yang kami gunakan dengan berkesan dalam aplikasi ini) ialah acara. Wails menyediakan sistem acara, tempat acara boleh dipancarkan atau diterima oleh Go atau JavaScript. Secara pilihan, data boleh dihantar dengan peristiwa. Mempelajari cara kami menggunakan peristiwa dalam aplikasi kami akan membawa kami menganalisis struct App:
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
Perkara pertama yang kita lihat ialah App struct yang mempunyai medan yang menyimpan Go Konteks, diperlukan oleh Wails dan penunjuk kepada struct Db (berkaitan dengan pangkalan data, seperti yang akan kita lihat) . 2 sifat lain ialah rentetan yang kami konfigurasikan supaya dialog asli (diuruskan oleh bahagian belakang) mempersembahkan tajuk mengikut bahasa yang dipilih oleh pengguna. Fungsi yang bertindak sebagai pembina (NewApp) untuk Apl hanya mencipta penuding kepada struct pangkalan data.
Seterusnya kita melihat 2 kaedah yang diperlukan oleh pilihan yang diperlukan Wails: permulaan dan sebelumTutup, yang masing-masing akan kami berikan kepada OnStartup dan OnBeforeClose pilihan. Dengan berbuat demikian mereka secara automatik akan menerima Konteks Go. beforeClose hanya menutup sambungan ke pangkalan data apabila menutup aplikasi. Tetapi permulaan melakukan lebih banyak lagi. Pertama, ia menetapkan konteks yang diterima dalam medan sepadannya. Kedua, ia mendaftarkan satu siri pendengar acara di bahagian belakang yang kami perlukan untuk mencetuskan satu siri tindakan.
Dalam Wails semua pendengar acara mempunyai tandatangan ini:
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
Iaitu, ia menerima konteks (yang kami simpan dalam medan ctx Apl), nama acara (yang akan kami tetapkan di bahagian hadapan) dan panggilan balik yang akan dilaksanakan tindakan yang kami perlukan, dan seterusnya boleh menerima parameter pilihan, jenis mana-mana atau antara muka kosong (antara muka{}), yang sama, jadi kami perlu membuat penegasan jenis.
Sesetengah pendengar yang kami isytiharkan mempunyai pemancar peristiwa bersarang yang diisytiharkan di dalamnya yang akan diterima di bahagian hadapan dan mencetuskan tindakan tertentu di sana. Tandatangannya kelihatan seperti ini:
/* main.go */ package main import ( "embed" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" ) //go:embed all:frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ // Title: "Nu-i uita • minimalist password manager", Width: 450, Height: 300, DisableResize: true, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, OnBeforeClose: app.beforeClose, Bind: []interface{}{ app, }, // Linux platform specific options Linux: &linux.Options{ Icon: icon, // WindowIsTranslucent: true, WebviewGpuPolicy: linux.WebviewGpuPolicyNever, // ProgramName: "wails", }, }) if err != nil { println("Error:", err.Error()) } }
Saya tidak akan menerangkan secara terperinci tentang perkara yang dilakukan oleh pendengar ini, bukan sahaja untuk ringkas tetapi juga kerana Go cukup ekspresif sehingga anda boleh mengetahui perkara yang mereka lakukan hanya dengan membaca kod. Saya hanya akan menerangkan beberapa daripadanya. Pendengar "change_titles" mengharapkan untuk menerima acara dengan nama itu. Peristiwa ini dicetuskan apabila pengguna menukar bahasa antara muka, menukar tajuk bar tajuk tetingkap aplikasi kepada nilai yang diterima oleh pendengar itu sendiri. Kami menggunakan pakej runtime Wails untuk mencapai ini. Acara ini juga menerima tajuk dialog "Pilih Direktori" dan "Pilih Fail" yang disimpan dalam sifat berasingan struct Apl untuk digunakan apabila diperlukan. Seperti yang anda lihat, kami memerlukan acara ini kerana tindakan "asli" ini perlu dilakukan dari bahagian belakang.
Sebutan khas untuk "import_data" dan "kata laluan" pendengar yang, boleh dikatakan, berrantai. Yang pertama ("import_data"), apabila diterima, mencetuskan pembukaan kotak dialog dengan kaedah runtime.OpenFileDialog. Seperti yang dapat kita lihat, kaedah ini menerima antara pilihannya tajuk yang akan dipaparkan, yang disimpan dalam medan Fail terpilih bagi struktur Aplikasi, seperti yang telah kami jelaskan. Jika pengguna memilih fail dan, oleh itu, pembolehubah lokasi fail tidak kosong, peristiwa dipancarkan (dipanggil "enter_password") yang diterima di bahagian hadapan untuk menunjukkan pop timbul di mana pengguna diminta memasukkan kata laluan induk yang dia digunakan semasa dia membuat eksport. Apabila pengguna berbuat demikian, bahagian hadapan memancarkan peristiwa ("kata laluan"), yang kami terima dalam pendengar bahagian belakang kami. Data yang diterima (kata laluan induk) dan laluan ke fail sandaran digunakan oleh kaedah struct Db, yang mewakili pangkalan data (ImportDump). Bergantung pada hasil pelaksanaan kaedah tersebut, peristiwa baharu ("data_import") dikeluarkan yang akan mencetuskan tetingkap timbul di bahagian hadapan dengan hasil import yang berjaya atau gagal.
Seperti yang dapat kita lihat, acara Wails ialah cara komunikasi yang berkuasa dan berkesan antara bahagian belakang dan bahagian hadapan.
Selebihnya kaedah struct App tidak lebih daripada kaedah yang bahagian belakang dedahkan kepada bahagian hadapan, seperti yang telah kami jelaskan, dan yang pada asasnya adalah operasi CRUD dengan pangkalan data dan yang mana, oleh itu, kami terangkan di bawah.
Untuk bahagian belakang ini, saya telah diilhamkan (membuat beberapa pengubahsuaian) oleh siaran ini (di sini di DEV.to) oleh vikkio88 dan repo pengurus kata laluannya, yang pertama kali dia buat dengan C#/Avalonia dan kemudian disesuaikan untuk menggunakan Go/ Fyne (Muscurd-ig).
Bahagian "tahap terendah" bahagian belakang ialah bahagian yang berkaitan dengan penyulitan kata laluan. Yang paling penting ialah 3 fungsi ini:
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
Saya tidak akan membincangkan butiran penyulitan simetri dengan AES dalam Go. Semua yang anda perlu tahu dijelaskan dengan baik dalam siaran ini, di sini di DEV.to.
AES ialah algoritma sifir blok yang mengambil kunci saiz tetap dan teks biasa saiz tetap serta mengembalikan teks sifir saiz tetap. Oleh kerana saiz blok AES ditetapkan kepada 16 bait, teks biasa mestilah sekurang-kurangnya 16 bait panjang. Yang menyebabkan masalah kepada kami kerana kami mahu dapat menyulitkan/menyahsulit data bersaiz sewenang-wenangnya. Untuk menyelesaikan masalah saiz blok plainteks minimum, mod sifir blok wujud. Di sini saya menggunakan mod GCM kerana ia adalah salah satu mod sifir blok simetri yang paling banyak diterima pakai. GCM memerlukan IV (vektor permulaan [array]) yang mesti sentiasa dijana secara rawak (istilah yang digunakan untuk tatasusunan sedemikian ialah bukan sekali).
Pada asasnya, fungsi enkripsi mengambil teks biasa untuk menyulitkan dan kunci rahsia yang akan sentiasa sepanjang 32 bait dan menjana penyulitan AES dengan kunci itu. Dengan penyulitan itu, kami menjana objek gcm yang kami gunakan untuk mencipta vektor permulaan 12 bait (tidak pernah). Kaedah Seal bagi objek gcm membolehkan kami "menyertai" dan menyulitkan teks biasa (sebagai kepingan bait) dengan vektor nonce dan akhirnya menukar hasilnya semula kepada rentetan.
Fungsi nyahsulit melakukan sebaliknya: bahagian pertamanya adalah sama dengan enkripsi, maka kerana kita tahu bahawa teks sifir sebenarnya bukan teks sifir, kita boleh membahagikan teks sifir kepada 2 komponennya. Kaedah NonceSize bagi objek gcm sentiasa menghasilkan "12" (iaitu panjang nonce), dan dengan itu kami membelah kepingan bait pada masa yang sama semasa kami menyahsulitnya dengan kaedah Buka objek gcm. Akhir sekali, kami menukar hasilnya kepada rentetan.
Fungsi keyfy memastikan kami mempunyai kunci rahsia 32-bait (dengan mengalasnya dengan "0" untuk mencapai panjang itu). Kami akan melihat bahawa di bahagian hadapan kami memastikan bahawa pengguna tidak memasukkan aksara lebih daripada satu bait (aksara bukan ASCII), supaya hasil fungsi ini sentiasa 32 bait panjang.
Kod selebihnya dalam fail ini pada asasnya bertanggungjawab untuk pengekodan/penyahkodan kepada base64 input/output fungsi yang diterangkan di atas.
Untuk menyimpan semua data aplikasi yang kami gunakan cloverDB. Ia adalah Pangkalan Data NoSQL berorientasikan dokumen yang ringan dan terbenam, serupa dengan MongoDB. Salah satu ciri pangkalan data ini ialah apabila rekod disimpan, ia diberikan ID (secara lalai, medan ditetapkan sebagai _id, sedikit seperti apa yang berlaku dalam MongoDB) yang merupakan rentetan uuid ( v4). Jadi, jika kita ingin mengisih rekod mengikut susunan kemasukan, kita mesti memberikannya cap masa apabila ia disimpan.
Berdasarkan fakta ini, kami akan mencipta model kami dan kaedah berkaitannya (master_password.go & password_entry.go):
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
MasterPassword mempunyai medan peribadi (clear) yang tidak disimpan/diambil (oleh itu tiada tag semanggi) dalam/dari pangkalan data, iaitu ia hanya tinggal dalam ingatan dan tidak disimpan pada cakera. Sifat ini ialah kata laluan induk yang tidak disulitkan itu sendiri dan akan digunakan sebagai kunci penyulitan untuk entri kata laluan. Nilai ini disimpan oleh setter pada objek MasterPassword atau ditetapkan (oleh panggilan balik) sebagai medan tidak dieksport (peribadi) dalam struct Db pakej dengan nama yang sama (db.go). Untuk input kata laluan kami menggunakan 2 struct, satu yang tidak mempunyai kata laluan yang disulitkan dan satu lagi yang kata laluannya sudah disulitkan, iaitu objek yang sebenarnya akan disimpan dalam pangkalan data (serupa dengan DTO , objek pemindahan data). Kaedah penyulitan/penyahsulitan kedua-dua struktur secara dalaman menggunakan objek Crypto, yang mempunyai sifat dengan kunci penyulitan (iaitu kata laluan induk ditukar kepada kepingan panjang 32 bait):
/* main.go */ package main import ( "embed" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" ) //go:embed all:frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ // Title: "Nu-i uita • minimalist password manager", Width: 450, Height: 300, DisableResize: true, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, OnBeforeClose: app.beforeClose, Bind: []interface{}{ app, }, // Linux platform specific options Linux: &linux.Options{ Icon: icon, // WindowIsTranslucent: true, WebviewGpuPolicy: linux.WebviewGpuPolicyNever, // ProgramName: "wails", }, }) if err != nil { println("Error:", err.Error()) } }
Kata Laluan Induk mempunyai 3 kaedah yang memainkan peranan penting dalam penjimatan/pemulihan data:
/* app.go */ package main import ( "context" "github.com/emarifer/Nu-i-uita/internal/db" "github.com/emarifer/Nu-i-uita/internal/models" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct type App struct { ctx context.Context db *db.Db selectedDirectory string selectedFile string } // NewApp creates a new App application struct func NewApp() *App { db := db.NewDb() return &App{db: db} } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { var fileLocation string a.ctx = ctx runtime.EventsOn(a.ctx, "change_titles", func(optionalData ...interface{}) { if appTitle, ok := optionalData[0].(string); ok { runtime.WindowSetTitle(a.ctx, appTitle) } if selectedDirectory, ok := optionalData[1].(string); ok { a.selectedDirectory = selectedDirectory } if selectedFile, ok := optionalData[2].(string); ok { a.selectedFile = selectedFile } }) runtime.EventsOn(a.ctx, "quit", func(optionalData ...interface{}) { runtime.Quit(a.ctx) }) runtime.EventsOn(a.ctx, "export_data", func(optionalData ...interface{}) { d, _ := runtime.OpenDirectoryDialog(a.ctx, runtime. OpenDialogOptions{ Title: a.selectedDirectory, }) if d != "" { f, err := a.db.GenerateDump(d) if err != nil { runtime.EventsEmit(a.ctx, "saved_as", err.Error()) return } runtime.EventsEmit(a.ctx, "saved_as", f) } }) runtime.EventsOn(a.ctx, "import_data", func(optionalData ...interface{}) { fileLocation, _ = runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ Title: a.selectedFile, }) // fmt.Println("SELECTED FILE:", fileLocation) if fileLocation != "" { runtime.EventsEmit(a.ctx, "enter_password") } }) runtime.EventsOn(a.ctx, "password", func(optionalData ...interface{}) { // fmt.Printf("MY PASS: %v", optionalData...) if pass, ok := optionalData[0].(string); ok { if len(fileLocation) != 0 { err := a.db.ImportDump(pass, fileLocation) if err != nil { runtime.EventsEmit(a.ctx, "imported_data", err.Error()) return } runtime.EventsEmit(a.ctx, "imported_data", "success") } } }) } // beforeClose is called when the application is about to quit, // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { defer a.db.Close() return false } ...
GetCrypto membolehkan anda mendapatkan contoh semasa objek Crypto supaya pakej db.go boleh menyulitkan/menyahsulit entri kata laluan. SetClear ialah setter yang kami nyatakan sebelum ini dan Semak ialah fungsi yang mengesahkan sama ada kata laluan induk yang dimasukkan oleh pengguna adalah betul; seperti yang kita dapat lihat, sebagai tambahan kepada kata laluan, ia mengambil sebagai hujah panggilan balik, yang bergantung pada kes akan menjadi setter yang disebutkan di atas (apabila kami mengimport data daripada fail sandaran ) atau kaedah SetMasterPassword bagi pakej db.go yang menetapkan nilai dalam medan peribadi struct Db apabila pengguna log masuk.
Saya tidak akan menerangkan secara terperinci semua kaedah pakej db.go kerana kebanyakan kodnya berkaitan dengan cara bekerja dengan cloverDB, yang anda boleh semak dalam dokumentasinya, walaupun saya telah menyebut beberapa perkara penting yang akan digunakan di sini.
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
Mula-mula kita mempunyai struct yang akan menyimpan penunjuk ke contoh cloverDB. Ia juga menyimpan penuding kepada contoh "penuh" bagi struct MasterPassword. "Penuh" di sini bermakna ia menyimpan kedua-dua kata laluan induk yang disulitkan (bermaksud ia wujud dalam pangkalan data dan oleh itu kata laluan induk semasa) dan kata laluan induk yang tidak disulitkan, yang akan digunakan untuk penyulitan entri kata laluan. Seterusnya kita mempunyai setupCollections, NewDb dan Tutup, yang merupakan fungsi dan kaedah untuk menyediakan pangkalan data apabila aplikasi dimulakan dan ditutup. cloverDB tidak mencipta fail/direktori storan secara automatik apabila digunakan dengan kaedah Terbuka, sebaliknya kita perlu menciptanya secara manual. Akhir sekali, GetLanguageCode dan SaveLanguageCode ialah kaedah untuk mendapatkan semula/menyimpan bahasa aplikasi yang dipilih oleh pengguna. Memandangkan kod bahasa yang dipilih ialah rentetan kecil ("en" atau "es"), untuk kesederhanaan kami tidak menggunakan struct untuk menyimpannya: contohnya, untuk mendapatkan semula kod bahasa daripada koleksi (cloverDB berfungsi dengan "dokumen" dan "koleksi", serupa dengan MongoDB), kami hanya lulus kekunci di bawahnya ia disimpan ("kod") dan membuat jenis penegasan.
Apabila pengguna log masuk buat kali pertama, kata laluan induk disimpan dalam pangkalan data: nilai ini (tidak disulitkan) ditetapkan dalam medan jelas objek MasterPassword, yang turut menyimpan yang telah disulitkan kata laluan, dan disimpan dalam struct Db:
/* main.go */ package main import ( "embed" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" ) //go:embed all:frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ // Title: "Nu-i uita • minimalist password manager", Width: 450, Height: 300, DisableResize: true, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, OnBeforeClose: app.beforeClose, Bind: []interface{}{ app, }, // Linux platform specific options Linux: &linux.Options{ Icon: icon, // WindowIsTranslucent: true, WebviewGpuPolicy: linux.WebviewGpuPolicyNever, // ProgramName: "wails", }, }) if err != nil { println("Error:", err.Error()) } }
Pemulihan kata laluan induk dilakukan dua kali apabila memulakan aplikasi:
Dalam kedua-dua kes, kaedah RecoverMasterPassword dipanggil, yang hanya jika terdapat kata laluan induk yang disimpan akan menetapkan contoh dalam medan cachedMp struct Db:
/* app.go */ package main import ( "context" "github.com/emarifer/Nu-i-uita/internal/db" "github.com/emarifer/Nu-i-uita/internal/models" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct type App struct { ctx context.Context db *db.Db selectedDirectory string selectedFile string } // NewApp creates a new App application struct func NewApp() *App { db := db.NewDb() return &App{db: db} } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { var fileLocation string a.ctx = ctx runtime.EventsOn(a.ctx, "change_titles", func(optionalData ...interface{}) { if appTitle, ok := optionalData[0].(string); ok { runtime.WindowSetTitle(a.ctx, appTitle) } if selectedDirectory, ok := optionalData[1].(string); ok { a.selectedDirectory = selectedDirectory } if selectedFile, ok := optionalData[2].(string); ok { a.selectedFile = selectedFile } }) runtime.EventsOn(a.ctx, "quit", func(optionalData ...interface{}) { runtime.Quit(a.ctx) }) runtime.EventsOn(a.ctx, "export_data", func(optionalData ...interface{}) { d, _ := runtime.OpenDirectoryDialog(a.ctx, runtime. OpenDialogOptions{ Title: a.selectedDirectory, }) if d != "" { f, err := a.db.GenerateDump(d) if err != nil { runtime.EventsEmit(a.ctx, "saved_as", err.Error()) return } runtime.EventsEmit(a.ctx, "saved_as", f) } }) runtime.EventsOn(a.ctx, "import_data", func(optionalData ...interface{}) { fileLocation, _ = runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ Title: a.selectedFile, }) // fmt.Println("SELECTED FILE:", fileLocation) if fileLocation != "" { runtime.EventsEmit(a.ctx, "enter_password") } }) runtime.EventsOn(a.ctx, "password", func(optionalData ...interface{}) { // fmt.Printf("MY PASS: %v", optionalData...) if pass, ok := optionalData[0].(string); ok { if len(fileLocation) != 0 { err := a.db.ImportDump(pass, fileLocation) if err != nil { runtime.EventsEmit(a.ctx, "imported_data", err.Error()) return } runtime.EventsEmit(a.ctx, "imported_data", "success") } } }) } // beforeClose is called when the application is about to quit, // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { defer a.db.Close() return false } ...
Seterusnya, terdapat 2 keping kod kecil tetapi penting:
EventsOn( ctx context.Context, eventName string, callback func(optionalData ...interface{}), ) func()
Melebihi operasi CRUD biasa bagi mana-mana todoapp, kami mempunyai fungsi atau kaedah lain untuk mengulas:
. ├── app.go ├── build │ ├── appicon.png │ ├── darwin │ │ ├── Info.dev.plist │ │ └── Info.plist │ ├── README.md │ └── windows │ ├── icon.ico │ ├── info.json │ ├── installer │ │ ├── project.nsi │ │ └── wails_tools.nsh │ └── wails.exe.manifest ├── frontend │ ├── index.html │ ├── package.json │ ├── package.json.md5 │ ├── package-lock.json │ ├── postcss.config.js │ ├── README.md │ ├── src │ │ ├── App.svelte │ │ ├── assets │ │ │ ├── fonts │ │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ │ └── OFL.txt │ │ │ └── images │ │ │ └── logo-universal.png │ │ ├── lib │ │ │ ├── BackBtn.svelte │ │ │ ├── BottomActions.svelte │ │ │ ├── EditActions.svelte │ │ │ ├── EntriesList.svelte │ │ │ ├── Language.svelte │ │ │ ├── popups │ │ │ │ ├── alert-icons.ts │ │ │ │ └── popups.ts │ │ │ ├── ShowPasswordBtn.svelte │ │ │ └── TopActions.svelte │ │ ├── locales │ │ │ ├── en.json │ │ │ └── es.json │ │ ├── main.ts │ │ ├── pages │ │ │ ├── About.svelte │ │ │ ├── AddPassword.svelte │ │ │ ├── Details.svelte │ │ │ ├── EditPassword.svelte │ │ │ ├── Home.svelte │ │ │ ├── Login.svelte │ │ │ └── Settings.svelte │ │ ├── style.css │ │ └── vite-env.d.ts │ ├── svelte.config.js │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── wailsjs │ ├── go │ │ ├── main │ │ │ ├── App.d.ts │ │ │ └── App.js │ │ └── models.ts │ └── runtime │ ├── package.json │ ├── runtime.d.ts │ └── runtime.js ├── go.mod ├── go.sum ├── internal │ ├── db │ │ └── db.go │ └── models │ ├── crypto.go │ ├── master_password.go │ └── password_entry.go ├── LICENSE ├── main.go ├── Makefile ├── README.md ├── scripts └── wails.json
loadPasswordEntryDTO ialah fungsi pembantu yang mencipta objek PasswordEntryDTO daripada satu dokumen yang diperoleh daripada cloverDB. loadManyPasswordEntryDTO melakukan perkara yang sama, tetapi daripada sekeping dokumen cloverDB, menghasilkan sekeping PasswordEntryDTO. Akhir sekali, loadManyPasswordEntry melakukan perkara yang sama seperti loadManyPasswordEntryDTO tetapi juga menyahsulit dokumen yang diperoleh daripada cloverDB daripada contoh objek Crypto yang dijana oleh getCInstance kaedah.
Akhir sekali, antara kaedah yang tidak berkaitan dengan CRUD, kami mempunyai kaedah yang digunakan dalam eksport/import data:
/* main.go */ package main import ( "embed" "github.com/wailsapp/wails/v2" "github.com/wailsapp/wails/v2/pkg/options" "github.com/wailsapp/wails/v2/pkg/options/assetserver" "github.com/wailsapp/wails/v2/pkg/options/linux" ) //go:embed all:frontend/dist var assets embed.FS //go:embed build/appicon.png var icon []byte func main() { // Create an instance of the app structure app := NewApp() // Create application with options err := wails.Run(&options.App{ // Title: "Nu-i uita • minimalist password manager", Width: 450, Height: 300, DisableResize: true, AssetServer: &assetserver.Options{ Assets: assets, }, BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, OnStartup: app.startup, OnBeforeClose: app.beforeClose, Bind: []interface{}{ app, }, // Linux platform specific options Linux: &linux.Options{ Icon: icon, // WindowIsTranslucent: true, WebviewGpuPolicy: linux.WebviewGpuPolicyNever, // ProgramName: "wails", }, }) if err != nil { println("Error:", err.Error()) } }
GenerateDump menggunakan struct DbDump yang akan menjadi objek yang disimpan dalam fail sandaran. Ia mengambil sebagai namanya laluan direktori yang dipilih oleh pengguna, format tarikh dan sambungan ad hoc. Kemudian kami mencipta contoh DbDump dengan kata laluan induk yang disulitkan, kepingan DTO (dengan kata laluan yang sepadan juga disulitkan) dan kod bahasa yang disimpan oleh pengguna dalam pangkalan data. Objek ini akhirnya dikodkan dalam binari oleh pakej Golang gob dalam fail yang telah kami buat, mengembalikan nama fail kepada UI untuk memaklumkan pengguna tentang kejayaan penciptaannya.
Sebaliknya, ImportDump mengambil sebagai hujah kata laluan induk yang diminta oleh UI kepada pengguna, iaitu kata laluan yang berkuat kuasa semasa eksport dilakukan dan laluan ke fail sandaran. Kini, ia menyahsulit fail yang dipilih menggunakan struktur DbDump dan kemudian memperoleh contoh MasterPassword daripada kata laluan induk yang disulitkan yang disimpan dalam DbDump. Dalam langkah seterusnya, kami mengesahkan bahawa kata laluan yang dibekalkan oleh pengguna adalah betul, sambil menetapkan medan yang jelas dalam contoh MasterPassword:
/* app.go */ package main import ( "context" "github.com/emarifer/Nu-i-uita/internal/db" "github.com/emarifer/Nu-i-uita/internal/models" "github.com/wailsapp/wails/v2/pkg/runtime" ) // App struct type App struct { ctx context.Context db *db.Db selectedDirectory string selectedFile string } // NewApp creates a new App application struct func NewApp() *App { db := db.NewDb() return &App{db: db} } // startup is called when the app starts. The context is saved // so we can call the runtime methods func (a *App) startup(ctx context.Context) { var fileLocation string a.ctx = ctx runtime.EventsOn(a.ctx, "change_titles", func(optionalData ...interface{}) { if appTitle, ok := optionalData[0].(string); ok { runtime.WindowSetTitle(a.ctx, appTitle) } if selectedDirectory, ok := optionalData[1].(string); ok { a.selectedDirectory = selectedDirectory } if selectedFile, ok := optionalData[2].(string); ok { a.selectedFile = selectedFile } }) runtime.EventsOn(a.ctx, "quit", func(optionalData ...interface{}) { runtime.Quit(a.ctx) }) runtime.EventsOn(a.ctx, "export_data", func(optionalData ...interface{}) { d, _ := runtime.OpenDirectoryDialog(a.ctx, runtime. OpenDialogOptions{ Title: a.selectedDirectory, }) if d != "" { f, err := a.db.GenerateDump(d) if err != nil { runtime.EventsEmit(a.ctx, "saved_as", err.Error()) return } runtime.EventsEmit(a.ctx, "saved_as", f) } }) runtime.EventsOn(a.ctx, "import_data", func(optionalData ...interface{}) { fileLocation, _ = runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ Title: a.selectedFile, }) // fmt.Println("SELECTED FILE:", fileLocation) if fileLocation != "" { runtime.EventsEmit(a.ctx, "enter_password") } }) runtime.EventsOn(a.ctx, "password", func(optionalData ...interface{}) { // fmt.Printf("MY PASS: %v", optionalData...) if pass, ok := optionalData[0].(string); ok { if len(fileLocation) != 0 { err := a.db.ImportDump(pass, fileLocation) if err != nil { runtime.EventsEmit(a.ctx, "imported_data", err.Error()) return } runtime.EventsEmit(a.ctx, "imported_data", "success") } } }) } // beforeClose is called when the application is about to quit, // either by clicking the window close button or calling runtime.Quit. // Returning true will cause the application to continue, false will continue shutdown as normal. func (a *App) beforeClose(ctx context.Context) (prevent bool) { defer a.db.Close() return false } ...
Akhir sekali, kami mendapat contoh objek Crypto daripada contoh MasterPasword yang dibuat pada langkah sebelumnya dan melakukan 2 perkara dalam gelung berikut:
Perkara terakhir yang kami tinggalkan ialah menyimpan kod bahasa dalam pangkalan data.
Dan itu sudah cukup untuk hari ini, tutorial ini telah menjadi panjang ??.
Dalam bahagian kedua, kami akan memperincikan bahagian hadapan yang, seperti yang saya katakan, dibuat dengan Svelte.
Jika anda tidak sabar, seperti yang saya telah beritahu, anda boleh menemui semua kod dalam repo ini.
Jumpa anda di bahagian kedua. Selamat mengekod?!!
Atas ialah kandungan terperinci Apl desktop pengurus kata laluan minimalis: pencerobohan ke dalam rangka kerja Golang's Wails (Bahagian 1). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!