桌面应用正在被重写:我用 Go + Wails 做了一个聊天软件后,彻底理解它为什么会火(含源码)

1,177 阅读5分钟

昨天第一次真正上手 Wails 时,那种“这也太自由了吧”的震惊感至今还没消退——
可配置的灵活度、极简的项目结构、前后端天然分离的开发模式、前端(Vue)热更新直接作用于桌面窗口、完全不需要安装浏览器内核、打包结果极轻,总总迹象,让我一下子意识到:
“桌面应用的开发方式,真的要被改写了。”

带着这种好奇和兴奋,今天我忍不住又继续开工,想再体验一次它带来的“高效节奏”。
而最能同时检验界面能力、通信流程和数据管理的项目,莫过于一个聊天应用。
于是我决定:用 Wails 做一个简单的桌面聊天软件,看看它到底能走到什么程度。

结果是:

它不仅好用,而且再次刷新了我对桌面开发的认知。

Wails 正在悄悄改变桌面应用的开发模式。
只需 Go + 前端框架(Vue/React) ,就能做出现代化、多平台、高性能的桌面应用。

今天我用 Wails + Vue3 搭建了一个完整的聊天应用(含聊天室切换、表情/图片发送、本地 SQLite 存储)。

本文从学习的角度深入解析项目结构、前后端通信、数据模型、UI 实现方式,以及为什么 Wails 会成为未来桌面开发的热门框架之一。

文中的wails的安装和项目创建这里不再赘述,首次接触它的可以查看上一篇文章Wails + Vue3 构建现代桌面 UI 框架——基于 Go 的跨平台桌面应用实战(含源码案例)


🧱项目结构:Wails 如何将 Go + 前端“自然融合”?

Wails 项目的目录结构非常清晰:

    chat-app/
    ├─ backend/          # Go 后端
    │  ├─ chat.go
    │  ├─ chat_ws.go
    │  ├─ repository.go
    │  ├─ file.go
    │  └─ models.go
    ├─ frontend/         # Vue3 前端
    │  └─ src/
    │     ├─ views/Chat.vue
    │     ├─ components/MessageBubble.vue
    │     ├─ store/chat.ts
    │     └─ styles/
    └─ wails.json        # 项目配置
    └─ main.go

这结构非常典型(各司其职,清晰明了):
前端用 Vue 展示 UI,后端用 Go 做所有逻辑与数据。

而 Wails 提供了关键能力:

自动生成前端可调用的 Go API(TypeScript SDK)

你写好一个 Go 方法:

func (c *ChatService) SendMessage(roomID string, msg string) error {
    // store message
}

前端就能直接使用:

import { SendMessage } from '@/wailsjs/go/chat/ChatService'

SendMessage("room1", "Hello")

这是我认为 Wails 最大的亮点之一——
无需写任何桥接代码。


🔌双向通信:Wails 的事件模型比我想象的更强

聊天软件最关键的是“消息实时更新”。
Wails 的做法非常优雅:

后端广播事件:

    runtime.EventsEmit(c.ctx, "new_message", msg)

前端监听事件

EventsOn("new_message", (msg: Message) => {
  chatStore.addMsg(msg)
})

这比 Electron 的 IPC 简单太多,也比 WebSocket 客户端轮询自然太多。
前端完全事件驱动,极其流畅。


🗄️ SQLite 本地存储:离线聊天记录也能秒加载

项目中我添加了一个本地数据库:

func (r *Repository) SaveMessage(m *Message) error {
    _, err := r.db.Exec(`
        INSERT INTO messages (room_id, sender, type, content, created_at)
        VALUES (?, ?, ?, ?, ?)
    `, m.RoomID, m.Sender, m.Type, m.Content, m.CreatedAt)
    return err
}

加载历史消息:

func (r *Repository) GetMessages(roomID string) ([]Message, error) {
    rows, err := r.db.Query(`
        SELECT sender, type, content, created_at
        FROM messages WHERE room_id = ?
        ORDER BY created_at ASC
    `, roomID)
    ...
}

前端只需调用(这样的调用,前端开发省掉了太多的事,它就像在跟你说你只管业务逻辑,其他的都交给我!这不就是我们追求的吗):

    LoadHistory(roomID).then(msgs => store.setHistory(roomID, msgs))

结果是:

  • 启动应用 → 聊天记录秒级加载
  • 多聊天室数据完全隔离
  • 图片/表情消息一样能储存

这是纯 Go + SQLite 的天然优势:轻、快、稳定。小应用的不二选择。


🎨UI 解析:前端生态在桌面端的全力释放

作为后端开发,我们要避开短板,选用一些成熟的样式框架,减少繁杂的css编写和调试。我采用 Vue3 + TailwindCSS + Pinia
消息气泡部分(Chat.vue)示例:

