Wails开发教程:原理、架构与设计思想

Wails是一个现代化的桌面应用开发框架,允许开发者使用Go语言和Web技术(HTML、CSS、JavaScript)来构建跨平台的桌面应用程序。它被设计为Go语言的快速且轻量的Electron替代方案。

目录
  1. info 1. Wails简介和概述
  2. 核心特点
  3. 适用场景
  4. architecture 2. Wails的架构设计
  5. 核心组件详解
  6. settings 3. Wails的工作原理
  7. 应用启动流程
  8. 前后端通信机制
  9. 资源处理机制
  10. compare 4. Wails与Electron的对比
  11. 架构对比
  12. 性能对比
  13. 开发体验对比
  14. 适用场景对比
文本版 · 供搜索与朗读

Wails开发详尽教程:原理、架构与设计思想 (1/2)

Wails开发详尽教程

原理、架构与设计思想深度解析

第 1 页 / 共 2 页

info
1. Wails简介和概述

Wails是一个现代化的桌面应用开发框架,允许开发者使用Go语言和Web技术(HTML、CSS、JavaScript)来构建跨平台的桌面应用程序。它被设计为Go语言的快速且轻量的Electron替代方案。

核心特点

原生性能:Wails不嵌入浏览器,而是利用各平台的原生渲染引擎(Windows上的WebView2,macOS上的WebKit,Linux上的WebKitGTK),从而提供更小的应用体积和更好的性能。

Go后端:使用Go语言的强大功能处理业务逻辑、系统调用和性能密集型任务。

Web前端:可以使用任何熟悉的前端技术(React、Vue、Svelte、Angular等)构建用户界面。

跨平台支持:支持Windows、macOS和Linux三大主流操作系统。

原生UI元素:提供原生菜单、对话框、主题和半透明窗口等原生UI元素。

轻量级:相比Electron,Wails应用程序体积更小,内存占用更低。

适用场景

Wails特别适合以下场景:

需要构建轻量级桌面应用的Go开发者

希望利用现有Web技术栈构建桌面应用的前端开发者

对应用体积和性能有较高要求的项目

需要与操作系统底层功能深度交互的应用

architecture
2. Wails的架构设计

Wails采用了一种独特的架构设计,将Go后端与Web前端无缝集成,同时保持各自的优势。其架构主要由以下几个核心组件组成:

前端层 (Frontend) - Web技术 (React, Vue, Svelte等)

桥接层 (Bridge Layer) - JavaScript ↔ Go 通信

后端层 (Backend) - Go语言实现

原生渲染引擎 (Native Rendering Engine) - WebView2/WebKit

核心组件详解

前端层 (Frontend)

前端层使用标准的Web技术构建用户界面,支持多种前端框架:

React:用于构建复杂交互界面的流行库

Vue:渐进式JavaScript框架,易于学习和使用

Svelte:编译时框架,生成高效的原生JavaScript代码

Angular:完整的前端框架,适合大型企业应用

Vanilla JS:纯JavaScript实现,适合轻量级应用

桥接层 (Bridge Layer)

桥接层是Wails架构的核心,负责前端JavaScript与后端Go代码之间的通信:

上下文 (Context):提供应用状态管理和生命周期控制

事件系统 (Events):支持Go和JavaScript之间的双向事件通信

方法调用 (Methods):允许前端直接调用后端Go方法

数据绑定 (Data Binding):自动将Go结构体转换为TypeScript定义

后端层 (Backend)

后端层使用Go语言实现,负责处理业务逻辑和系统交互:

应用逻辑 (App Logic):开发者编写的业务代码

运行时 (Runtime):提供窗口管理、菜单、对话框等原生功能

上下文管理 (Context):管理应用状态和资源

原生渲染引擎 (Native Rendering Engine)

Wails不嵌入浏览器,而是使用各平台的原生渲染引擎:

Windows:使用Microsoft WebView2(基于Chromium)

macOS:使用WebKit(Safari的渲染引擎)

Linux:使用WebKitGTK

这种设计使得Wails应用体积更小,性能更好,同时能够提供与原生应用一致的用户体验。

settings
3. Wails的工作原理

Wails的工作原理可以从应用启动、前后端通信、以及资源处理三个方面来理解。

应用启动流程

用户启动Wails应用

操作系统加载原生二进制文件

Go后端初始化,创建应用上下文

启动原生WebView组件

加载前端资源(HTML、CSS、JS)

前端JavaScript初始化,建立与Go后端的连接

应用完全启动,用户可以交互

前后端通信机制

Wails采用了一种高效的桥接机制,实现前端JavaScript与后端Go代码之间的通信:

