From 7862b1d88e003c1b4eb7ae908df293adbd1b8b2c Mon Sep 17 00:00:00 2001 From: shine <1042864399@qq.com> Date: Sat, 12 Oct 2024 13:37:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BA=86=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E5=85=A8=E9=83=A8=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cfg/config.go | 2 +- main.go | 2 + models/online.go | 10 ++- repo/baseCard.go | 15 +++- service/baseCardService.go | 2 +- service/client.go | 146 ++++++++++++++++++++++++++++++++++++- service/erlangCard.go | 2 +- service/execFile.go | 2 +- service/server.go | 81 +++++++++++++++++++- service/zeroconf.go | 31 ++++---- views/otherUsers.go | 79 +++++++++++++++++++- 11 files changed, 342 insertions(+), 30 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 7381d20..6c853e9 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -24,7 +24,7 @@ var T = Config{ Avatar: "./data/avatar", ZeroconfKey: "work_cations_service", ZeroconfPort: 16800, - ServerAddr: ":16800", + ServerAddr: ":16801", } func init() { diff --git a/main.go b/main.go index 46e45f3..c49c40a 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "fyne.io/fyne/v2/widget" "work_cation/assets" "work_cation/global" + "work_cation/service" "work_cation/version" "work_cation/views" ) @@ -33,6 +34,7 @@ func main() { // 主界面 func mainView(w fyne.Window) fyne.CanvasObject { + service.Zeroconf.StartFindService() var ( content = container.NewMax() a = fyne.CurrentApp() diff --git a/models/online.go b/models/online.go index ad40e83..6ebe6f4 100644 --- a/models/online.go +++ b/models/online.go @@ -5,37 +5,39 @@ import ( "fmt" "github.com/grandcat/zeroconf" "net" + "work_cation/cfg" ) // Online 在线数据 type Online struct { AddrIPv4 []net.IP `json:"-"` // Host machine IPv4 address AddrIPv6 []net.IP `json:"-"` - Port int `json:"port"` + Port string `json:"port"` ID string `json:"id"` Ip string `json:"ip"` } func (o *Online) Url(router string) string { - return fmt.Sprintf("http://%s:%d%s", o.Ip, o.Port, router) + return fmt.Sprintf("http://%s%s%s", o.Ip, o.Port, router) } func UserTOnlineList(user *Users) []string { return []string{ user.ID, user.Ip, + cfg.T.ServerAddr, } } func NewOnline(entry *zeroconf.ServiceEntry) (*Online, error) { - if len(entry.Text) != 2 { + if len(entry.Text) != 3 { return nil, errors.New("invalid online entry") } return &Online{ AddrIPv4: entry.AddrIPv4, AddrIPv6: entry.AddrIPv6, - Port: entry.Port, + Port: entry.Text[2], ID: entry.Text[0], Ip: entry.Text[1], }, nil diff --git a/repo/baseCard.go b/repo/baseCard.go index 315a7a7..25ea63a 100644 --- a/repo/baseCard.go +++ b/repo/baseCard.go @@ -15,6 +15,19 @@ func (*baseCardRepo) FindAll(db *gorm.DB) []models.BaseCard { return cards } -func (*baseCardRepo) Create(db *gorm.DB, baseCard *models.BaseCard) error { +func (*baseCardRepo) Find(db *gorm.DB, uuid string) models.BaseCard { + var card models.BaseCard + db.Where("uuid = ?", uuid).Find(&card) + return card +} + +func (*baseCardRepo) CreateOrSave(db *gorm.DB, baseCard *models.BaseCard) error { + var card models.BaseCard + if err := db.Where("uuid = ?", baseCard.UUID).Find(&card).Error; err != nil { + return err + } + if card.UUID != "" { + return db.Where("uuid = ?", baseCard.UUID).Save(baseCard).Error + } return db.Create(baseCard).Error } diff --git a/service/baseCardService.go b/service/baseCardService.go index 14a377a..5fae698 100644 --- a/service/baseCardService.go +++ b/service/baseCardService.go @@ -14,7 +14,7 @@ type BaseCardService struct{} var BaseCard = &BaseCardService{} func (*BaseCardService) Create(db *gorm.DB, baseCard models.BaseCard) { - repo.BaseCard.Create(db, &baseCard) + repo.BaseCard.CreateOrSave(db, &baseCard) } func (*BaseCardService) Update(db *gorm.DB, updateCard models.BaseCard) { diff --git a/service/client.go b/service/client.go index 2a7b0a9..6adf907 100644 --- a/service/client.go +++ b/service/client.go @@ -1,9 +1,17 @@ package service import ( + "archive/zip" + "context" "encoding/json" + "fmt" + "fyne.io/fyne/v2/data/binding" "io" "net/http" + "os" + "path/filepath" + "time" + "work_cation/cfg" "work_cation/models" ) @@ -14,7 +22,13 @@ var Client = &ClientService{} func (c *ClientService) GetUser(online *models.Online) (*models.Users, error) { var user models.Users - resp, err := http.Get(online.Url("/user")) + ctx, carnal := context.WithTimeout(context.TODO(), time.Millisecond*500) + defer carnal() + res, err := http.NewRequestWithContext(ctx, "", online.Url("/user"), nil) + if err != nil { + return nil, err + } + resp, err := http.DefaultClient.Do(res) if err != nil { return nil, err } @@ -29,3 +43,133 @@ func (c *ClientService) GetUser(online *models.Online) (*models.Users, error) { } return &user, nil } + +func (c *ClientService) GetCards(online *models.Online) ([]models.BaseCard, error) { + var cards []models.BaseCard + ctx, carnal := context.WithTimeout(context.TODO(), time.Millisecond*500) + defer carnal() + res, err := http.NewRequestWithContext(ctx, "", online.Url("/cards"), nil) + if err != nil { + return nil, err + } + resp, err := http.DefaultClient.Do(res) + if err != nil { + return nil, err + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &cards) + if err != nil { + return nil, err + } + return cards, nil +} + +func (c *ClientService) Download(online *models.Online, uuid string, progress binding.Float) error { + + resp, err := http.Get(online.Url("/download/card/" + uuid)) + if err != nil { + return err + } + defer resp.Body.Close() + + downloadFile := filepath.Join(cfg.T.CardDir, fmt.Sprintf("%s.zip", uuid)) + _, err = os.Stat(downloadFile) + if err == nil { + os.Remove(downloadFile) + } + + file, err := os.Create(downloadFile) + if err != nil { + return err + } + defer file.Close() + + r := Rr{ + file: file, + tot: resp.ContentLength, + progress: 0, + oneUp: 0, + progressBind: progress, + } + + _, err = io.Copy(&r, resp.Body) + if err != nil { + return err + } + // 解压 + destDir := filepath.Join(cfg.T.CardDir, uuid) + _ = os.RemoveAll(destDir) + if err = unzipFile(downloadFile, destDir); err != nil { + return err + } + _ = os.Remove(downloadFile) + return nil +} + +type Rr struct { + file *os.File + tot int64 + progress int64 + + oneUp int + progressBind binding.Float +} + +func (r *Rr) Write(p []byte) (n int, err error) { + n, err = r.file.Write(p) + if err != nil { + return + } + r.progress += int64(n) + + nowUp := int(r.progress * 100 / r.tot) + if nowUp != r.oneUp { + r.progressBind.Set(float64(nowUp)) + r.oneUp = nowUp + } + return +} + +func unzipFile(zipFile, destDir string) error { + // 打开ZIP文件 + r, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer r.Close() + // 创建目标目录 + err = os.MkdirAll(destDir, os.ModePerm) + if err != nil { + return err + } + // 遍历ZIP文件中的文件并解压缩到目标目录 + for _, f := range r.File { + rc, err := f.Open() + if err != nil { + return err + } + // 创建文件 + path := filepath.Join(destDir, f.Name) + if f.FileInfo().IsDir() { + os.MkdirAll(path, os.ModePerm) + } else { + // 创建文件 + file, err := os.Create(path) + if err != nil { + return err + } + _, err = io.Copy(file, rc) + if err != nil { + return err + } + file.Close() + } + rc.Close() + } + + return nil +} diff --git a/service/erlangCard.go b/service/erlangCard.go index 4c5d9cc..ce20b21 100644 --- a/service/erlangCard.go +++ b/service/erlangCard.go @@ -39,7 +39,7 @@ func (*erlangCardService) Create(erlangCard *models.ErlangCards) error { return global.DB.Transaction(func(tx *gorm.DB) error { var baseCard = erlangCard.BaseCard - err = repo.BaseCard.Create(global.DB, &baseCard) + err = repo.BaseCard.CreateOrSave(global.DB, &baseCard) return err }) } diff --git a/service/execFile.go b/service/execFile.go index 3457f1d..cf48b66 100644 --- a/service/execFile.go +++ b/service/execFile.go @@ -49,6 +49,6 @@ func (*ExecFileService) Create(info *models.ExecFiles, chooseDir string) error { } return global.DB.Transaction(func(tx *gorm.DB) error { var baseCard = info.BaseCard - return repo.BaseCard.Create(tx, &baseCard) + return repo.BaseCard.CreateOrSave(tx, &baseCard) }) } diff --git a/service/server.go b/service/server.go index 387ad2a..29cdc9b 100644 --- a/service/server.go +++ b/service/server.go @@ -1,10 +1,15 @@ package service import ( + "archive/zip" "errors" + "fmt" "github.com/gin-gonic/gin" + "io" "log" "net/http" + "os" + "path/filepath" "work_cation/cfg" "work_cation/global" "work_cation/repo" @@ -52,11 +57,35 @@ func (s *serverService) StatusOffline() error { } func (s *serverService) StartListenServer() error { - router := gin.New() + router := gin.Default() router.GET("/user", func(c *gin.Context) { user := repo.User.GetUserInfo(global.DB) c.JSON(200, user) }) + router.GET("/cards", func(c *gin.Context) { + cards := repo.BaseCard.FindAll(global.DB) + c.JSON(200, cards) + }) + 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) + return + } + }) srv := &http.Server{ Addr: cfg.T.ServerAddr, @@ -71,3 +100,53 @@ func (s *serverService) StartListenServer() error { s.server = srv return nil } + +func zipFolder(folderPath string, zipFile io.Writer) error { + zipWriter := zip.NewWriter(zipFile) + defer zipWriter.Close() + err := filepath.Walk(folderPath, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + header.Name, err = filepath.Rel(folderPath, filePath) + if err != nil { + return err + } + + if info.IsDir() { + header.Name += "/" + } else { + header.Method = zip.Deflate + } + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + + if !info.IsDir() { + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + _, err = io.Copy(writer, file) + if err != nil { + return err + } + } + return nil + }) + + if err != nil { + return err + } + + return nil +} diff --git a/service/zeroconf.go b/service/zeroconf.go index 2d24513..baca849 100644 --- a/service/zeroconf.go +++ b/service/zeroconf.go @@ -4,13 +4,14 @@ import ( "context" "fmt" "github.com/grandcat/zeroconf" - "time" + "slices" "work_cation/cfg" "work_cation/models" ) type zeroconfService struct { - server *zeroconf.Server + server *zeroconf.Server + onlines []models.Online } var Zeroconf = &zeroconfService{} @@ -37,31 +38,31 @@ func (s *zeroconfService) Close() { } // FindService 查找被发现服务 -func (s *zeroconfService) FindService() (chan *models.Online, error) { +func (s *zeroconfService) FindService() ([]models.Online, error) { + return s.onlines, nil +} + +func (s *zeroconfService) StartFindService() error { resolver, err := zeroconf.NewResolver(nil) if err != nil { - return nil, err + return err } allEntries := make(chan *zeroconf.ServiceEntry) - useEntries := make(chan *models.Online) - go func(results <-chan *zeroconf.ServiceEntry) { for entry := range results { + fmt.Println(entry.AddrIPv4, entry.ServiceInstanceName()) if entry.ServiceInstanceName() == fmt.Sprintf("%s._http._tcp.local.", cfg.T.ZeroconfKey) { online, err := models.NewOnline(entry) if err != nil { continue } - useEntries <- online + delOnlines := slices.DeleteFunc(s.onlines, func(item models.Online) bool { + return item.ID == online.ID + }) + s.onlines = append(delOnlines, *online) } } }(allEntries) - ctx, cancel := context.WithCancel(context.Background()) - err = resolver.Browse(ctx, "_http._tcp", "local.", allEntries) - if err != nil { - cancel() - return nil, err - } - time.AfterFunc(10*time.Second, cancel) - return useEntries, nil + err = resolver.Browse(context.Background(), "_http._tcp", "local.", allEntries) + return err } diff --git a/views/otherUsers.go b/views/otherUsers.go index a1b3912..7454baf 100644 --- a/views/otherUsers.go +++ b/views/otherUsers.go @@ -1,10 +1,14 @@ package views import ( + "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" + "time" "work_cation/global" "work_cation/models" "work_cation/repo" @@ -18,11 +22,11 @@ func otherUser(w fyne.Window) fyne.CanvasObject { return widget.NewLabel("网络异常请稍后尝试") } go func() { - for online := range findService { + for _, online := range findService { var ( onlineCopy = online ) - baseCardV, err := itemOnlineUserView(w, onlineCopy) + baseCardV, err := itemOnlineUserView(w, &onlineCopy) if err != nil { continue } @@ -69,9 +73,76 @@ func itemOnlineUserView(w fyne.Window, data *models.Online) (fyne.CanvasObject, }) showErrButton := widget.NewButton("查看主页", func() { - dialog.ShowInformation("错误", "", w) + otherUserIndexWin(user, data) }) - card := widget.NewCard(user.Name, user.Ip, container.NewVBox(followLabel, showErrButton, followButton)) return card, nil } + +func otherUserIndexWin(user *models.Users, data *models.Online) { + myApp := fyne.CurrentApp() + myWindow := myApp.NewWindow(user.Name) + myWindow.CenterOnScreen() + myWindow.Resize(fyne.NewSize(620, 500)) + myWindow.SetContent(otherUserIndexView(myWindow, data)) + myWindow.Show() +} + +func otherUserIndexView(w fyne.Window, online *models.Online) fyne.CanvasObject { + gridWrap := container.NewGridWrap(fyne.NewSize(200, 200)) + cards, _ := service.Client.GetCards(online) + for _, baseCard := range cards { + var baseCardCopy = baseCard + baseCardV := baseOtherCardView(w, baseCardCopy, online) + gridWrap.Add(baseCardV) + } + scroll := container.NewScroll(gridWrap) + return container.NewBorder(nil, nil, nil, nil, scroll) +} + +func baseOtherCardView(w fyne.Window, baseCardCopy models.BaseCard, online *models.Online) *widget.Card { + + saveCard := repo.BaseCard.Find(global.DB, baseCardCopy.UUID) + + infoWid := container.NewVBox( + widget.NewLabel(baseCardCopy.ToolType), + widget.NewLabel(fmt.Sprintf("本地:%s", saveCard.UpdateTx.Format(time.DateTime))), + widget.NewLabel(fmt.Sprintf("作者:%s", baseCardCopy.UpdateTx.Format(time.DateTime))), + ) + + baseCardV := widget.NewCard(baseCardCopy.Title, baseCardCopy.Text, container.NewBorder(nil, widget.NewToolbar( + widget.NewToolbarAction(theme.DownloadIcon(), func() { + b := saveCard.UpdateTx.Unix() == baseCardCopy.UpdateTx.Unix() + fmt.Println(saveCard.UpdateTx, baseCardCopy.UpdateTx) + if b { + dialog.ShowInformation("结果", "已下载本地", w) + return + } + // 下载到本地 若没关注自动关注 + + progress := binding.NewFloat() + progress.Set(0) + progressBar := widget.NewProgressBarWithData(progress) + progressBar.Max = 100 + smaillWin := dialog.NewCustom("下载中", "关闭", progressBar, w) + smaillWin.Show() + err := service.Client.Download(online, baseCardCopy.UUID, progress) + if err != nil { + smaillWin.SetDismissText("失败") + return + } + // 修改数据 + progress.Set(99) + saveCard.UpdateTx = baseCardCopy.UpdateTx + err = repo.BaseCard.CreateOrSave(global.DB, &baseCardCopy) + if err != nil { + smaillWin.SetDismissText("失败") + return + } + progress.Set(100) + smaillWin.SetDismissText("完成 请去[我的]查看") + + }), + ), nil, nil, infoWid)) + return baseCardV +}