新增聊天功能

This commit is contained in:
2024-10-14 17:40:11 +08:00
parent 99ed84f534
commit 245875881c
13 changed files with 360 additions and 12 deletions
+9
View File
@@ -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)}
+5
View File
@@ -35,6 +35,11 @@ func main() {
// 主界面 // 主界面
func mainView(w fyne.Window) fyne.CanvasObject { func mainView(w fyne.Window) fyne.CanvasObject {
service.Zeroconf.StartFindService() service.Zeroconf.StartFindService()
views.ListenChat()
// 启动服务
if err := service.Server.StartListenServer(); err != nil {
return widget.NewLabel(err.Error())
}
var ( var (
content = container.NewMax() content = container.NewMax()
a = fyne.CurrentApp() a = fyne.CurrentApp()
+14
View File
@@ -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
}
+1
View File
@@ -3,4 +3,5 @@ package models
// UserFollows 关注用户 // UserFollows 关注用户
type UserFollows struct { type UserFollows struct {
Users Users
Sort int
} }
+13
View File
@@ -0,0 +1,13 @@
package utils
import (
"fmt"
"syscall"
"testing"
)
func TestP1(t *testing.T) {
text := "test"
data := syscall.StringToUTF16(text)
fmt.Println(data)
}
+8
View File
@@ -24,3 +24,11 @@ func (u *userFollowRepo) GetUser(db *gorm.DB, uuid string) *models.UserFollows {
} }
return &user 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
}
+31
View File
@@ -2,17 +2,21 @@ package service
import ( import (
"archive/zip" "archive/zip"
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/data/binding"
"github.com/gin-gonic/gin"
"io" "io"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"work_cation/cfg" "work_cation/cfg"
"work_cation/global"
"work_cation/models" "work_cation/models"
"work_cation/repo"
) )
type ClientService struct { type ClientService struct {
@@ -110,6 +114,33 @@ func (c *ClientService) Download(online *models.Online, uuid string, progress bi
return nil 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 { type Rr struct {
file *os.File file *os.File
tot int64 tot int64
+30 -7
View File
@@ -10,8 +10,10 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"time"
"work_cation/cfg" "work_cation/cfg"
"work_cation/global" "work_cation/global"
"work_cation/models"
"work_cation/repo" "work_cation/repo"
) )
@@ -36,19 +38,15 @@ func (s *serverService) Online() error {
if err := Zeroconf.Register(user); err != nil { if err := Zeroconf.Register(user); err != nil {
return err return err
} }
// 启动服务
if err := s.StartListenServer(); err != nil {
return err
}
// 修改状态 // 修改状态
s.Status = StatusOnline s.Status = StatusOnline
return nil return nil
} }
func (s *serverService) StatusOffline() error { func (s *serverService) StatusOffline() error {
if err := s.server.Close(); err != nil { //if err := s.server.Close(); err != nil {
return err // return err
} //}
// 关闭服务发现 // 关闭服务发现
Zeroconf.Close() 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{ srv := &http.Server{
Addr: cfg.T.ServerAddr, Addr: cfg.T.ServerAddr,
Handler: router, Handler: router,
+9
View File
@@ -42,6 +42,15 @@ func (s *zeroconfService) FindService() ([]models.Online, error) {
return s.onlines, nil 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 { func (s *zeroconfService) StartFindService() error {
resolver, err := zeroconf.NewResolver(nil) resolver, err := zeroconf.NewResolver(nil)
if err != nil { if err != nil {
+125
View File
@@ -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
}
+6 -5
View File
@@ -17,15 +17,16 @@ type Tutorial struct {
var ( var (
// Tutorials 定义每个教程的元数据 // Tutorials 定义每个教程的元数据
Tutorials = map[string]Tutorial{ Tutorials = map[string]Tutorial{
"welcome": {"主页", "", mainUserViews, true}, "welcome": {"主页", "", mainUserViews, true},
"canvas": {"我的", "", allCardsViews, true}, "canvas": {"我的", "", allCardsViews, true},
"create": {"新建", "", allCreateCards, true}, "create": {"新建", "", allCreateCards, true},
"otherUsers": {"同事", "", otherUser, true}, "otherUsers": {"同事", "", otherUser, true},
"followUsers": {"关注", "", followUsers, true},
} }
// TutorialIndex 定义我们的教程应该如何在索引树中布局 // TutorialIndex 定义我们的教程应该如何在索引树中布局
TutorialIndex = map[string][]string{ TutorialIndex = map[string][]string{
"": {"welcome", "canvas", "otherUsers", "create"}, "": {"welcome", "canvas", "followUsers", "otherUsers", "create"},
//"collections": {"list", "table", "tree"}, //"collections": {"list", "table", "tree"},
//"containers": {"apptabs", "border", "box", "center", "doctabs", "grid", "scroll", "split"}, //"containers": {"apptabs", "border", "box", "center", "doctabs", "grid", "scroll", "split"},
//"widgets": {"accordion", "button", "card", "entry", "form", "input", "progress", "text", "toolbar"}, 58 * 3 + 106 = 280 //"widgets": {"accordion", "button", "card", "entry", "form", "input", "progress", "text", "toolbar"}, 58 * 3 + 106 = 280
+108
View File
@@ -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
}
+1
View File
@@ -75,6 +75,7 @@ func itemOnlineUserView(w fyne.Window, data *models.Online) (fyne.CanvasObject,
showErrButton := widget.NewButton("查看主页", func() { showErrButton := widget.NewButton("查看主页", func() {
otherUserIndexWin(user, data) otherUserIndexWin(user, data)
}) })
card := widget.NewCard(user.Name, user.Ip, container.NewVBox(followLabel, showErrButton, followButton)) card := widget.NewCard(user.Name, user.Ip, container.NewVBox(followLabel, showErrButton, followButton))
return card, nil return card, nil
} }