方法调用

Wails自动将导出的Go方法暴露给前端JavaScript,使得前端可以直接调用这些方法:

go

// Go后端代码
type App struct {
runtime *wails.Runtime
}

func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}

javascript

// 前端JavaScript代码
async function sayHello() {
const result = await window.backend.App.Greet("World");
console.log(result); // 输出: Hello, World!
}

事件系统

Wails提供了统一的事件系统,支持Go和JavaScript之间的双向事件通信:

go

// Go后端发送事件
func (a *App) SendNotification() {
a.runtime.Events.Emit("notification", "New message received")
}

javascript

// 前端JavaScript监听事件
window.runtime.EventsOn("notification", (message) => {
console.log("Notification:", message);
// 显示通知
});

数据绑定

Wails能够自动将Go结构体转换为TypeScript定义,确保前后端数据类型的一致性:

go

// Go结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"isActive"`
}

typescript

// 自动生成的TypeScript定义
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}

资源处理机制

Wails提供了灵活的资源处理机制,支持开发模式和生产模式的不同需求:

开发模式

在开发模式下,Wails会:

启动一个开发服务器,实时监控文件变化

自动重新编译Go代码并重启应用

自动刷新前端资源,无需手动刷新

bash

wails dev

生产模式

在生产模式下,Wails会:

将前端资源打包到二进制文件中

优化应用性能和体积

生成可用于分发的安装包

bash

wails build

compare
4. Wails与Electron的对比

Wails经常被比作Go版本的Electron,但两者在架构、性能和资源消耗等方面有显著差异。

架构对比

特性
Wails
Electron

后端语言
Go
Node.js

前端技术
任何Web技术
任何Web技术

渲染引擎
系统原生WebView
嵌入式Chromium

应用结构
单一二进制文件
主进程 + 多个渲染进程

进程模型
单进程
多进程

性能对比

指标
Wails
Electron

启动速度
快
较慢

内存占用
低(通常5-20MB)
高(通常50-100MB+)

CPU使用率
低
中等

应用体积
小(通常5-15MB)
大(通常50-100MB+)

开发体验对比

方面
Wails
Electron

学习曲线
需要Go知识
需要Node.js知识

调试工具
标准Go调试工具
丰富的前端调试工具

生态系统
Go生态系统 + Web生态系统
Node.js生态系统 + Web生态系统

原生功能
直接通过Go调用
通过Node.js原生模块或C++插件

适用场景对比

Wails更适合:

对应用体积和性能有较高要求的项目

Go开发者构建桌面应用

需要与系统底层深度交互的应用

资源受限的环境

Electron更适合:

需要快速开发和迭代的项目

复杂的多窗口应用

需要丰富前端调试工具的项目

团队主要是前端开发者

tips_and_updates
选择建议

选择Wails还是Electron取决于项目需求和团队技术栈。如果团队熟悉Go语言,且对应用体积和性能有较高要求,Wails是更好的选择。如果团队主要是前端开发者,且需要快速开发和丰富的调试工具,Electron可能更适合。

© 2025 Wails开发详尽教程 | 原理、架构与设计思想深度解析

第 1 页
arrow_forward
第 2 页

👍 1

想参与讨论或点赞?登录后使用完整功能

讨论回复(2)

✨
文本版 · 供搜索与朗读

Wails开发详尽教程:原理、架构与设计思想 (2/2)

Wails开发详尽教程

原理、架构与设计思想深度解析

第 2 页 / 共 2 页

build
5. Wails的开发环境搭建

要开始使用Wails开发桌面应用,需要先搭建好开发环境。以下是详细的步骤:

系统要求

操作系统:Windows 10/11, macOS 10.15+, Linux

Go:版本1.21或更高(macOS 15+需要Go 1.23.3+)

Node.js:版本15或更高(包含NPM)

安装Go

访问Go官方下载页面

下载适合你操作系统的Go安装包

按照官方安装说明进行安装

验证安装:

bash

go version

5. 确保Go的bin目录已添加到PATH环境变量:

bash

echo $PATH | grep go/bin

安装Node.js和NPM

访问Node.js官方下载页面

下载适合你操作系统的Node.js安装包(LTS版本推荐)

按照安装向导完成安装

验证安装:

bash

node --version
npm --version

安装Wails CLI

Wails CLI是Wails的命令行工具,用于创建、构建和管理Wails项目。

使用Go安装Wails CLI:

bash

go install github.com/wailsapp/wails/v2/cmd/wails@latest

2. 验证安装:

bash

wails version

平台特定依赖

Windows

Windows系统需要安装WebView2运行时:

检查是否已安装WebView2:

