新增聊天功能
This commit is contained in:
@@ -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)}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@ package models
|
|||||||
// UserFollows 关注用户
|
// UserFollows 关注用户
|
||||||
type UserFollows struct {
|
type UserFollows struct {
|
||||||
Users
|
Users
|
||||||
|
Sort int
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestP1(t *testing.T) {
|
||||||
|
text := "test"
|
||||||
|
data := syscall.StringToUTF16(text)
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+2
-1
@@ -21,11 +21,12 @@ var (
|
|||||||
"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
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user