diff --git a/conf/conf.ini.sample b/conf/conf.ini.sample index 9479a6b..fc99ddb 100644 --- a/conf/conf.ini.sample +++ b/conf/conf.ini.sample @@ -25,6 +25,7 @@ PWD = zimaboard Email = user@gmail.com Description = description Initialized = false +Avatar = [zerotier] UserName = user diff --git a/main.go b/main.go index 17b1c99..1f0b53d 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,8 @@ func init() { service.MyService = service.NewService(sqliteDB, loger2.NewOLoger()) service.Cache = cache.Init() //go service.UDPConnect([]string{}) - //go service.SocketConnect() + go service.SocketConnect() + //go service.UDPService() route.InitFunction() } diff --git a/model/sys_common.go b/model/sys_common.go index 504dd80..9301133 100644 --- a/model/sys_common.go +++ b/model/sys_common.go @@ -16,6 +16,7 @@ type UserModel struct { Email string Description string Initialized bool + Avatar string } //服务配置 diff --git a/pkg/sqlite/db.go b/pkg/sqlite/db.go index 565408d..2b85a71 100644 --- a/pkg/sqlite/db.go +++ b/pkg/sqlite/db.go @@ -31,7 +31,7 @@ func GetDb(projectPath string) *gorm.DB { return nil } gdb = db - err = db.AutoMigrate(&model2.TaskDBModel{}, &model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.PersionDownloadDBModel{}) + err = db.AutoMigrate(&model2.TaskDBModel{}, &model2.AppNotify{}, &model2.AppListDBModel{}, &model2.SerialDisk{}, model2.PersionDownloadDBModel{}, model2.FriendModel{}) if err != nil { fmt.Println("检查和创建数据库出错", err) } diff --git a/route/route.go b/route/route.go index 7172ca8..0bdb20f 100644 --- a/route/route.go +++ b/route/route.go @@ -291,11 +291,11 @@ func InitRouter() *gin.Engine { v1PersonGroup.Use() { // v1PersonGroup.GET("/test", v1.PersonTest) - // v1PersonGroup.GET("/users", v1.Users) //用户列表 - // v1PersonGroup.POST("/user", v1.Users) //添加用户 - // v1PersonGroup.GET("/directory", v1.Users) //文件列表 - v1PersonGroup.GET("/download", v1.GetPersionFile) //下载文件 - // v1PersonGroup.PUT("/edit/:id", v1.EditUser) //修改好友 + v1PersonGroup.GET("/users", v1.GetPersionFriend) //用户列表 + v1PersonGroup.POST("/user", v1.PostAddPersionFriend) //添加用户 + v1PersonGroup.GET("/directory", v1.GetPersionDirectory) //文件列表 + //v1PersonGroup.GET("/download", v1.GetPersionFile) //下载文件 + v1PersonGroup.PUT("/edit/:token", v1.PutPersionNick) //修改好友 v1PersonGroup.GET("/list", v1.GetPersionDownloadList) //下载列表(需要考虑试试下载速度) // v1PersonGroup.PUT("/state/:id", v1.PutPersionCancelDownload) //修改下载状态(开始暂停删除) diff --git a/route/v1/persion.go b/route/v1/persion.go index b65d53e..d44f556 100644 --- a/route/v1/persion.go +++ b/route/v1/persion.go @@ -2,6 +2,7 @@ package v1 import ( "encoding/json" + "fmt" "net/http" "time" @@ -83,3 +84,97 @@ func GetPersionDownloadList(c *gin.Context) { c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)}) } + +// @Summary add friend +// @Produce application/json +// @Accept application/json +// @Tags persion +// @Param token formData int true "Opponent token" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /persion/edit [put] +func PutPersionNick(c *gin.Context) { + token := c.Param("token") + nick := c.PostForm("nick") + if len(token) == 0 || len(nick) == 0 { + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)}) + return + } + friend := model2.FriendModel{} + friend.Token = token + friend.NickName = nick + service.MyService.Friend().EditFriendNick(friend) + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)}) +} + +// @Summary add friend +// @Produce application/json +// @Accept application/json +// @Tags persion +// @Param token formData int true "Opponent token" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /persion/users [get] +func GetPersionFriend(c *gin.Context) { + list := service.MyService.Friend().GetFriendList() + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: list}) +} + +// @Summary add friend +// @Produce application/json +// @Accept application/json +// @Tags persion +// @Param token formData int true "Opponent token" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /persion/user [post] +func PostAddPersionFriend(c *gin.Context) { + token := c.PostForm("token") + if len(token) == 0 { + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)}) + return + } + //step:远程验证token是否存在 + msg := model.MessageModel{} + msg.Type = types.PERSONADDFRIEND + msg.To = token + msg.Data = token + msg.From = config.ServerInfo.Token + msg.UUId = uuid.NewV4().String() + b, _ := json.Marshal(msg) + err := service.WebSocketConn.WriteMessage(websocket.TextMessage, b) + fmt.Println(err) + + friend := model2.FriendModel{} + friend.Token = token + service.MyService.Friend().AddFriend(friend) + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS)}) +} + +func GetPersionDirectory(c *gin.Context) { + path := c.Query("path") + persion := c.Query("persion") + if len(path) == 0 && len(persion) == 0 { + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.INVALID_PARAMS, Message: oasis_err2.GetMsg(oasis_err2.INVALID_PARAMS)}) + return + } + //任务标识 + uuid := uuid.NewV4().String() + m := model.MessageModel{} + m.Data = path + m.From = config.ServerInfo.Token + m.To = persion + m.Type = "directory" + m.UUId = uuid + result, err := service.Dial("192.168.2.225:9902", m) + if err != nil { + fmt.Println(err) + } + dataModel := []model.Path{} + if m.UUId == m.UUId { + dataModelByte, _ := json.Marshal(result.Data) + err := json.Unmarshal(dataModelByte, &dataModel) + fmt.Println(err) + } + c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: dataModel}) +} diff --git a/service/friend.go b/service/friend.go new file mode 100644 index 0000000..16a23b4 --- /dev/null +++ b/service/friend.go @@ -0,0 +1,47 @@ +package service + +import ( + model2 "github.com/IceWhaleTech/CasaOS/service/model" + "gorm.io/gorm" +) + +type FriendService interface { + AddFriend(m model2.FriendModel) + DeleteFriend(m model2.FriendModel) + EditFriendNick(m model2.FriendModel) + GetFriendById(m model2.FriendModel) model2.FriendModel + GetFriendList() (list []model2.FriendModel) + UpdateAddFriendType(m model2.FriendModel) +} + +type friendService struct { + db *gorm.DB +} + +func (p *friendService) AddFriend(m model2.FriendModel) { + p.db.Create(&m) +} +func (p *friendService) DeleteFriend(m model2.FriendModel) { + p.db.Where("token = ?", m.Token).Delete(&m) +} +func (p *friendService) EditFriendNick(m model2.FriendModel) { + p.db.Model(&m).Where("token = ?", m.Token).Update("nick_name", m.NickName) +} + +func (p *friendService) GetFriendById(m model2.FriendModel) model2.FriendModel { + p.db.Model(m).Where("token = ?", m.Token).First(&m) + return m +} + +func (p *friendService) GetFriendList() (list []model2.FriendModel) { + p.db.Select("nick_name", "avatar", "name", "profile", "token", "state").Find(&list) + return list +} + +func (p *friendService) UpdateAddFriendType(m model2.FriendModel) { + p.db.Model(&m).Updates(m) +} + +func NewFriendService(db *gorm.DB) FriendService { + return &friendService{db: db} +} diff --git a/service/model/o_friend.go b/service/model/o_friend.go new file mode 100644 index 0000000..ff36e9d --- /dev/null +++ b/service/model/o_friend.go @@ -0,0 +1,16 @@ +package model + +type FriendModel struct { + State int `json:"state"` //备用 + CreatedAt string `gorm:"<-:create;autoCreateTime" json:"created_at"` + UpdatedAt string `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at"` + NickName string `json:"nick_name"` //custom name + Avatar string `json:"avatar"` //头像 + Name string `json:"name"` + Token string `gorm:"column:token;primary_key" json:"token"` + Profile string `json:"profile"` +} + +func (p *FriendModel) TableName() string { + return "o_friend" +} diff --git a/service/person.go b/service/person.go index 846fb34..352ae5d 100644 --- a/service/person.go +++ b/service/person.go @@ -1,6 +1,7 @@ package service import ( + "bufio" "context" "crypto/cipher" "crypto/rand" @@ -17,6 +18,7 @@ import ( "os" "path/filepath" "reflect" + "strconv" "time" "github.com/IceWhaleTech/CasaOS/model" @@ -268,16 +270,10 @@ func generateTLSConfig() *tls.Config { if err != nil { panic(err) } - // return &tls.Config{ - // ClientSessionCache: globalSessionCache, - // RootCAs: root, - // InsecureSkipVerify: false, - // NextProtos: nil, - // SessionTicketsDisabled: true, - // } return &tls.Config{ - Certificates: []tls.Certificate{tlsCert}, - NextProtos: []string{"quic-echo-example"}, + Certificates: []tls.Certificate{tlsCert}, + NextProtos: []string{"bench"}, + SessionTicketsDisabled: true, } } @@ -510,3 +506,197 @@ func AsyncUDPConnect(dst *net.UDPAddr) { func NewPersonService(db *gorm.DB) PersonService { return &personService{db: db} } + +//======================================================================================================================================================================= + +var StreamList map[string]quic.Stream +var ServiceMessage chan model.MessageModel + +func UDPService() { + quicConfig := &quic.Config{ + ConnectionIDLength: 4, + KeepAlive: true, + } + srcAddr := &net.UDPAddr{ + IP: net.IPv4zero, Port: 9902} //注意端口必须固定 + fmt.Println(srcAddr.String()) + listener, err := quic.ListenAddr(srcAddr.String(), generateTLSConfig(), quicConfig) + if err != nil { + fmt.Println(err) + } + defer listener.Close() + ctx := context.Background() + acceptFailures := 0 + const maxAcceptFailures = 10 + if err != nil { + panic(err) + } + for { + select { + case <-ctx.Done(): + fmt.Println(ctx.Err()) + return + default: + } + + session, err := listener.Accept(ctx) + if err != nil { + fmt.Println("Listen (BEP/quic): Accepting connection:", err) + + acceptFailures++ + if acceptFailures > maxAcceptFailures { + // Return to restart the listener, because something + // seems permanently damaged. + fmt.Println(err) + return + } + + // Slightly increased delay for each failure. + time.Sleep(time.Duration(acceptFailures) * time.Second) + + continue + } + + acceptFailures = 0 + + streamCtx, cancel := context.WithTimeout(ctx, time.Second*10) + stream, err := session.AcceptStream(streamCtx) + cancel() + if err != nil { + fmt.Println("failed to accept stream from %s: %v", session.RemoteAddr(), err) + _ = session.CloseWithError(1, err.Error()) + continue + } + + // prefixByte := make([]byte, 4) + // c1, err := io.ReadFull(stream, prefixByte) + // fmt.Println(c1, err) + // prefixLength, err := strconv.Atoi(string(prefixByte)) + // if err != nil { + // fmt.Println(err) + // } + // messageByte := make([]byte, prefixLength) + // t, err := io.ReadFull(stream, messageByte) + // fmt.Println(t, err) + // m := model.MessageModel{} + // err = json.Unmarshal(messageByte, &m) + // if err != nil { + // fmt.Println(err) + // } + + go ProcessingContent(stream) + } +} + +//处理内容 +func ProcessingContent(stream quic.Stream) { + //需要处理关闭问题 + + for { + prefixByte := make([]byte, 4) + c1, err := io.ReadFull(stream, prefixByte) + fmt.Println(c1) + if err != nil { + return + } + prefixLength, err := strconv.Atoi(string(prefixByte)) + if err != nil { + fmt.Println(err) + } + messageByte := make([]byte, prefixLength) + t, err := io.ReadFull(stream, messageByte) + if err != nil { + return + } + fmt.Println(t, err) + m := model.MessageModel{} + err = json.Unmarshal(messageByte, &m) + fmt.Println(m) + if err != nil { + fmt.Println(err) + } + if m.Type == "hello" { + //什么也不做 + continue + } else if m.Type == "directory" { + list := MyService.ZiMa().GetDirPath(m.Data.(string)) + m.To = m.From + m.Data = list + m.From = config.ServerInfo.Token + SendData(stream, m) + break + } else if m.Type == "file_data" { + SendFileData(stream, m.Data.(string), m.From, m.UUId) + break + } else if m.Type == types.PERSONADDFRIEND { + friend := model2.FriendModel{} + dataModelByte, _ := json.Marshal(m.Data) + err := json.Unmarshal(dataModelByte, &friend) + if err != nil { + fmt.Println(err) + continue + } + go MyService.Friend().UpdateAddFriendType(friend) + mi := model2.FriendModel{} + mi.Avatar = config.UserInfo.Avatar + mi.Profile = config.UserInfo.Description + mi.Name = config.UserInfo.UserName + m.To = m.From + m.Data = mi + m.Type = types.PERSONADDFRIEND + m.From = config.ServerInfo.Token + + SendData(stream, m) + break + } else { + //不应有不做返回的数据 + //ServiceMessage <- m + break + } + } + stream.Close() + +} + +//文件分片发送 +func SendFileData(stream quic.Stream, filePath, to, uuid string) error { + + fStat, err := os.Stat(filePath) + if err != nil { + return err + } + + blockSize, length := file.GetBlockInfo(fStat.Size()) + + f, err := os.Open(filePath) + if err != nil { + fmt.Println("读取失败", err) + return err + } + bufferedReader := bufio.NewReader(f) + buf := make([]byte, blockSize) + for i := 0; i < length; i++ { + + tran := model.TranFileModel{} + + _, err = bufferedReader.Read(buf) + + if err == io.EOF { + fmt.Println("读取完毕", err) + } + + tran.Hash = file.GetHashByContent(buf) + tran.Index = i + + msg := model.MessageModel{} + msg.Type = "file_data" + msg.Data = tran + msg.From = config.ServerInfo.Token + msg.To = to + msg.UUId = uuid + b, _ := json.Marshal(msg) + stream.Write(b) + } + defer stream.Close() + return nil +} diff --git a/service/service.go b/service/service.go index 9aba0c7..c40da69 100644 --- a/service/service.go +++ b/service/service.go @@ -33,6 +33,7 @@ type Repository interface { Shortcuts() ShortcutsService Search() SearchService Person() PersonService + Friend() FriendService } func NewService(db *gorm.DB, log loger2.OLog) Repository { @@ -55,6 +56,7 @@ func NewService(db *gorm.DB, log loger2.OLog) Repository { shortcuts: NewShortcutsService(db), search: NewSearchService(), person: NewPersonService(db), + friend: NewFriendService(db), } } @@ -76,8 +78,12 @@ type store struct { shortcuts ShortcutsService search SearchService person PersonService + friend FriendService } +func (c *store) Friend() FriendService { + return c.friend +} func (c *store) Rely() RelyService { return c.rely } diff --git a/service/socket.go b/service/socket.go index 056e75b..ff44464 100644 --- a/service/socket.go +++ b/service/socket.go @@ -4,12 +4,16 @@ import ( "encoding/json" "fmt" "net/url" + "reflect" "strings" "time" "github.com/IceWhaleTech/CasaOS/model" "github.com/IceWhaleTech/CasaOS/pkg/config" + model2 "github.com/IceWhaleTech/CasaOS/service/model" + "github.com/IceWhaleTech/CasaOS/types" "github.com/gorilla/websocket" + uuid "github.com/satori/go.uuid" ) var WebSocketConn *websocket.Conn @@ -36,6 +40,32 @@ func SocketConnect() { fmt.Println(err) //开始尝试udp链接 go UDPConnect(content.Ips) + } else if msa.Type == types.PERSONADDFRIEND { + // new add friend + uuid := uuid.NewV4().String() + mi := model2.FriendModel{} + mi.Avatar = config.UserInfo.Avatar + mi.Profile = config.UserInfo.Description + mi.Name = config.UserInfo.UserName + m := model.MessageModel{} + m.Data = mi + m.From = config.ServerInfo.Token + m.To = msa.From + m.Type = types.PERSONADDFRIEND + m.UUId = uuid + result, err := Dial("192.168.2.225:9902", m) + friend := model2.FriendModel{} + if err != nil && !reflect.DeepEqual(result, friend) { + dataModelByte, _ := json.Marshal(result.Data) + err := json.Unmarshal(dataModelByte, &friend) + if err != nil { + fmt.Println(err) + } + } + if len(friend.Token) == 0 { + friend.Token = m.From + } + MyService.Friend().AddFriend(friend) } } }() diff --git a/service/udpconn.go b/service/udpconn.go index edf8688..dca32e2 100644 --- a/service/udpconn.go +++ b/service/udpconn.go @@ -1,7 +1,6 @@ package service import ( - "bufio" "context" "crypto/md5" "crypto/tls" @@ -11,8 +10,8 @@ import ( "io" "io/ioutil" "net" - "os" "strconv" + "time" "github.com/IceWhaleTech/CasaOS/model" "github.com/IceWhaleTech/CasaOS/pkg/config" @@ -25,7 +24,9 @@ var UDPconn *net.UDPConn var PeopleMap map[string]quic.Stream var Message chan model.MessageModel -func Dial(addr string, token string) error { +func Dial(addr string, msg model.MessageModel) (m model.MessageModel, err error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() Message = make(chan model.MessageModel) quicConfig := &quic.Config{ ConnectionIDLength: 4, @@ -36,21 +37,26 @@ func Dial(addr string, token string) error { NextProtos: []string{"bench"}, SessionTicketsDisabled: true, } - session, err := quic.DialAddr(addr, tlsConf, quicConfig) - defer session.CloseWithError(0, "") + + session, err := quic.DialAddrContext(ctx, addr, tlsConf, quicConfig) if err != nil { - return err + return m, err } - stream, err := session.OpenStreamSync(context.Background()) + + stream, err := session.OpenStreamSync(ctx) if err != nil { - return err + session.CloseWithError(1, err.Error()) + return m, err } - SayHello(stream, token) - //写 + + SayHello(stream, msg.To) + + SendData(stream, msg) + go ReadContent(stream) - //读 - //结果 - return nil + result := <-Message + stream.Close() + return result, nil } func SayHello(stream quic.Stream, to string) { @@ -60,62 +66,17 @@ func SayHello(stream quic.Stream, to string) { msg.To = to msg.From = config.ServerInfo.Token msg.UUId = uuid.NewV4().String() - b, _ := json.Marshal(msg) - prefixLength := file.PrefixLength(len(b)) - - data := append(prefixLength, b...) - stream.Write(data) + SendData(stream, msg) } var pathsss string -//文件分片发送 -func SendFileData(stream quic.Stream, filePath, to, uuid string) error { - - fStat, err := os.Stat(filePath) - if err != nil { - return err - } - - blockSize, length := file.GetBlockInfo(fStat.Size()) - - f, err := os.Open(filePath) - if err != nil { - fmt.Println("读取失败", err) - return err - } - bufferedReader := bufio.NewReader(f) - buf := make([]byte, blockSize) - for i := 0; i < length; i++ { - - tran := model.TranFileModel{} - - _, err = bufferedReader.Read(buf) - - if err == io.EOF { - fmt.Println("读取完毕", err) - } - - tran.Hash = file.GetHashByContent(buf) - tran.Index = i - - msg := model.MessageModel{} - msg.Type = "file_data" - msg.Data = tran - msg.From = config.ServerInfo.Token - msg.To = to - msg.UUId = uuid - b, _ := json.Marshal(msg) - stream.Write(b) - } - defer stream.Close() - return nil -} - //发送数据 func SendData(stream quic.Stream, m model.MessageModel) { b, _ := json.Marshal(m) - stream.Write(b) + prefixLength := file.PrefixLength(len(b)) + data := append(prefixLength, b...) + stream.Write(data) } //读取数据 @@ -124,12 +85,12 @@ func ReadContent(stream quic.Stream) { for { prefixByte := make([]byte, 4) c1, err := io.ReadFull(stream, prefixByte) - fmt.Println(c1, err) + fmt.Println(c1, err, string(prefixByte)) prefixLength, err := strconv.Atoi(string(prefixByte)) messageByte := make([]byte, prefixLength) t, err := io.ReadFull(stream, messageByte) - fmt.Println(t, err) + fmt.Println(t, err, string(messageByte)) m := model.MessageModel{} err = json.Unmarshal(messageByte, &m) if err != nil { diff --git a/types/persion.go b/types/persion.go new file mode 100644 index 0000000..ea2f87f --- /dev/null +++ b/types/persion.go @@ -0,0 +1,3 @@ +package types + +const PERSONADDFRIEND = "add_user"