bash

wails doctor

2. 如果未安装,可以从Microsoft官网下载并安装

macOS

macOS系统需要安装Xcode命令行工具:

bash

xcode-select --install

Linux

Linux系统需要安装一些依赖包,具体命令因发行版而异。可以使用Wails doctor命令检查所需依赖:

bash

wails doctor

根据输出结果安装相应的依赖包。例如,在Ubuntu/Debian系统上:

bash

sudo apt update
sudo apt install build-essential libgtk-3-dev libwebkit2gtk-4.0-dev

验证开发环境

运行以下命令验证开发环境是否正确配置:

bash

wails doctor

tips_and_updates
提示

如果所有检查都通过,说明开发环境已经搭建完成,可以开始创建Wails项目了。

folder
6. Wails项目结构

了解Wails项目的标准结构对于高效开发至关重要。本节将详细介绍Wails项目的目录结构和各个文件的作用。

标准项目结构

text

myproject/
├── app.go # 主应用文件
├── build/ # 构建配置和资源
│ ├── appicon.png # 应用图标
│ ├── darwin/ # macOS特定构建配置
│ ├── windows/ # Windows特定构建配置
│ └── linux/ # Linux特定构建配置
├── frontend/ # 前端源代码
│ ├── dist/ # 构建后的前端资源
│ ├── src/ # 前端源代码
│ ├── package.json # 前端依赖配置
│ ├── vite.config.js # Vite构建配置
│ └── wailsjs/ # 自动生成的JS绑定
├── go.mod # Go模块定义
├── go.sum # Go依赖校验
└── wails.json # Wails项目配置

核心文件详解

app.go

这是Wails应用的主文件,包含应用的核心逻辑和结构。一个典型的app.go文件如下:

go

package main

import (
"context"
"fmt"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

// App 结构体
type App struct {
ctx context.Context
}

// NewApp 创建一个新的App实例
func NewApp() *App {
return &App{}
}

// OnStartup 应用启动时调用
func (a *App) OnStartup(ctx context.Context) {
a.ctx = ctx
}

// OnDomReady 前端DOM加载完成时调用
func (a *App) OnDomReady(ctx context.Context) {
// 在这里可以安全地调用运行时方法
}

// OnShutdown 应用关闭时调用
func (a *App) OnShutdown(ctx context.Context) {
// 清理资源
}

// Greet 示例方法,可从前端调用
func (a *App) Greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}

func main() {
// 创建Wails应用实例
app := NewApp()

// 配置应用选项
err := wails.Run(&options.App{
Title: "My Project",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{
Assets: http.Dir("./frontend/dist"),
},
OnStartup: app.OnStartup,
OnDomReady: app.OnDomReady,
OnShutdown: app.OnShutdown,
})

if err != nil {
println("Error:", err.Error())
}
}

wails.json

这是Wails项目的配置文件,定义了项目的各种设置:

json

{
"name": "myproject",
"outputfilename": "myproject",
"frontend:install": "npm install",
"frontend:build": "npm run build",
"frontend:dev:watcher": "npm run dev",
"frontend:dev:serverUrl": "auto",
"wailsjsdir": "./frontend",
"version": "2",
"debug": false,
"devServer": {
"bindAddress": "localhost",
"port": 34115,
"assetServer": {
"host": "localhost",
"port": 34116
}
},
"author": {
"name": "Your Name",
"email": "your.email@example.com"
},
"info": {
"companyName": "Your Company",
"productName": "My Project",
"productVersion": "1.0.0",
"copyright": "Copyright © 2025, Your Company",
"comments": "Built with Wails"
},
"nsisType": "multiple",
"obfuscated": false,
"garbleargs": "",
"packaged": true
}

项目创建和配置

创建新项目

使用Wails CLI创建新项目:

bash

# 创建一个使用Vue和TypeScript的新项目
wails init -n myproject -t vue-ts

# 进入项目目录
cd myproject

# 安装依赖
wails doctor
npm install

运行项目

在开发模式下运行项目:

bash

wails dev

构建生产版本:

bash

wails build

extension
7. Wails的核心功能

Wails提供了丰富的功能,使开发者能够构建功能强大的桌面应用。本节将详细介绍Wails的核心功能及其使用方法。

原生UI元素

Wails提供了对原生UI元素的支持,使应用能够与操作系统无缝集成。

菜单

Wails允许创建原生菜单,包括应用菜单、上下文菜单和托盘菜单。

go

package main

import (
"context"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/menu"
"github.com/wailsapp/wails/v2/pkg/options"
)

// App 结构体
type App struct {
ctx context.Context
}

