diff --git a/global/data_test.go b/global/data_test.go index b493d93..3181fc8 100644 --- a/global/data_test.go +++ b/global/data_test.go @@ -41,14 +41,14 @@ func TestP2(t *testing.T) { ) f := func(tx *gorm.DB) *gorm.DB { - // Find SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY UserID desc - // First SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY UserID desc,`test_models`.`id` LIMIT 1 + // Find SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY User desc + // First SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY User desc,`test_models`.`id` LIMIT 1 // row value misused 滥用 return tx. Where("id in ?", id). //Or("id = ? ", id+1). Select("value"). - Order("UserID desc").Find(&row) + Order("User desc").Find(&row) } testSql(db, f) @@ -65,7 +65,7 @@ func TestP3(t *testing.T) { f := func(tx *gorm.DB) *gorm.DB { // Update `test_models` SET `value`="[1 2]",`updated_at`="2024-10-05 13:21:47.253" WHERE id in (1,2) AND `test_models`.`deleted_at` IS NULL 返回个数:2 err: - // Save SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY UserID desc,`test_models`.`id` LIMIT 1 + // Save SELECT `id`,`value` FROM `test_models` WHERE id = 1 AND `test_models`.`deleted_at` IS NULL ORDER BY User desc,`test_models`.`id` LIMIT 1 // row value misused 滥用 return tx.Model(&TestModel{}). Where("id in ?", 5). @@ -75,7 +75,7 @@ func TestP3(t *testing.T) { //Update("value", []byte(fmt.Sprintf("%d", id))) //Or("id = ? ", id+1). //Select("value"). - //Order("UserID desc").Find(&row) + //Order("User desc").Find(&row) } testSql(db, f) diff --git a/global/games.go b/global/games.go new file mode 100644 index 0000000..256ec1a --- /dev/null +++ b/global/games.go @@ -0,0 +1,33 @@ +package global + +import "sync" + +type RunGameInfo struct { + Uuid string + Type TyGameType + Obj any +} + +var RunGames = make(map[string]*RunGameInfo) +var lock = sync.Mutex{} + +func GetGameInfo(uuid string) *RunGameInfo { + return RunGames[uuid] +} +func CreateGame(game *RunGameInfo) { + lock.Lock() + defer lock.Unlock() + RunGames[game.Uuid] = game +} + +func DeleteGame(uuid string) { + lock.Lock() + defer lock.Unlock() + delete(RunGames, uuid) +} + +type TyGameType = int + +const ( + GameType1 TyGameType = iota +) diff --git a/main.go b/main.go index d7db25b..f29ea7f 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,7 @@ func main() { func mainView(w fyne.Window) fyne.CanvasObject { service.Zeroconf.StartFindService() views.ListenChat() + views.StartGameListen() // 启动服务 if err := service.Server.StartListenServer(); err != nil { return widget.NewLabel(err.Error()) diff --git a/models/gameMessage.go b/models/gameMessage.go index 1cb373a..6bb9b1b 100644 --- a/models/gameMessage.go +++ b/models/gameMessage.go @@ -1,6 +1,9 @@ package models type GameMessage struct { - UserID string `json:"user_id"` - Pos int `json:"pos"` + Router string `json:"router"` + User *Users `json:"user_id"` + Pos int `json:"pos"` + GameType int + GameUuid string } diff --git a/models/online.go b/models/online.go index 6ebe6f4..182cb3e 100644 --- a/models/online.go +++ b/models/online.go @@ -18,6 +18,9 @@ type Online struct { } func (o *Online) Url(router string) string { + if o.Port == "" { + o.Port = cfg.T.ServerAddr + } return fmt.Sprintf("http://%s%s%s", o.Ip, o.Port, router) } diff --git a/models/user.go b/models/user.go index dc5d736..a45e5fe 100644 --- a/models/user.go +++ b/models/user.go @@ -1,12 +1,16 @@ package models +import "time" + type Users struct { - ID string `gorm:"primarykey" json:"id"` - Ip string `json:"ip"` - Name string `json:"name"` // 昵称 - Avatar string `json:"avatar"` // 头像 - Cover string `json:"cover"` // 封面 - Email string `json:"email"` - Phone string `json:"phone"` - Address string `json:"address"` // 工位 + ID string `gorm:"primarykey" json:"id"` + Ip string `json:"ip"` + UpdateTx time.Time `json:"update_tx"` + Port string `json:"port"` + Name string `json:"name"` // 昵称 + Avatar string `json:"avatar"` // 头像 + Cover string `json:"cover"` // 封面 + Email string `json:"email"` + Phone string `json:"phone"` + Address string `json:"address"` // 工位 } diff --git a/repo/user.go b/repo/user.go index a692b59..e06676c 100644 --- a/repo/user.go +++ b/repo/user.go @@ -3,6 +3,8 @@ package repo import ( "gorm.io/gorm" "os/user" + "time" + "work_cation/cfg" "work_cation/models" "work_cation/pkg/utils" ) @@ -38,6 +40,12 @@ func (u *userRepo) GetUserInfo(db *gorm.DB) *models.Users { } db.Create(&users) } + if users.Port != cfg.T.ServerAddr { + users.Port = cfg.T.ServerAddr + users.UpdateTx = time.Now() + db.Model(&models.Users{}).Where("id = ?", users.ID). + Updates(users) + } u.isNew = true u.users = users return &users @@ -45,5 +53,6 @@ func (u *userRepo) GetUserInfo(db *gorm.DB) *models.Users { func (u *userRepo) Update(db *gorm.DB, newUser *models.Users) error { u.isNew = false - return db.Model(&models.Users{}).Where("UserID = ?", newUser.ID).Updates(newUser).Error + return db.Model(&models.Users{}).Where("id = ?", newUser.ID). + Updates(newUser).Error } diff --git a/service/client.go b/service/client.go index 0b0b35b..2cb2f3e 100644 --- a/service/client.go +++ b/service/client.go @@ -115,29 +115,45 @@ func (c *ClientService) Download(online *models.Online, uuid string, progress bi } 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}) + req := gin.H{ + "text": text, + } + msg, err := PostSend("/chat", online, req) if err != nil { return "", err } - res, err := http.NewRequestWithContext(ctx, "POST", online.Url("/chat"), bytes.NewBuffer(b)) + return msg["message"].(string), err +} + +func (c *ClientService) StartGame(online *models.Online, gameUuid string, gameType int) (string, error) { + req := gin.H{ + "type": gameType, + "game_uuid": gameUuid, + } + msg, err := PostSend("/start_game", online, req) if err != nil { return "", err } - res.Header.Set("Content-Type", "application/json") - res.Header.Set("User-UserID", user.ID) - resp, err := http.DefaultClient.Do(res) + return msg["message"].(string), err +} + +func (c *ClientService) PlayGame(online *models.Online, gameUuid string, pos int) (string, error) { + req := gin.H{ + "pos": pos, + "game_uuid": gameUuid, + } + msg, err := PostSend("/play_game", online, req) 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 +} + +func (c *ClientService) CloseGame(online *models.Online, gameUuid string) (string, error) { + req := gin.H{ + "game_uuid": gameUuid, } + msg, err := PostSend("/close_game", online, req) return msg["message"].(string), err } @@ -204,3 +220,30 @@ func unzipFile(zipFile, destDir string) error { return nil } + +func PostSend(router string, online *models.Online, req gin.H) (gin.H, error) { + var user = repo.User.GetUserInfo(global.DB) + ctx, carnal := context.WithTimeout(context.TODO(), time.Millisecond*500) + defer carnal() + b, err := json.Marshal(req) + if err != nil { + return nil, err + } + res, err := http.NewRequestWithContext(ctx, "POST", online.Url(router), bytes.NewBuffer(b)) + if err != nil { + return nil, err + } + res.Header.Set("Content-Type", "application/json") + res.Header.Set("User-User", user.ID) + resp, err := http.DefaultClient.Do(res) + if err != nil { + return nil, 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 nil, err + } + return msg, err +} diff --git a/service/server.go b/service/server.go index 3e0af7d..a49e1c0 100644 --- a/service/server.go +++ b/service/server.go @@ -66,17 +66,14 @@ func (s *serverService) StartListenServer() error { router.GET("/download/card/:uuid", func(c *gin.Context) { uuid := c.Param("uuid") card := repo.BaseCard.Find(global.DB, uuid) - fmt.Println("card:", card) if card.UUID == "" { c.JSON(404, nil) return } cardPath := filepath.Join(cfg.T.CardDir, card.UUID) - // 设置响应头 c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip", uuid)) c.Header("Content-Type", "application/zip") - err := zipFolder(cardPath, c.Writer) if err != nil { c.JSON(500, err) @@ -85,15 +82,8 @@ func (s *serverService) StartListenServer() error { }) router.POST("/chat", func(c *gin.Context) { - uuid := c.GetHeader("User-UserID") - 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": "输入异常"}) + user, msg, is := publicPostCheck(c) + if !is { return } message := &models.Message{ @@ -105,23 +95,45 @@ func (s *serverService) StartListenServer() error { c.JSON(200, gin.H{"message": "ok"}) }) - // 游戏接口 - router.POST("/game", func(c *gin.Context) { - uuid := c.GetHeader("User-UserID") - user := repo.UserFollow.GetUser(global.DB, uuid) - if user.Ip != c.ClientIP() { - c.JSON(200, gin.H{"message": "对方未关注你"}) + // 开始游戏 + router.POST("/start_game", func(c *gin.Context) { + user, msg, is := publicPostCheck(c) + if !is { return } - var msg = make(map[string]interface{}) - if err := c.ShouldBind(&msg); err != nil { - c.JSON(200, gin.H{"message": "输入异常"}) - return - } - message := &models.GameMessage{ - UserID: user.ID, - Pos: msg["text"].(int), + Router: "/start_game", + User: &user.Users, + GameType: int(msg["type"].(float64)), + GameUuid: msg["game_uuid"].(string), + } + global.Send.Game1Chan <- message + c.JSON(200, gin.H{"message": "ok"}) + }) + router.POST("/play_game", func(c *gin.Context) { + user, msg, is := publicPostCheck(c) + if !is { + return + } + message := &models.GameMessage{ + Router: "/play_game", + User: &user.Users, + Pos: int(msg["pos"].(float64)), + GameUuid: msg["game_uuid"].(string), + } + global.Send.Game1Chan <- message + c.JSON(200, gin.H{"message": "ok"}) + }) + + router.POST("/close_game", func(c *gin.Context) { + user, msg, is := publicPostCheck(c) + if !is { + return + } + message := &models.GameMessage{ + Router: "/close_game", + User: &user.Users, + GameUuid: msg["game_uuid"].(string), } global.Send.Game1Chan <- message c.JSON(200, gin.H{"message": "ok"}) @@ -190,3 +202,18 @@ func zipFolder(folderPath string, zipFile io.Writer) error { return nil } + +func publicPostCheck(c *gin.Context) (*models.UserFollows, map[string]interface{}, bool) { + uuid := c.GetHeader("User-User") + user := repo.UserFollow.GetUser(global.DB, uuid) + if user.Ip != c.ClientIP() { + c.JSON(200, gin.H{"message": "对方未关注你"}) + return nil, nil, false + } + var msg = make(map[string]interface{}) + if err := c.ShouldBind(&msg); err != nil { + c.JSON(200, gin.H{"message": "输入异常"}) + return nil, nil, false + } + return user, msg, true +} diff --git a/views/chatView.go b/views/chatView.go index b6b3156..08b5c75 100644 --- a/views/chatView.go +++ b/views/chatView.go @@ -8,9 +8,9 @@ import ( "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "time" - "work_cation/cfg" "work_cation/global" "work_cation/models" + "work_cation/pkg/utils" "work_cation/repo" "work_cation/service" ) @@ -79,7 +79,7 @@ func OpenChat(user models.Users) { } online := service.Zeroconf.GetOnline(user.ID) if online == nil { - online = &models.Online{ID: user.ID, Ip: user.Ip, Port: cfg.T.ServerAddr} + online = &models.Online{ID: user.ID, Ip: user.Ip, Port: user.Port} } msg, err := service.Client.Chat(online, en.Text) if err != nil || msg != "ok" { @@ -101,6 +101,16 @@ func OpenChat(user models.Users) { dialog.ShowInformation("未开发", "分享脚本功能 尽请期待", w) }), widget.NewToolbarAction(theme.CancelIcon(), func() { w.Close() }), + widget.NewToolbarAction(theme.MailForwardIcon(), func() { + game := NewTenGame(nil) + online := &models.Online{ID: user.ID, Ip: user.Ip, Port: user.Port} + err := game.AddNet(utils.Uuid.CreateUUID(), 0, online, &user) + if err != nil { + dialog.ShowInformation("错误", err.Error(), w) + return + } + game.StartShow() + }), ) button := widget.NewButton("", submit) button.SetIcon(theme.ConfirmIcon()) diff --git a/views/followUsers.go b/views/followUsers.go index f5ef775..528e4f4 100644 --- a/views/followUsers.go +++ b/views/followUsers.go @@ -7,7 +7,6 @@ import ( "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" "slices" - "work_cation/cfg" "work_cation/global" "work_cation/models" "work_cation/repo" @@ -61,7 +60,7 @@ func itemFollowUserView(w fyne.Window, data *models.Online, user *models.UserFol go func() { defer func() { _ = recover() }() if data == nil { - data = &models.Online{ID: user.ID, Ip: user.Ip, Port: cfg.T.ServerAddr} + data = &models.Online{ID: user.ID, Ip: user.Ip, Port: user.Port} } newUser, err := service.Client.GetUser(data) if err != nil { @@ -71,7 +70,7 @@ func itemFollowUserView(w fyne.Window, data *models.Online, user *models.UserFol if err == nil { onlineShow.SetText("在线") } - if err == nil && newUser.Name != user.Name { + if err == nil && newUser.UpdateTx != user.UpdateTx { // 更新用户信息 fmt.Println("更新用户信息:", user, newUser) repo.UserFollow.UnFollow(global.DB, user) diff --git a/views/gamesViews.go b/views/gamesViews.go index 79c620f..8ddf777 100644 --- a/views/gamesViews.go +++ b/views/gamesViews.go @@ -18,6 +18,9 @@ import ( "image/png" "slices" "sync" + "work_cation/global" + "work_cation/models" + "work_cation/service" ) func TenChinaGameView() { @@ -35,6 +38,39 @@ func TenChinaGameView() { game.StartShow() } +func StartGameListen() { + go func() { + for { + select { + case msg := <-global.Send.Game1Chan: + switch msg.Router { + case "/play_game": + g := global.GetGameInfo(msg.GameUuid) + if g != nil { + game := g.Obj.(*TenGame) + game.Play(game.userIndex, msg.Pos) + } + case "/close_game": + g := global.GetGameInfo(msg.GameUuid) + if g != nil { + dialog.ShowInformation("提示", "对方已退出", g.Obj.(*TenGame).w) + } + case "/start_game": + g := global.GetGameInfo(msg.GameUuid) + if g != nil { + fmt.Println("start_game") + continue + } + game := NewTenGame(nil) + online := &models.Online{ID: msg.User.ID, Ip: msg.User.Ip, Port: msg.User.Port} + game.AddNet(msg.GameUuid, 1, online, msg.User) + game.StartShow() + } + } + } + }() +} + // 格子棋 Checkered-Chess type TenGame struct { lock sync.Mutex // 玩家操作锁 @@ -60,6 +96,13 @@ type TenGame struct { Ctx context.Context Cancel func() + + isNet bool // 是否是网络模式 + uuid string + myIndex int + user *models.Users // + userIndex int + online *models.Online } func NewTenGame(winCallback func(int)) *TenGame { @@ -83,22 +126,56 @@ func NewTenGame(winCallback func(int)) *TenGame { winSum: 5, Ctx: ctx, Cancel: cancel, + isNet: false, } return game } +func (t *TenGame) AddNet(uuid string, myIndex int, online *models.Online, user *models.Users) error { + t.isNet = true + t.online = online + t.myIndex = myIndex + t.user = user + t.uuid = uuid + info := &global.RunGameInfo{ + Uuid: t.uuid, + Type: global.GameType1, + Obj: t, + } + global.CreateGame(info) + + // 发起人 + if myIndex == 0 { + t.userIndex = 1 + _, err := service.Client.StartGame(t.online, t.uuid, global.GameType1) + if err != nil { + t.isNet = false + return err + } + } + return nil +} + func (t *TenGame) StartShow() { myApp := fyne.CurrentApp() - myWindow := myApp.NewWindow(fmt.Sprintf("Game %0.f*%0.f win:%d", t.itemX, t.itemY, t.winSum)) - t.w = myWindow + myWindow := myApp.NewWindow("") + t.w = myWindow + t.setTitle() wSize := fyne.NewSize(t.itemX*(t.itemSize.Width), t.itemY*(t.itemSize.Height)) myWindow.Resize(wSize) myWindow.SetContent(container.New(layout.NewMaxLayout(), canvas.NewImageFromResource(drawABackgroundImage(wSize, int(t.itemX), int(t.itemY))), t.CanvasObject())) + myWindow.SetOnClosed(func() { + if t.isNet { + global.DeleteGame(t.uuid) + service.Client.CloseGame(t.online, t.uuid) + } + }) myWindow.CenterOnScreen() + myWindow.RequestFocus() myWindow.Show() } @@ -115,7 +192,10 @@ func (t *TenGame) CanvasObject() fyne.CanvasObject { t.Items = append(t.Items, image) toggle := widget.NewButton("", func() { - _ = t.Play(0, itemIndex) + err := t.Play(t.myIndex, itemIndex) + if err != nil { + dialog.ShowError(err, t.w) + } }) toggle.Resize(t.itemSize) @@ -132,21 +212,48 @@ func (t *TenGame) CanvasObject() fyne.CanvasObject { func (t *TenGame) Play(userIndex int, pos int) error { t.lock.Lock() defer t.lock.Unlock() - //if t.currentRoundPlayer != userIndex { - // return errors.New("你急个der") - //} + if t.isNet && t.currentRoundPlayer != userIndex { + return errors.New("你急个der") + } if slices.Contains(t.allMaps, pos) { return errors.New("已经下过了") } + + if t.isNet && t.myIndex == userIndex { + // todo 广播数据 + _, err := service.Client.PlayGame(t.online, t.uuid, pos) + if err != nil { + return err + } + } // 下棋 t.allMaps = append(t.allMaps, pos) t.play(pos) // 推进 + t.advance() + return nil +} + +// 推进 +func (t *TenGame) advance() { t.currentRoundPlayer++ if t.currentRoundPlayer+1 > len(t.playerID2Image) { t.currentRoundPlayer = 0 } - return nil + t.setTitle() +} + +func (t *TenGame) setTitle() { + title := fmt.Sprintf("Game %0.f*%0.f win:%d", t.itemX, t.itemY, t.winSum) + if t.isNet { + title += fmt.Sprintf(" | 我 vs %s", t.user.Name) + if t.currentRoundPlayer == t.myIndex { + title += " | 我的回合" + } else { + title += " | 对手回合" + } + } + t.w.SetTitle(title) } /*