Skip to content

Latest commit

ย 

History

History
413 lines (338 loc) ยท 19.1 KB

File metadata and controls

413 lines (338 loc) ยท 19.1 KB

ๆ–ฐไบบไปฃ็ ้˜…่ฏปๆŒ‡ๅ—

้กน็›ฎๆฆ‚่ฟฐ

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 ๅบ“)

ๆŽจ่้˜…่ฏป้กบๅบ

ๆŒ‰ไพ่ต–ๅ…ณ็ณปไปŽๅบ•ๅฑ‚ๅˆฐไธŠๅฑ‚๏ผŒๅปบ่ฎฎๆŒ‰ไปฅไธ‹้กบๅบ้˜…่ฏป๏ผš

็ฌฌไธ€้˜ถๆฎต๏ผš็†่งฃๅ่ฎฎๅ’Œๆ•ฐๆฎ็ป“ๆž„๏ผˆ็บฆ 400 ่กŒ๏ผŒ15 ๅˆ†้’Ÿ๏ผ‰

้กบๅบ ๆ–‡ไปถ ่ฏดๆ˜Ž
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 ็ฎก็†ๅ‘˜ๅ่ฎฎ๏ผšๅฐ็ฆ/่งฃๅฐใ€ๅนฟๆ’ญใ€้ขๆฟ่ฝฎ่ฏข

็ฌฌไบŒ้˜ถๆฎต๏ผš็†่งฃๆœๅŠก็ซฏ๏ผˆ็บฆ 800 ่กŒ๏ผŒ30 ๅˆ†้’Ÿ๏ผ‰

้กบๅบ ๆ–‡ไปถ ่ฏดๆ˜Ž
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 ๅ…ฅๅฃ๏ผš็ป„่ฃ…ๅฏๅŠจ

็ฌฌไธ‰้˜ถๆฎต๏ผš็†่งฃๅฎขๆˆท็ซฏๆ ธๅฟƒๅฑ‚๏ผˆ็บฆ 600 ่กŒ๏ผŒ25 ๅˆ†้’Ÿ๏ผ‰

้กบๅบ ๆ–‡ไปถ ่ฏดๆ˜Ž
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 ็ป“ๆž„ไฝ“๏ผšไพ่ต–็ป„่ฃ…

็ฌฌๅ››้˜ถๆฎต๏ผš็†่งฃๅฎขๆˆท็ซฏไผ ่พ“ไธŽๅญ˜ๅ‚จ๏ผˆ็บฆ 400 ่กŒ๏ผŒ15 ๅˆ†้’Ÿ๏ผ‰

้กบๅบ ๆ–‡ไปถ ่ฏดๆ˜Ž
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 ่ฟ็งป

็ฌฌไบ”้˜ถๆฎต๏ผš็†่งฃๅฎขๆˆท็ซฏ UI๏ผˆ็บฆ 600 ่กŒๆ ธๅฟƒ๏ผŒ20 ๅˆ†้’Ÿ๏ผ‰

้กบๅบ ๆ–‡ไปถ ่ฏดๆ˜Ž
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 ่ฎค่ฏ

  • ็”จๆˆทๅˆ›ๅปบ่บซไปฝๆ—ถ็”Ÿๆˆ 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

็ฆป็บฟ Outbox

  • ๅฎขๆˆท็ซฏๅ‘้€ๆถˆๆฏๆ—ถๅŒๆ—ถๅ†™ๅ…ฅๆœฌๅœฐ 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

็ป“ๆž„ๅŒ–ๅนถๅ‘ โ€” Context ๆ ‘

้กน็›ฎไฝฟ็”จ 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.yaml

ๅฏๅŠจๅฎขๆˆท็ซฏ

cd client

# ๅฏๅŠจ๏ผˆ่ฟžๆŽฅๆœฌๅœฐๆœๅŠก็ซฏ๏ผ‰
go run ./cmd/client

# Wayland ไธ‹ๅปบ่ฎฎ
go run -tags wayland ./cmd/client

่ฟ่กŒๆต‹่ฏ•

cd server && go test ./...
cd client && go test ./...

ไฟฎๆ”น Proto ๅŽ้‡ๆ–ฐ็”Ÿๆˆ

./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 ๅทฅๅ…ท้“พ