// 创建应用菜单
func (a *App) createApplicationMenu() *menu.Menu {
appMenu := menu.NewMenu()

// 文件菜单
fileMenu := appMenu.AddSubmenu("File")
fileMenu.AddText("&New", nil, a.onNew)
fileMenu.AddText("&Open", nil, a.onOpen)
fileMenu.AddSeparator()
fileMenu.AddText("&Save", nil, a.onSave)
fileMenu.AddText("Save &As", nil, a.onSaveAs)
fileMenu.AddSeparator()
fileMenu.AddText("&Quit", keys.CmdOrCtrl("q"), a.onQuit)

// 编辑菜单
editMenu := appMenu.AddSubmenu("Edit")
editMenu.AddText("&Undo", keys.CmdOrCtrl("z"), a.onUndo)
editMenu.AddText("&Redo", keys.CmdOrCtrl("shift+z"), a.onRedo)
editMenu.AddSeparator()
editMenu.AddText("Cu&t", keys.CmdOrCtrl("x"), a.onCut)
editMenu.AddText("&Copy", keys.CmdOrCtrl("c"), a.onCopy)
editMenu.AddText("&Paste", keys.CmdOrCtrl("v"), a.onPaste)

return appMenu
}

// 菜单回调函数
func (a *App) onNew(_ context.Context) {
// 处理新建操作
}

func (a *App) onOpen(_ context.Context) {
// 处理打开操作
}

// ... 其他回调函数

func main() {
app := NewApp()

err := wails.Run(&options.App{
Title: "Menu Demo",
Width: 800,
Height: 600,
Menu: app.createApplicationMenu(),
OnStartup: app.OnStartup,
OnDomReady: app.OnDomReady,
OnShutdown: app.OnShutdown,
})

if err != nil {
println("Error:", err.Error())
}
}

前后端通信

Wails提供了强大的前后端通信机制,使前端JavaScript能够轻松调用后端Go方法,并支持双向事件通信。

方法调用

Wails自动将导出的Go方法暴露给前端JavaScript,使得前端可以直接调用这些方法:

go

// Go后端代码
package main

import (
"context"
"fmt"
"time"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

// User 结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
IsActive bool `json:"isActive"`
}

// App 结构体
type App struct {
ctx context.Context
}

// 获取用户信息
func (a *App) GetUser(id int) (*User, error) {
// 模拟从数据库获取用户
if id == 1 {
return &User{
ID: 1,
Name: "John Doe",
Email: "john@example.com",
IsActive: true,
}, nil
}

return nil, fmt.Errorf("User not found")
}

// 获取用户列表
func (a *App) GetUserList() ([]User, error) {
// 模拟从数据库获取用户列表
users := []User{
{ID: 1, Name: "John Doe", Email: "john@example.com", IsActive: true},
{ID: 2, Name: "Jane Smith", Email: "jane@example.com", IsActive: true},
{ID: 3, Name: "Bob Johnson", Email: "bob@example.com", IsActive: false},
}

return users, nil
}

// 执行耗时操作
func (a *App) LongRunningOperation(seconds int) (string, error) {
// 模拟耗时操作
time.Sleep(time.Duration(seconds) * time.Second)

return fmt.Sprintf("Operation completed after %d seconds", seconds), nil
}

javascript

// 前端JavaScript代码
// 获取单个用户
async function getUser() {
try {
const user = await window.backend.App.GetUser(1);
console.log("User:", user);
return user;
} catch (error) {
console.error("Error getting user:", error);
return null;
}
}

// 获取用户列表
async function getUserList() {
try {
const users = await window.backend.App.GetUserList();
console.log("Users:", users);
return users;
} catch (error) {
console.error("Error getting user list:", error);
return [];
}
}

// 执行耗时操作
async function performLongOperation() {
try {
const result = await window.backend.App.LongRunningOperation(3);
console.log("Result:", result);
return result;
} catch (error) {
console.error("Error performing long operation:", error);
return null;
}
}

事件系统

Wails提供了统一的事件系统,支持Go和JavaScript之间的双向事件通信:

go

// Go后端发送事件
func (a *App) SendNotification() {
a.runtime.Events.Emit("notification", "New message received")
}

javascript

// 前端JavaScript监听事件
window.runtime.EventsOn("notification", (message) => {
console.log("Notification:", message);
// 显示通知
});

文件系统操作

Wails提供了丰富的文件系统操作功能,使应用能够读写文件、目录等。

go

package main

