diff --git a/global/send.go b/global/send.go new file mode 100644 index 0000000..f96882f --- /dev/null +++ b/global/send.go @@ -0,0 +1,9 @@ +package global + +import "work_cation/models" + +type SendGlobal struct { + SendChan chan *models.Message +} + +var Send = &SendGlobal{SendChan: make(chan *models.Message, 1000)} diff --git a/main.go b/main.go index c49c40a..d7db25b 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,11 @@ func main() { // 主界面 func mainView(w fyne.Window) fyne.CanvasObject { service.Zeroconf.StartFindService() + views.ListenChat() + // 启动服务 + if err := service.Server.StartListenServer(); err != nil { + return widget.NewLabel(err.Error()) + } var ( content = container.NewMax() a = fyne.CurrentApp() diff --git a/models/message.go b/models/message.go new file mode 100644 index 0000000..72588b4 --- /dev/null +++ b/models/message.go @@ -0,0 +1,14 @@ +package models + +import "time" + +type ChatMessage struct { + User Users + Time time.Time + Text string +} +type Message struct { + Cmd string + User UserFollows + ChatMessage ChatMessage +} diff --git a/models/userFollow.go b/models/userFollow.go index 996d246..f91c229 100644 --- a/models/userFollow.go +++ b/models/userFollow.go @@ -3,4 +3,5 @@ package models // UserFollows 关注用户 type UserFollows struct { Users + Sort int } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..1f91012 --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,13 @@ +package utils + +import ( + "fmt" + "syscall" + "testing" +) + +func TestP1(t *testing.T) { + text := "test" + data := syscall.StringToUTF16(text) + fmt.Println(data) +} diff --git a/repo/userFollow.go b/repo/userFollow.go index 0f1537f..3eea325 100644 --- a/repo/userFollow.go +++ b/repo/userFollow.go @@ -24,3 +24,11 @@ func (u *userFollowRepo) GetUser(db *gorm.DB, uuid string) *models.UserFollows { } return &user } +func (u *userFollowRepo) UnFollow(db *gorm.DB, user *models.UserFollows) error { + return db.Where("id = ?", user.ID).Delete(user).Error +} +func (u *userFollowRepo) All(db *gorm.DB) ([]models.UserFollows, error) { + var users []models.UserFollows + err := db.Model(&models.UserFollows{}).Find(&users).Error + return users, err +} diff --git a/service/client.go b/service/client.go index fc5fa8a..5a6d12d 100644 --- a/service/client.go +++ b/service/client.go @@ -2,17 +2,21 @@ package service import ( "archive/zip" + "bytes" "context" "encoding/json" "fmt" "fyne.io/fyne/v2/data/binding" + "github.com/gin-gonic/gin" "io" "net/http" "os" "path/filepath" "time" "work_cation/cfg" + "work_cation/global" "work_cation/models" + "work_cation/repo" ) type ClientService struct { @@ -110,6 +114,33 @@ func (c *ClientService) Download(online *models.Online, uuid string, progress bi return nil } +func (c *ClientService) Chat(online *models.Online, text string) (string, error) { + var user = repo.User.GetUserInfo(global.DB) + ctx, carnal := context.WithTimeout(context.TODO(), time.Millisecond*500) + defer carnal() + b, err := json.Marshal(gin.H{"text": text, "uuid": user.ID}) + if err != nil { + return "", err + } + res, err := http.NewRequestWithContext(ctx, "POST", online.Url("/chat"), bytes.NewBuffer(b)) + if err != nil { + return "", err + } + res.Header.Set("Content-Type", "application/json") + res.Header.Set("User-ID", user.ID) + resp, err := http.DefaultClient.Do(res) + if err != nil { + return "", err + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + var msg = make(map[string]any) + if err = json.Unmarshal(data, &msg); err != nil { + return "", err + } + return msg["message"].(string), err +} + type Rr struct { file *os.File tot int64 diff --git a/service/server.go b/service/server.go index 29cdc9b..62f98b5 100644 --- a/service/server.go +++ b/service/server.go @@ -10,8 +10,10 @@ import ( "net/http" "os" "path/filepath" + "time" "work_cation/cfg" "work_cation/global" + "work_cation/models" "work_cation/repo" ) @@ -36,19 +38,15 @@ func (s *serverService) Online() error { if err := Zeroconf.Register(user); err != nil { return err } - // 启动服务 - if err := s.StartListenServer(); err != nil { - return err - } // 修改状态 s.Status = StatusOnline return nil } func (s *serverService) StatusOffline() error { - if err := s.server.Close(); err != nil { - return err - } + //if err := s.server.Close(); err != nil { + // return err + //} // 关闭服务发现 Zeroconf.Close() // 修改状态 @@ -87,6 +85,31 @@ func (s *serverService) StartListenServer() error { } }) + router.POST("/chat", func(c *gin.Context) { + uuid := c.GetHeader("User-ID") + user := repo.UserFollow.GetUser(global.DB, uuid) + if user.Ip != c.ClientIP() { + c.JSON(200, gin.H{"message": "对方未关注你"}) + return + } + var msg = make(map[string]interface{}) + if err := c.ShouldBind(&msg); err != nil { + c.JSON(200, gin.H{"message": "输入异常"}) + return + } + message := &models.Message{ + Cmd: "chat", + User: *user, + ChatMessage: models.ChatMessage{ + User: user.Users, + Time: time.Now(), + Text: msg["text"].(string), + }, + } + global.Send.SendChan <- message + c.JSON(200, gin.H{"message": "ok"}) + }) + srv := &http.Server{ Addr: cfg.T.ServerAddr, Handler: router, diff --git a/service/zeroconf.go b/service/zeroconf.go index baca849..e252f08 100644 --- a/service/zeroconf.go +++ b/service/zeroconf.go @@ -42,6 +42,15 @@ func (s *zeroconfService) FindService() ([]models.Online, error) { return s.onlines, nil } +func (s *zeroconfService) GetOnline(id string) *models.Online { + for _, online := range s.onlines { + if online.ID == id { + return &online + } + } + return nil +} + func (s *zeroconfService) StartFindService() error { resolver, err := zeroconf.NewResolver(nil) if err != nil { diff --git a/views/chatView.go b/views/chatView.go new file mode 100644 index 0000000..df6cd4b --- /dev/null +++ b/views/chatView.go @@ -0,0 +1,125 @@ +package views + +import ( + "fmt" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" + "time" + "work_cation/cfg" + "work_cation/global" + "work_cation/models" + "work_cation/repo" + "work_cation/service" +) + +type ChatView struct { + usersChat map[string]*ChatUserInfo +} + +type ChatUserInfo struct { + models.Users + messages []models.ChatMessage + shows *fyne.Container + scroll *container.Scroll +} + +var chat = &ChatView{usersChat: make(map[string]*ChatUserInfo)} + +func ListenChat() { + go func() { + for { + message := <-global.Send.SendChan + userInfo := chat.usersChat[message.User.ID] + if userInfo == nil { + userInfo = &ChatUserInfo{} + chat.usersChat[message.User.ID] = userInfo + } + userInfo.messages = append(userInfo.messages, message.ChatMessage) + userInfo.Users = message.User.Users + if userInfo.shows == nil { + OpenChat(message.User.Users) + } else { + userInfo.shows.Add(itemMessage(message.ChatMessage)) + userInfo.scroll.ScrollToBottom() + } + } + }() +} + +func OpenChat(user models.Users) { + userInfo := chat.usersChat[user.ID] + if userInfo == nil { + userInfo = &ChatUserInfo{} + chat.usersChat[user.ID] = userInfo + } + if userInfo.shows == nil { + w := fyne.CurrentApp().NewWindow(user.Name) + w.CenterOnScreen() + w.Resize(fyne.NewSize(500, 300)) + list := container.NewVBox() + for _, item := range userInfo.messages { + list.Add(itemMessage(item)) + } + scroll := container.NewScroll(list) + userInfo.scroll = scroll + scroll.ScrollToBottom() + // 发送表单 + en := widget.NewEntry() + formBase := &widget.Form{ + SubmitText: "发送", + OnSubmit: func() { + if en.Text == "" { + return + } + online := service.Zeroconf.GetOnline(user.ID) + if online == nil { + online = &models.Online{ID: user.ID, Ip: user.Ip, Port: cfg.T.ServerAddr} + } + msg, err := service.Client.Chat(online, en.Text) + if err != nil || msg != "ok" { + dialog.ShowInformation("发送失败", err.Error(), w) + return + } + chatItem := models.ChatMessage{ + User: *repo.User.GetUserInfo(global.DB), + Time: time.Now(), + Text: en.Text, + } + userInfo.messages = append(userInfo.messages, chatItem) + list.Add(itemMessage(chatItem)) + en.SetText("") + scroll.ScrollToBottom() + }} + + formBase.AppendItem(&widget.FormItem{Text: "", Widget: en}) + w.SetContent(container.NewBorder(nil, formBase, nil, nil, scroll)) + userInfo.shows = list + w.SetOnClosed(func() { + userInfo.shows = nil + userInfo.scroll = nil + }) + w.Show() + } + userInfo.scroll.ScrollToBottom() +} + +func itemMessage(msg models.ChatMessage) fyne.CanvasObject { + my := repo.User.GetUserInfo(global.DB) + var card *widget.Card + if msg.User.ID != my.ID { + card = widget.NewCard("", + fmt.Sprintf("%s %s", msg.Time.Format(time.DateTime), + msg.User.Name), widget.NewLabel(msg.Text)) + } else { + + title := fmt.Sprintf("%s %s", msg.Time.Format(time.DateTime), "我") + card = widget.NewCard("", "", + container.NewVBox( + widget.NewLabelWithStyle(title, fyne.TextAlignTrailing, fyne.TextStyle{}), + widget.NewLabelWithStyle(msg.Text, fyne.TextAlignTrailing, fyne.TextStyle{}))) + + } + return card +} diff --git a/views/data.go b/views/data.go index a6967f0..61ca4c5 100644 --- a/views/data.go +++ b/views/data.go @@ -17,15 +17,16 @@ type Tutorial struct { var ( // Tutorials 定义每个教程的元数据 Tutorials = map[string]Tutorial{ - "welcome": {"主页", "", mainUserViews, true}, - "canvas": {"我的", "", allCardsViews, true}, - "create": {"新建", "", allCreateCards, true}, - "otherUsers": {"同事", "", otherUser, true}, + "welcome": {"主页", "", mainUserViews, true}, + "canvas": {"我的", "", allCardsViews, true}, + "create": {"新建", "", allCreateCards, true}, + "otherUsers": {"同事", "", otherUser, true}, + "followUsers": {"关注", "", followUsers, true}, } // TutorialIndex 定义我们的教程应该如何在索引树中布局 TutorialIndex = map[string][]string{ - "": {"welcome", "canvas", "otherUsers", "create"}, + "": {"welcome", "canvas", "followUsers", "otherUsers", "create"}, //"collections": {"list", "table", "tree"}, //"containers": {"apptabs", "border", "box", "center", "doctabs", "grid", "scroll", "split"}, //"widgets": {"accordion", "button", "card", "entry", "form", "input", "progress", "text", "toolbar"}, 58 * 3 + 106 = 280 diff --git a/views/followUsers.go b/views/followUsers.go new file mode 100644 index 0000000..5661290 --- /dev/null +++ b/views/followUsers.go @@ -0,0 +1,108 @@ +package views + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" + "slices" + "work_cation/cfg" + "work_cation/global" + "work_cation/models" + "work_cation/repo" + "work_cation/service" +) + +func followUsers(w fyne.Window) fyne.CanvasObject { + gridWrap := container.NewGridWrap(fyne.NewSize(200, 190)) + + findService, err := service.Zeroconf.FindService() + if err != nil { + return widget.NewLabel(err.Error()) + } + users, err := repo.UserFollow.All(global.DB) + if err != nil { + return widget.NewLabel(err.Error()) + } + slices.SortFunc(users, func(a, b models.UserFollows) int { + return b.Sort - a.Sort + }) + for _, user := range users { + index := slices.IndexFunc(findService, indexOnline2User(user.ID)) + var onlineCopy *models.Online + if index != -1 { + onlineCopy = &findService[index] + } + var baseCardV fyne.CanvasObject + del := func() { gridWrap.Remove(baseCardV) } + var userCopy = user + baseCardV, err = itemFollowUserView(w, onlineCopy, &userCopy, del) + if err != nil { + continue + } + gridWrap.Add(baseCardV) + } + + scroll := container.NewScroll(gridWrap) + return container.NewBorder(nil, nil, nil, nil, scroll) +} + +func indexOnline2User(id string) func(models.Online) bool { + return func(item models.Online) bool { + return item.ID == id + } +} + +func itemFollowUserView(w fyne.Window, data *models.Online, user *models.UserFollows, del func()) (fyne.CanvasObject, error) { + + onlineShow := widget.NewLabel("离线") + + go func() { + defer func() { _ = recover() }() + if data == nil { + data = &models.Online{ID: user.ID, Ip: user.Ip, Port: cfg.T.ServerAddr} + } + newUser, err := service.Client.GetUser(data) + if err != nil { + data = nil + return + } + if err == nil { + onlineShow.SetText("在线") + } + if err == nil && newUser.Name != user.Name { + // 更新用户信息 + repo.UserFollow.UnFollow(global.DB, user) + repo.UserFollow.Follow(global.DB, &models.UserFollows{Users: *newUser}) + } + }() + + followButton := widget.NewButton("取消关注", func() { + err := repo.UserFollow.UnFollow(global.DB, user) + if err != nil { + dialog.ShowInformation("取消失败", err.Error(), w) + return + } + dialog.ShowInformation("", "取消成功", w) + del() + }) + showErrButton := widget.NewButton("查看主页", func() { + if data == nil { + dialog.ShowInformation("查看失败", "用户未在线", w) + return + } + otherUserIndexWin(&user.Users, data) + }) + maishuiButton := widget.NewButton("聊天", func() { + //if data == nil { + // dialog.ShowInformation("查看失败", "用户未在线", w) + // return + //} + OpenChat(user.Users) + }) + + card := widget.NewCard(user.Name, user.Ip, container.NewVBox(onlineShow, + container.NewHBox(showErrButton, followButton), + maishuiButton)) + return card, nil +} diff --git a/views/otherUsers.go b/views/otherUsers.go index 7454baf..4af3959 100644 --- a/views/otherUsers.go +++ b/views/otherUsers.go @@ -75,6 +75,7 @@ func itemOnlineUserView(w fyne.Window, data *models.Online) (fyne.CanvasObject, showErrButton := widget.NewButton("查看主页", func() { otherUserIndexWin(user, data) }) + card := widget.NewCard(user.Name, user.Ip, container.NewVBox(followLabel, showErrButton, followButton)) return card, nil }