<div ref="msgContainer" class="flex-1 overflow-auto p-6 bg-gradient-to-b from-white to-gray-50">
  <div v-for="m in messages" :key="m.id" class="mb-4 flex" :class="m.username===username ? 'justify-end' : 'justify-start'">
    <div :class="['max-w-[60%] p-4 rounded-2xl shadow-md', m.username===username ? 'bg-gradient-to-r from-indigo-500 to-indigo-600 text-white' : 'bg-white border border-gray-200 shadow-sm']">
      <div class="flex items-center justify-between">
        <div class="text-sm font-medium">{{ m.username }}</div>
        <div class="text-xs text-gray-200" v-if="m.username===username">{{ formatTime(m.timestamp) }}</div>
      </div>
      <div class="mt-2">
        <template v-if="m.type==='text'">
          <div class="whitespace-pre-wrap">{{ m.content }}</div>
        </template>
        <template v-else-if="m.type==='emoji'">
          <div class="text-2xl">{{ m.content }}</div>
        </template>
        <template v-else-if="m.type==='image'">
          <img :src="m.content" class="w-48 h-48 object-cover rounded cursor-pointer" @click="viewImage(m.content)" />
        </template>
        <template v-else-if="m.type==='recall'">
          <div class="italic text-sm text-gray-400">消息已撤回</div>
        </template>
      </div>
      <div class="text-xs text-gray-400 mt-1" v-if="m.username!==username">{{ formatTime(m.timestamp) }}</div>
    </div>
  </div>
</div>

Vue 在桌面端的表现令人惊喜:

  • 现代、漂亮的 UI 不再困难
  • 热更新开发体验接近 Vite + 浏览器
  • 任何前端 UI 库都能直接用

这就是为什么我说——

Wails 并不是“桌面界面框架”,而是“让前端生态进入桌面世界”。


🖼️发送图片 & 表情:Wails 让文件处理变得非常直接

前端读取本地文件:

function selectImage() {
  const input = document.createElement('input')
  input.type = 'file'
  input.accept = 'image/*'
  input.onchange = () => {
    if (input.files && input.files[0]) {
        const file = input.files[0]
        // 这里需要将文件转换为 base64 或者直接上传二进制
        // 示例代码简化了读取过程
        const reader = new FileReader()
        reader.onload = (e) => {
             // 假设 UploadImage 接受 base64 字符串
             if(e.target?.result) {
                 UploadImage(room, e.target.result as string).then(url => SendMessage(room, url))
             }
        }
        reader.readAsDataURL(file)
    }
  }
  input.click()
}

后端接收图片:

func (c *ChatService) UploadImage(roomID string, dataBase64 string) (string, error) {
    // 实际项目中需要解析 base64
    // 这里仅作演示
    filename := fmt.Sprintf("img_%d.png", time.Now().Unix())
    path := filepath.Join(c.DataDir, filename)
    // os.WriteFile(path, decodedData, 0644)
    return filename, nil
}

没有浏览器沙盒限制,没有繁琐权限,桌面端体验极其自由。


🚀一键运行脚本:开发体验极其顺滑

    #!/bin/bash
    go mod tidy
    wails dev

真正做到了:

  1. 克隆项目
  2. 执行脚本
  3. 直接启动桌面 IM

而整个应用打包后只有几十 MB。
对比 Electron 的 100+MB 起步,差距非常明显。

运行效果:

微信图片_2025-11-29_125904_230.png


📌总结:为什么我认为 Wails 会火?

经过这次“聊天应用”实践,我对它的理解非常明确:


✔ 1. 前端界面能力 = 全能力释放(WEB 生态盘活)

你不是在堆原生控件,
你是在写 真正的现代前端界面


✔ 2. Go 后端能力 = 稳、快、强(天然适合桌面)

构建速度快、并发强、跨平台优秀。


✔ 3. 体积小、启动快、无浏览器内核

最终打包体积小得离谱,启动速度秒开。


✔ 4. 简洁的 API(尤其是前后端通信)

IPC 简化到前端能直接调用 Go 方法。


✔ 5. 开发体验优秀(个人/团队都很适合)

前端热更新 + Go 构建速度 = 真香。


📦开源地址(含完整源码)

GitHub:
👉 github.com/louis-xie-p…

Gitee:
👉 gitee.com/louis_xie/c…

很多时候我们不是做不出桌面应用,
而是被历史的技术栈劝退了。

Wails 的出现,让我第一次真正感受到:

“桌面应用也可以像 Web 一样快、像 Go 一样强、像 Electron 一样灵活。”

如果你也是 Go 或前端开发者,
我真心建议你亲手试一次 Wails。

欢迎在评论区留言讨论你的使用体验和感受。

我相信你会非常惊喜。