import (
"context"
"os"
"path/filepath"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

// App 结构体
type App struct {
ctx context.Context
}

// 读取文件内容
func (a *App) ReadFile(path string) (string, error) {
content, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(content), nil
}

// 写入文件内容
func (a *App) WriteFile(path, content string) error {
return os.WriteFile(path, []byte(content), 0644)
}

// 检查文件是否存在
func (a *App) FileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}

// 创建目录
func (a *App) CreateDirectory(path string) error {
return os.MkdirAll(path, 0755)
}

// 删除文件
func (a *App) DeleteFile(path string) error {
return os.Remove(path)
}

// 删除目录
func (a *App) DeleteDirectory(path string) error {
return os.RemoveAll(path)
}

// 列出目录内容
func (a *App) ListDirectory(path string) ([]string, error) {
entries, err := os.ReadDir(path)
if err != nil {
return nil, err
}

var files []string
for _, entry := range entries {
files = append(files, entry.Name())
}

return files, nil
}

// 获取文件信息
func (a *App) GetFileInfo(path string) (map[string]interface{}, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}

fileInfo := map[string]interface{}{
"name": info.Name(),
"size": info.Size(),
"mode": info.Mode(),
"modTime": info.ModTime(),
"isDir": info.IsDir(),
}

return fileInfo, nil
}

// 获取应用数据目录
func (a *App) GetAppDataDir() (string, error) {
// 获取用户数据目录
userDataDir, err := os.UserConfigDir()
if err != nil {
return "", err
}

// 创建应用特定的数据目录
appDataDir := filepath.Join(userDataDir, "myapp")
err = os.MkdirAll(appDataDir, 0755)
if err != nil {
return "", err
}

return appDataDir, nil
}

系统集成

Wails提供了与操作系统深度集成的功能,使应用能够访问系统级功能。

系统托盘

go

package main

import (
"context"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/menu"
"github.com/wailsapp/wails/v2/pkg/options"
"github.com/wailsapp/wails/v2/pkg/options/mac"
"github.com/wailsapp/wails/v2/pkg/options/windows"
)

// App 结构体
type App struct {
ctx context.Context
}

// 创建托盘菜单
func (a *App) createTrayMenu() *menu.Menu {
trayMenu := menu.NewMenu()

// 显示/隐藏窗口
if a.runtime.Window.IsVisible() {
trayMenu.AddText("Hide", nil, func(_ context.Context) {
a.runtime.Window.Hide()
})
} else {
trayMenu.AddText("Show", nil, func(_ context.Context) {
a.runtime.Window.Show()
})
}

trayMenu.AddSeparator()

// 退出应用
trayMenu.AddText("Quit", nil, func(_ context.Context) {
a.runtime.Quit()
})

return trayMenu
}

func main() {
app := NewApp()

err := wails.Run(&options.App{
Title: "Tray Demo",
Width: 800,
Height: 600,
Tray: &options.Tray{
Icon: "build/appicon.png",
Menu: app.createTrayMenu(),
Tooltip: "Tray Demo App",
},
OnStartup: app.OnStartup,
OnDomReady: app.OnDomReady,
OnShutdown: app.OnShutdown,
})

if err != nil {
println("Error:", err.Error())
}
}

系统通知

go

package main

