ChatView ๆฏไธไธช็ซฏๅฐ็ซฏ็ๅณๆถ้่ฎฏๅบ็จ๏ผไฝฟ็จ Ed25519 ๅ ฌ้ฅไฝไธบ็จๆทๆ ่ฏใ้กน็ฎๅไธบไธ้จๅ๏ผ
- Server โ gRPC ๆๅก็ซฏ๏ผGo ๅฎ็ฐ๏ผPostgreSQL ๅญๅจ
- Client โ ๆก้ขๅฎขๆท็ซฏ๏ผGo + Fyne GUI๏ผๆฌๅฐ SQLite ็ผๅญ
- Proto โ protobuf ๅ่ฎฎๅฎไน๏ผgRPC ้ไฟกๅ่ฎฎ
| ็นๆง | ๅฎ็ฐๆนๅผ |
|---|---|
| ็จๆทๆ ่ฏ | Ed25519 ๅ ฌ้ฅ |
| ่ฎค่ฏ | Challenge-Response ็ญพๅ๏ผ็ง้ฅไธ็ฆปๅผๅฎขๆท็ซฏ๏ผ |
| ้ไฟก | gRPC + protobuf |
| ๆๅก็ซฏๅญๅจ | PostgreSQL |
| ๅฎขๆท็ซฏ็ผๅญ | SQLite |
| GUI | Fyne (Go ๅ็่ทจๅนณๅฐ UI ๅบ) |
ๆไพ่ตๅ ณ็ณปไปๅบๅฑๅฐไธๅฑ๏ผๅปบ่ฎฎๆไปฅไธ้กบๅบ้ ่ฏป๏ผ
| ้กบๅบ | ๆไปถ | ่ฏดๆ |
|---|---|---|
| 1 | proto/chatview/common/types.proto |
ๆไธพๅฎไน + ้็จๆถๆฏไฝ๏ผFriendInfoใMessageใAdminStats๏ผ |
| 2 | proto/chatview/auth.proto |
่ฎค่ฏๅ่ฎฎ๏ผChallenge-Response ๆต็จ |
| 3 | proto/chatview/chat.proto |
่ๅคฉๅ่ฎฎ๏ผๅฅฝๅใๆถๆฏใๅๅฒใๅทฒ่ฏป |
| 4 | proto/chatview/events.proto |
ไบไปถๆต๏ผๆฐๆถๆฏใๅฅฝๅ็ถๆใๅนฟๆญใๅผบๅถไธ็บฟ |
| 5 | proto/chatview/admin.proto |
็ฎก็ๅๅ่ฎฎ๏ผๅฐ็ฆ/่งฃๅฐใๅนฟๆญใ้ขๆฟ่ฝฎ่ฏข |
| ้กบๅบ | ๆไปถ | ่ฏดๆ |
|---|---|---|
| 6 | server/internal/contextx/auth.go |
Context ๆณจๅ ฅ Principal๏ผ25 ่ก๏ผๆ็ฎ๏ผ |
| 7 | server/internal/db/db.go |
PostgreSQL ่ฟๆฅใไบๅกใ่ฟ็งปใ่ฏญไนๅ้่ฏฏๅ็ฑป |
| 8 | server/internal/eventhub/hub.go |
ๅๅธ/่ฎข้ ไบไปถๆป็บฟ๏ผ110 ่ก๏ผ |
| 9 | server/internal/interceptor/auth.go |
่ฎค่ฏๆฆๆชๅจ๏ผไป metadata ๆๅ token ้ช่ฏ |
| 10 | server/internal/interceptor/admin.go |
็ฎก็ๅๆ้ๆฆๆชๅจ |
| 11 | server/internal/service/auth.go |
่ฎค่ฏๆๅก๏ผRequestChallenge + Login |
| 12 | server/internal/service/chat_send.go |
ๆถๆฏๅ้๏ผๅน็ญ + ไธฒ่กๅ้่ฏ |
| 13 | server/internal/service/chat_history.go |
ๆถๆฏๅๅฒ๏ผๆธธๆ ๅ้กต |
| 14 | server/internal/service/events.go |
ไบไปถๆต่ฎข้ |
| 15 | server/cmd/server/main.go |
ๅ ฅๅฃ๏ผ็ป่ฃ ๅฏๅจ |
| ้กบๅบ | ๆไปถ | ่ฏดๆ |
|---|---|---|
| 16 | client/internal/domain/types.go |
ๅฎขๆท็ซฏๆฐๆฎ็ฑปๅ๏ผLoginResultใMessageใEvent ็ญ๏ผ |
| 17 | client/internal/core/ports.go |
ๆฅๅฃๅฎไน๏ผauthPort/chatPort/eventPort/adminPort |
| 18 | client/internal/identity/store.go |
ๆฌๅฐ Ed25519 ๅฏ้ฅ็ฎก็๏ผPIN ๅ ๅฏๅญๅจ๏ผ |
| 19 | client/internal/core/auth.go |
็ปๅฝ็ผๆ๏ผๅจ็บฟ/็ฆป็บฟๅ่ทฏๅพ |
| 20 | client/internal/core/messages.go |
ๆถๆฏๅ้๏ผๅจ็บฟ็ดๅ vs outbox ๅผๆญฅ |
| 21 | client/internal/core/outbox.go |
็ฆป็บฟๆถๆฏ้ๅ๏ผworker ้่ฏๆบๅถ |
| 22 | client/internal/core/history.go |
ๆถๆฏๅๅฒ + ๅๆญฅ |
| 23 | client/internal/core/service.go |
Service ็ปๆไฝ๏ผไพ่ต็ป่ฃ |
| ้กบๅบ | ๆไปถ | ่ฏดๆ |
|---|---|---|
| 24 | client/internal/rpcclient/client.go |
gRPC ่ฟๆฅ็ฎก็ |
| 25 | client/internal/rpcclient/auth.go |
่ฎค่ฏ RPC ๆ ๅฐ |
| 26 | client/internal/rpcclient/chat.go |
่ๅคฉ RPC ๆ ๅฐ |
| 27 | client/internal/rpcclient/events.go |
ไบไปถๆต RPC ๆ ๅฐ |
| 28 | client/internal/storage/store.go |
SQLite ็ผๅญ๏ผๅฅฝๅใๆถๆฏใoutbox๏ผ |
| 29 | client/internal/storage/migrations.go |
SQLite schema ่ฟ็งป |
| ้กบๅบ | ๆไปถ | ่ฏดๆ |
|---|---|---|
| 30 | client/internal/ui/app.go |
Application ็ปๆไฝ + Run ๅ ฅๅฃ |
| 31 | client/internal/ui/auth_view.go |
็ปๅฝ/ๆณจๅ/ๅฏผๅ ฅ UI |
| 32 | client/internal/ui/shell_view.go |
ไธป็้ขๅธๅฑ๏ผไพง่พนๆ + ่ๅคฉ้ขๆฟ |
| 33 | client/internal/ui/sync.go |
ๆฐๆฎๅๆญฅ๏ผๅฅฝๅๅทๆฐใๆถๆฏๅ ่ฝฝใๅ้ |
| 34 | client/internal/ui/state.go |
็ถๆ็ฎก็๏ผๆถๆฏๅๅนถใsession ็ๅฝๅจๆ |
| 35 | client/internal/ui/async.go |
็ปๆๅๅนถๅ๏ผsessionSnapshot + ๆณๅไปปๅกๆจกๆฟ |
| 36 | client/internal/ui/events.go |
ๅฎๆถไบไปถๅค็ + ่ชๅจ้่ฟ |
| 37 | client/internal/ui/components.go |
ๅฏๅค็จ UI ็ปไปถ |
โโ UI: auth_view.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ็จๆท่พๅ
ฅ PIN โ Application.login(pin) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโ Core: auth.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Service.Login(ctx, pin) โ
โ 1. identity.Load(pin) โ ่งฃๅฏๆฌๅฐ Ed25519 ็ง้ฅ โ
โ 2. rpc.Login(ctx, pubKey, sign) โ
โ โโ ๅจ็บฟๆๅ โ setSession(pubKey, role, offline=false) โ
โ โโ ็ฝ็ปๆ
้ โ setSession(pubKey, 0, offline=true) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโ RPC: rpcclient/auth.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Client.Login() โ
โ 1. RequestChallenge(publicKey) โ ่ทๅพ้ๆบ challenge โ
โ 2. ed25519.Sign(privateKey, challenge) โ
โ 3. Login(publicKey, signature) โ ่ทๅพ sessionToken โ
โ 4. ไฟๅญ token ๅฐ client metadata โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ gRPC
โโ Server: service/auth.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ AuthService.Login() โ
โ 1. ้ช่ฏ Ed25519 ็ญพๅ โ
โ 2. ๆฃๆฅ็จๆท็ถๆ๏ผๆฏๅฆ่ขซๅฐ็ฆ๏ผ โ
โ 3. ็ๆ session token โ ๅๅ
ฅ sessions ่กจ โ
โ 4. ่ฟๅ sessionToken + role + publicKey โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโ UI: sync.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application.sendMessage() โ
โ 1. ๆธ
็ฉบ่พๅ
ฅๆก โ
โ 2. core.Service.SendMessage(ctx, receiver, text) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโ Core: messages.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Service.SendMessage() โ
โ 1. ็ๆ clientMessageID๏ผUUIDv7๏ผๅน็ญ้ฎ๏ผ โ
โ 2. ๅๅปบ pending ็ถๆ message โ
โ 3. cache == nil๏ผ โ
โ โโ ๆฏ โ ็ดๆฅ rpc.SendMessageWithID() โ
โ โโ ๅฆ โ cache.EnqueueOutbox() + sendOutboxItem() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ gRPC
โโ Server: service/chat_send.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ChatService.SendMessage() โ
โ 1. ไธฒ่กๅ้่ฏ๏ผๆๅค 3 ๆฌก๏ผ โ
โ 2. clientMessageID ๅป้ๆฃๆฅ โ
โ 3. ้ช่ฏๆฅๆถ่
ๅญๅจ โ
โ 4. ไบๅกๅ
๏ผEnsureConversation โ next_seq++ โ INSERT message โ
โ 5. ไบไปถๆจ้ โ Hub.Push(receiver, NewMessageEvent) โ
โ 6. ่ฟๅ messageId + serverSeq โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโ UI: events.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Application.watchEvents() โ
โ 1. core.Service.Subscribe(ctx) โ
โ 2. ๅพช็ฏ่ฏปๅ event channel โ
โ 3. ๆ นๆฎ event.Kind ๅๅ๏ผ โ
โ new_message โ syncConversation + refreshFriends โ
โ friend_status โ refreshFriends โ
โ system_broadcast โ notify โ
โ force_offline โ logout + showAuth โ
โ admin_update โ refreshAdmin โ
โ 4. ่ฟๆฅๆญๅผๆถ exponential backoff ่ชๅจ้่ฟ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโ Server: eventhub/hub.go โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Hub โ
โ Register(pubKey, clientID, chan) โ
โ Unregister(pubKey, clientID) โ
โ Push(pubKey, event) โ ๅนฟๆญ็ปๆๅฎ็จๆท็ๆๆ่ฟๆฅ โ
โ Broadcast(event) โ ๅนฟๆญ็ปๆๆๅจ็บฟ็จๆท โ
โ PushAdmins(ctx, store, e) โ ไป
ๅนฟๆญ็ป็ฎก็ๅ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโ
โ Proto โ ๅ่ฎฎๅฎไน
โ ๅฎไนๆไปถ โ (5 files)
โโโโโโฌโโโโโโ
โ protoc ็ๆ
โโโโโโผโโโโโโ
โ api/gen/ โ ็ๆไปฃ็
โ (ๆ้ค) โ (9 files)
โโโโโโฌโโโโโโ
โ import
โโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโ
โ โ โ
โโโโโโโผโโโโโโ โโโโโโโผโโโโโโ โ
โ Server โ โ Client โ โ
โ gRPC ๆๅก โโโโค Fyne ๆก้ข โ โ
โโโโโโโฌโโโโโโ โโโโโโโฌโโโโโโ โ
โ โ โ
โโโโโโโผโโโโโโ โโโโโโโผโโโโโโ โ
โPostgreSQL โ โ SQLite โ โ
โ ๆๅก็ซฏๅญๅจ โ โ ๆฌๅฐ็ผๅญ โ โ
โโโโโโโโโโโโโ โโโโโโโโโโโโโ โ
- ็จๆทๅๅปบ่บซไปฝๆถ็ๆ Ed25519 ๅฏ้ฅๅฏน
- ็ง้ฅ็จ PIN + NaCl secretbox ๅ ๅฏๅญๅจๅจๆฌๅฐ
- ็ปๅฝๆถๅฎขๆท็ซฏ็จ็ง้ฅ็ญพๅๆๅก็ซฏ็้ๆบ challenge
- ๆๅก็ซฏ็จๅ ฌ้ฅ้ช่ฏ็ญพๅ๏ผ็ง้ฅไธ็ฆปๅผๅฎขๆท็ซฏ
- ไฝ็ฝฎ๏ผ
client/internal/identity/store.goใserver/internal/service/auth.go
- ๅฎขๆท็ซฏๅ้ๆถๆฏๆถ็ๆ
clientMessageID๏ผUUIDv7๏ผ - ๆๅก็ซฏๆถๅฐๅๆฃๆฅๆญค ID ๆฏๅฆๅทฒๅๅ ฅ๏ผๅทฒๅๅ็ดๆฅ่ฟๅๅๅงๅๅบ
- ้ ๅ serialization failure ่ชๅจ้่ฏ๏ผไฟ่ฏ exactly-once ่ฏญไน
- ไฝ็ฝฎ๏ผ
server/internal/service/chat_send.goใserver/internal/service/chat_dedupe.go
- ๅฎขๆท็ซฏๅ้ๆถๆฏๆถๅๆถๅๅ ฅๆฌๅฐ SQLite outbox ่กจ
- ็ฝ็ปไธ้ๆถๆถๆฏๅ็ๅจ pending ็ถๆ
- ๅๅฐ worker ๅฎๆ้่ฏ๏ผๆๅๅๆดๆฐๆฌๅฐ็ถๆไธบ sent
- ไฝ็ฝฎ๏ผ
client/internal/core/outbox.goใclient/internal/storage/outbox.go
- ๆๅก็ซฏ Hub ็ปดๆค
pubKey โ [client channel]ๆ ๅฐ - ๆถๆฏๅ้ๆๅๅ๏ผๆๅก็ซฏ Push ไบไปถๅฐๆฅๆถ่ ็ๆๆ่ฟๆฅ
- ๅฎขๆท็ซฏ้่ฟ gRPC server-streaming ่ฎข้ ไบไปถๆต
- ไฝ็ฝฎ๏ผ
server/internal/eventhub/hub.goใclient/internal/ui/events.go
้กน็ฎไฝฟ็จ Go context ๆๅปบ goroutine ็ๅฝๅจๆๆ ๏ผๅฎ็ฐ็บง่ๅๆถโโๅๆถๆ น่็น่ชๅจไผ ๆญๅฐๆๆๅญ goroutine๏ผไธไผๆณๆผใ
Session Context๏ผไผ่ฏๆ น่็น๏ผ
client/internal/ui/state.go:66-84 โ ๆฏๆฌก็ปๅฝๅๅปบๆฐ็ context.WithCancel๏ผ็ปๅบๆถๅๆถ๏ผ
func (a *Application) startSessionContext() {
a.sessionCtx, a.sessionCancel = context.WithCancel(context.Background())
}
func (a *Application) stopSessionContext() {
a.sessionCancel() // โ ่ฟไธ่กไผ็บง่ๅๆถๆๆๅญ goroutine
}SessionSnapshot โ ๅนถๅๅฎๅ จ็ไธไธๆๅฟซ็ ง
client/internal/ui/async.go โ goroutine ๅฏๅจๆถๆ่ทๅฝๅ session ็ context + ็ๆฌๅท๏ผ
type sessionSnapshot struct {
ctx context.Context
version int
}
func runSessionTask[T any](a *Application, work func(context.Context) (T, error), apply func(T, error)) {
snapshot := a.currentSessionSnapshot() // ๆ่ทๅฟซ็
ง
go func() {
result, err := work(snapshot.ctx) // ๅๅฐๅทฅไฝๆ็ฅ ctx ๅๆถ
a.doInSession(snapshot, func() { // ๅๅฐ UI ็บฟ็จๆถไบๆฌกๆ ก้ช
apply(result, err)
})
}()
}doInSession ๅ้ไฟๆค๏ผๅ
ๆฃๆฅ ctx.Err()๏ผcontext ๆฏๅฆๅทฒๅๆถ๏ผ๏ผๅๆฃๆฅ sessionVersion๏ผๆฏๅฆๅทฒ็ปๅบๅ็ปๅ
ฅ๏ผใ
goroutine ๆ ็ปๆ
startSessionContext() โ ๆ น context
โ
โโ startOutboxWorker(ctx) โ ctx, cancel = WithCancel(parent)
โ โโ processOutbox(ctx) โ ctx.Done()
โ
โโ watchEvents(ctx) โ ctx, cancel = WithCancel(sessionCtx)
โ โโ select { event, err, ctx.Done() }
โ
โโ runSessionTask(snapshot) โ snapshot.ctx = sessionCtx
โ โโ work(snapshot.ctx) โ ctx ๅๆถๅๆๅ่ฟๅ
โ
โโ startOutboxPoller(ctx) โ ctx, cancel = WithCancel(sessionCtx)
โโ ticker โ ctx.Done()
ๆๅก็ซฏๅ็
server/cmd/server/main.go โ OS ไฟกๅท่ฝฌไธบ context๏ผ
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
go store.RunCleanup(ctx, cfg.CleanupInterval) // ๅๅฐๆธ
็ๆ็ฅ ctx
go eventhub.RunPresenceHealer(ctx, store, hub, ...) // ๅจ็บฟไฟฎๅคๆ็ฅ ctx
// Ctrl+C โ stop() โ ctx.Done() โ ๆๆๅๅฐ goroutine ไผ้
้ๅบ่ฎพ่ฎก่ฆ็น
| ๆบๅถ | ไฝ็จ | ไฝ็ฝฎ |
|---|---|---|
context.WithCancel |
ๅๅปบๅฏๅๆถๅญๆ | startOutboxWorkerใwatchEventsใstartOutboxPoller |
ctx.Done() |
goroutine ้ๅบไฟกๅท | ๆๆๅๅฐ select ่ฏญๅฅ |
sessionSnapshot |
ๆ่ท context + version๏ผ้ฒๆญข็ซๆ | async.go |
doInSession |
UI ๅ่ฐๅไบๆฌก็กฎ่ฎค session ๆๆๆง | async.go:27-31 |
sessionVersion |
ๆดๆฐ็ๆฌๅท๏ผcontext ไนๅค็ๅ ๅบไฟๆค | state.go |
# PostgreSQL๏ผๆๅก็ซฏ้่ฆ๏ผ
# Go 1.21+
# protoc๏ผๅฏ้๏ผไป
ไฟฎๆน proto ๆถ้่ฆ๏ผcd server
# ๅๅปบๆฐๆฎๅบ
createdb chatview
# ๅฏๅจ๏ผไฝฟ็จ็คบไพ้
็ฝฎ๏ผ
go run ./cmd/server -config config.example.yamlcd client
# ๅฏๅจ๏ผ่ฟๆฅๆฌๅฐๆๅก็ซฏ๏ผ
go run ./cmd/client
# Wayland ไธๅปบ่ฎฎ
go run -tags wayland ./cmd/clientcd server && go test ./...
cd client && go test ./..../scripts/gen-proto่ฏฆๆ ่ง PROTO_GUIDE.mdใ
chatview/
โโโ proto/ # protobuf ๅ่ฎฎๅฎไน
โ โโโ chatview/
โ โโโ common/types.proto
โ โโโ auth.proto
โ โโโ chat.proto
โ โโโ events.proto
โ โโโ admin.proto
โโโ api/ # Go module๏ผ็ๆ + ๆๅ๏ผ
โ โโโ go.mod
โ โโโ gen/chatview/ # protoc ็ๆไปฃ็ ๏ผไธๆๅจไฟฎๆน๏ผ
โโโ server/
โ โโโ cmd/server/main.go # ๆๅก็ซฏๅ
ฅๅฃ
โ โโโ config.example.yaml
โ โโโ configuration.md
โ โโโ migrations/ # PostgreSQL DDL ่ฟ็งป
โ โโโ internal/
โ โโโ auth/ # Ed25519 ๅ ๅฏๅทฅๅ
ท
โ โโโ config/ # ้
็ฝฎๅ ่ฝฝ
โ โโโ contextx/ # Context Principal ๆณจๅ
ฅ
โ โโโ db/ # PostgreSQL ๆฐๆฎๅฑ
โ โโโ eventhub/ # ไบไปถๆป็บฟ
โ โโโ interceptor/ # gRPC ๆฆๆชๅจ๏ผ่ฎค่ฏ/ๆ้/ๆฅๅฟ๏ผ
โ โโโ service/ # gRPC ๆๅกๅฎ็ฐ
โโโ client/
โ โโโ cmd/client/main.go # ๅฎขๆท็ซฏๅ
ฅๅฃ
โ โโโ client.example.yaml
โ โโโ configuration.md
โ โโโ internal/
โ โโโ config/ # ้
็ฝฎๅ ่ฝฝ
โ โโโ core/ # ไธๅก็ผๆ๏ผports ๆฅๅฃ + ๅฎ็ฐ๏ผ
โ โโโ domain/ # ็บฏๆฐๆฎ็ฑปๅ
โ โโโ identity/ # Ed25519 ๅฏ้ฅ็ฎก็
โ โโโ rpcclient/ # gRPC ไผ ่พ้้
โ โโโ storage/ # SQLite ๆฌๅฐ็ผๅญ
โ โโโ ui/ # Fyne ๆก้ข UI
โโโ scripts/gen-proto # Proto ไปฃ็ ็ๆ่ๆฌ
โโโ tools/protoc-*/ # protoc ๅทฅๅ
ท้พ