import (
"context"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

// App 结构体
type App struct {
ctx context.Context
}

// 显示系统通知
func (a *App) ShowNotification(title, message string) {
a.runtime.Notification.Notify(title, message)
}

// 显示带图标的系统通知
func (a *App) ShowNotificationWithIcon(title, message, iconPath string) {
a.runtime.Notification.Notify(title, message)
// 注意:图标支持可能因平台而异
}

code
8. Wails的实战案例

通过实际案例来学习Wails的开发是最有效的方式。本节将通过几个完整的实战案例,展示如何使用Wails构建功能丰富的桌面应用。

案例1:待办事项管理应用

这是一个简单的待办事项管理应用,展示了Wails的基本功能,包括前后端通信、数据持久化和原生UI元素。

项目结构

text

todo-app/
├── app.go # 主应用文件
├── models/ # 数据模型
│ └── todo.go # 待办事项模型
├── services/ # 业务逻辑
│ └── todo_service.go # 待办事项服务
├── build/ # 构建配置和资源
├── frontend/ # 前端源代码
│ ├── src/
│ │ ├── components/
│ │ │ ├── TodoList.vue # 待办事项列表组件
│ │ │ ├── TodoForm.vue # 待办事项表单组件
│ │ │ └── TodoItem.vue # 待办事项项组件
│ │ ├── stores/
│ │ │ └── todoStore.js # 状态管理
│ │ ├── App.vue
│ │ └── main.js
│ └── ...
├── go.mod
├── go.sum
└── wails.json

后端实现

go

package models

import "time"

// Todo 待办事项模型
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Completed bool `json:"completed"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

go

package services

import (
"fmt"
"sync"
"time"

"todo-app/models"
)

// TodoService 待办事项服务
type TodoService struct {
todos []*models.Todo
nextID int
mutex sync.RWMutex
}

// NewTodoService 创建新的待办事项服务
func NewTodoService() *TodoService {
service := &TodoService{
todos: make([]*models.Todo, 0),
nextID: 1,
}

// 添加一些初始数据
service.todos = append(service.todos, &models.Todo{
ID: service.nextID,
Title: "Learn Wails",
Completed: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
service.nextID++

service.todos = append(service.todos, &models.Todo{
ID: service.nextID,
Title: "Build a desktop app",
Completed: false,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
})
service.nextID++

return service
}

// GetAllTodos 获取所有待办事项
func (s *TodoService) GetAllTodos() []*models.Todo {
s.mutex.RLock()
defer s.mutex.RUnlock()

// 返回副本以避免外部修改
todos := make([]*models.Todo, len(s.todos))
copy(todos, s.todos)

return todos
}

// CreateTodo 创建新的待办事项
func (s *TodoService) CreateTodo(title string) (*models.Todo, error) {
if title == "" {
return nil, fmt.Errorf("Title cannot be empty")
}

s.mutex.Lock()
defer s.mutex.Unlock()

now := time.Now()
todo := &models.Todo{
ID: s.nextID,
Title: title,
Completed: false,
CreatedAt: now,
UpdatedAt: now,
}

s.todos = append(s.todos, todo)
s.nextID++

return todo, nil
}

// UpdateTodo 更新待办事项
func (s *TodoService) UpdateTodo(id int, title string, completed bool) (*models.Todo, error) {
if title == "" {
return nil, fmt.Errorf("Title cannot be empty")
}

s.mutex.Lock()
defer s.mutex.Unlock()

for i, todo := range s.todos {
if todo.ID == id {
s.todos[i].Title = title
s.todos[i].Completed = completed
s.todos[i].UpdatedAt = time.Now()
return s.todos[i], nil
}
}

return nil, fmt.Errorf("Todo not found")
}

// DeleteTodo 删除待办事项
func (s *TodoService) DeleteTodo(id int) error {
s.mutex.Lock()
defer s.mutex.Unlock()

for i, todo := range s.todos {
if todo.ID == id {
s.todos = append(s.todos[:i], s.todos[i+1:]...)
return nil
}
}

return fmt.Errorf("Todo not found")
}

// ToggleTodoCompletion 切换待办事项完成状态
func (s *TodoService) ToggleTodoCompletion(id int) (*models.Todo, error) {
s.mutex.Lock()
defer s.mutex.Unlock()

for i, todo := range s.todos {
if todo.ID == id {
s.todos[i].Completed = !s.todos[i].Completed
s.todos[i].UpdatedAt = time.Now()
return s.todos[i], nil
}
}

return nil, fmt.Errorf("Todo not found")
}

前端实现

javascript

import { defineStore } from 'pinia';

export const useTodoStore = defineStore('todo', {
state: () => ({
todos: [],
loading: false,
error: null,
}),

getters: {
completedTodos() {
return this.todos.filter(todo => todo.completed);
},

activeTodos() {
return this.todos.filter(todo => !todo.completed);
},

totalCount() {
return this.todos.length;
},

completedCount() {
return this.completedTodos.length;
},

activeCount() {
return this.activeTodos.length;
},
},

actions: {
async fetchTodos() {
this.loading = true;
this.error = null;

try {
this.todos = await window.backend.App.GetAllTodos();
} catch (error) {
this.error = error.message;
console.error('Error fetching todos:', error);
} finally {
this.loading = false;
}
},

async createTodo(title) {
this.loading = true;
this.error = null;

try {
const todo = await window.backend.App.CreateTodo(title);
this.todos.push(todo);
} catch (error) {
this.error = error.message;
console.error('Error creating todo:', error);
} finally {
this.loading = false;
}
},

async updateTodo(id, title, completed) {
this.loading = true;
this.error = null;

try {
const todo = await window.backend.App.UpdateTodo(id, title, completed);
const index = this.todos.findIndex(t => t.id === id);
if (index !== -1) {
this.todos[index] = todo;
}
} catch (error) {
this.error = error.message;
console.error('Error updating todo:', error);
} finally {
this.loading = false;
}
},

async deleteTodo(id) {
this.loading = true;
this.error = null;

try {
await window.backend.App.DeleteTodo(id);
this.todos = this.todos.filter(todo => todo.id !== id);
} catch (error) {
this.error = error.message;
console.error('Error deleting todo:', error);
} finally {
this.loading = false;
}
},

async toggleTodoCompletion(id) {
this.loading = true;
this.error = null;

try {
const todo = await window.backend.App.ToggleTodoCompletion(id);
const index = this.todos.findIndex(t => t.id === id);
if (index !== -1) {
this.todos[index] = todo;
}
} catch (error) {
this.error = error.message;
console.error('Error toggling todo completion:', error);
} finally {
this.loading = false;
}
},
},
});

运行应用

bash

# 在项目根目录下运行开发服务器
wails dev

# 构建生产版本
wails build

案例2:文件管理器

这个案例展示了如何使用Wails构建一个简单的文件管理器,包括文件浏览、文件操作和系统集成等功能。

项目结构

text

file-manager/
├── app.go # 主应用文件
├── models/ # 数据模型
│ └── file_info.go # 文件信息模型
├── services/ # 业务逻辑
│ └── file_service.go # 文件服务
├── build/ # 构建配置和资源
├── frontend/ # 前端源代码
│ ├── src/
│ │ ├── components/
│ │ │ ├── FileList.vue # 文件列表组件
│ │ │ ├── FileItem.vue # 文件项组件
│ │ │ ├── Breadcrumb.vue # 面包屑导航组件
│ │ │ └── Toolbar.vue # 工具栏组件
│ │ ├── stores/
│ │ │ └── fileStore.js # 状态管理
│ │ ├── App.vue
│ │ └── main.js
│ └── ...
├── go.mod
├── go.sum
└── wails.json

后端实现

go

package models

import (
"os"
"time"
)

// FileInfo 文件信息模型
type FileInfo struct {
Name string `json:"name"`
Path string `json:"path"`
IsDir bool `json:"isDir"`
Size int64 `json:"size"`
LastModified time.Time `json:"lastModified"`
Permissions os.FileMode `json:"permissions"`
}

go

package services

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"

"file-manager/models"
)

// FileService 文件服务
type FileService struct {
currentPath string
}

// NewFileService 创建新的文件服务
func NewFileService() *FileService {
// 获取当前工作目录作为初始路径
currentPath, _ := os.Getwd()

return &FileService{
currentPath: currentPath,
}
}

// GetCurrentPath 获取当前路径
func (s *FileService) GetCurrentPath() string {
return s.currentPath
}

// SetCurrentPath 设置当前路径
func (s *FileService) SetCurrentPath(path string) error {
// 检查路径是否存在
info, err := os.Stat(path)
if err != nil {
return err
}

// 检查是否是目录
if !info.IsDir() {
return fmt.Errorf("Path is not a directory")
}

s.currentPath = path
return nil
}

// ListFiles 列出当前目录的文件和文件夹
func (s *FileService) ListFiles() ([]*models.FileInfo, error) {
entries, err := os.ReadDir(s.currentPath)
if err != nil {
return nil, err
}

var files []*models.FileInfo

for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}

file := &models.FileInfo{
Name: entry.Name(),
Path: filepath.Join(s.currentPath, entry.Name()),
IsDir: entry.IsDir(),
Size: info.Size(),
LastModified: info.ModTime(),
Permissions: info.Mode(),
}

files = append(files, file)
}

// 排序:目录在前,文件在后,按名称排序
sort.Slice(files, func(i, j int) bool {
if files[i].IsDir && !files[j].IsDir {
return true
}
if !files[i].IsDir && files[j].IsDir {
return false
}
return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name)
})

return files, nil
}

运行应用

bash

# 在项目根目录下运行开发服务器
wails dev

# 构建生产版本
wails build

lightbulb
9. Wails的最佳实践和优化建议

为了充分发挥Wails的潜力,构建高性能、高质量的桌面应用,本节将介绍一些最佳实践和优化建议。

项目结构最佳实践

模块化设计

将应用功能划分为不同的模块,每个模块负责特定的功能。这有助于代码的组织和维护。

text

myapp/
├── app.go # 主应用文件
├── modules/ # 功能模块
│ ├── auth/ # 认证模块
│ │ ├── auth.go # 认证逻辑
│ │ └── models.go # 认证相关模型
│ ├── database/ # 数据库模块
│ │ ├── db.go # 数据库连接
│ │ └── models.go # 数据模型
│ └── services/ # 业务服务
│ ├── user_service.go
│ └── product_service.go
├── shared/ # 共享代码
│ ├── utils/ # 工具函数
│ └── constants/ # 常量定义
├── frontend/ # 前端代码
└── build/ # 构建配置

性能优化建议

减少前后端通信频率

前后端通信是Wails应用中的潜在性能瓶颈。以下是一些减少通信频率的建议:

warning
注意

1. 批量操作:将多个小操作合并为一个大操作,减少通信次数。

2. 数据缓存:在前端缓存常用数据,避免重复请求。

3. 事件驱动更新:使用事件机制通知前端数据变化,而不是轮询。

go

// 不推荐:多次调用
func (a *App) UpdateUserPreferences(userID int, preferences map[string]interface{}) error {
for key, value := range preferences {
err := a.userService.UpdatePreference(userID, key, value)
if err != nil {
return err
}
}
return nil
}

// 推荐:批量更新
func (a *App) UpdateUserPreferences(userID int, preferences map[string]interface{}) error {
return a.userService.UpdatePreferences(userID, preferences)
}

安全最佳实践

输入验证

始终验证用户输入,防止安全漏洞。

go

// 后端:验证用户输入
func (a *App) CreateUser(userData map[string]interface{}) (*models.User, error) {
// 验证用户名
username, ok := userData["username"].(string)
if !ok || username == "" {
return nil, fmt.Errorf("username is required and must be a string")
}

// 验证用户名长度
if len(username) < 3 || len(username) > 20 {
return nil, fmt.Errorf("username must be between 3 and 20 characters")
}

// 验证用户名格式
if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(username) {
return nil, fmt.Errorf("username can only contain letters, numbers, and underscores")
}

// 验证邮箱
email, ok := userData["email"].(string)
if !ok || email == "" {
return nil, fmt.Errorf("email is required and must be a string")
}

// 验证邮箱格式
if !regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`).MatchString(email) {
return nil, fmt.Errorf("invalid email format")
}

// 创建用户
user, err := a.userService.CreateUser(username, email)
if err != nil {
return nil, fmt.Errorf("failed to create user: %v", err)
}

return user, nil
}

跨平台兼容性

处理平台差异

不同操作系统可能有不同的行为和限制,需要妥善处理这些差异。

go

// 处理不同操作系统的路径分隔符
func (a *App) JoinPath(elements ...string) string {
return filepath.Join(elements...)
}

// 处理不同操作系统的换行符
func (a *App) GetLineBreak() string {
if runtime.GOOS == "windows" {
return "\r\n"
}
return "\n"
}

调试和日志记录

使用日志记录

添加适当的日志记录,帮助调试和监控应用行为。

go

package main

import (
"context"
"log"
"os"

"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
)

// App 应用结构体
type App struct {
ctx context.Context
logger *log.Logger
}

// NewApp 创建新的应用实例
func NewApp() *App {
// 创建日志记录器
logger := log.New(os.Stdout, "APP: ", log.LstdFlags|log.Lshortfile)

return &App{
logger: logger,
}
}

// OnStartup 应用启动时调用
func (a *App) OnStartup(ctx context.Context) {
a.ctx = ctx
a.logger.Println("Application starting up")
}

// ProcessData 处理数据
func (a *App) ProcessData(data map[string]interface{}) (map[string]interface{}, error) {
a.logger.Printf("Processing data: %+v", data)

// 处理数据...

a.logger.Println("Data processed successfully")

return result, nil
}

tips_and_updates
总结

Wails是一个强大的桌面应用开发框架,它结合了Go语言的性能和Web技术的灵活性,为开发者提供了一种构建跨平台桌面应用的高效方式。

通过本教程,我们深入了解了Wails的原理、架构和设计思想,学习了如何搭建开发环境、构建项目结构、实现核心功能,并通过实战案例掌握了Wails的实际应用。同时,我们还探讨了Wails的最佳实践和优化建议,帮助开发者构建高性能、高质量的桌面应用。

Wails的主要优势包括:

轻量级:相比Electron,Wails应用体积更小,内存占用更低。

高性能:利用Go语言的性能优势和系统原生渲染引擎,提供接近原生应用的性能。

跨平台:支持Windows、macOS和Linux三大主流操作系统。

开发效率:结合Go和Web技术,开发者可以使用熟悉的技术栈快速构建应用。

原生集成:提供原生UI元素和系统集成能力,使应用能够与操作系统无缝集成。

随着桌面应用开发需求的不断增长,Wails作为一个新兴的框架,正在吸引越来越多的开发者和企业的关注。通过掌握Wails的开发技能,开发者可以在桌面应用开发领域获得更多的机会和竞争优势。

© 2025 Wails开发详尽教程 | 原理、架构与设计思想深度解析

arrow_back
第 1 页
arrow_forward
第 2 页

❤️ 1
✨

Wails确实是目前最佳的多端App开发框架(Go语言)!

✅ 1
合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens