This commit is contained in:
zhaocy 2022-12-12 08:30:21 +08:00
parent 7296524768
commit 60d10b3a11
30 changed files with 1998 additions and 77 deletions

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "GUI",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go"
}
]
}

View File

@ -12,21 +12,27 @@ var _ lib.ICaller = (*FriendApply)(nil)
type FriendApply struct { type FriendApply struct {
lib.Action lib.Action
Desc string
} }
func (a *FriendApply) BuildReq(head *pb.UserMessage) lib.ActionReq { func (a *FriendApply) ShowInfo() lib.CallerInfo {
return lib.CallerInfo{
Key: "friend.apply",
Desc: "好友申请",
}
}
func (a *FriendApply) BuildReq(store lib.IStore, head *pb.UserMessage) lib.RawReq {
id := time.Now().UnixNano() id := time.Now().UnixNano()
var req []byte var req []byte
b := a.Get("friend.apply") b := store.Get("friend.apply")
rsp := &pb.FriendRandlistResp{} rsp := &pb.FriendRandlistResp{}
if err := proto.Unmarshal(b, rsp); err != nil { if err := proto.Unmarshal(b, rsp); err != nil {
panic(err) panic(err)
} }
if len(rsp.List) == 0 { if len(rsp.List) == 0 {
return lib.ActionReq{} return lib.RawReq{}
} }
if lib.ProtoMarshal(&pb.FriendApplyReq{ if lib.ProtoMarshal(&pb.FriendApplyReq{
@ -38,5 +44,5 @@ func (a *FriendApply) BuildReq(head *pb.UserMessage) lib.ActionReq {
} }
req = data req = data
} }
return lib.ActionReq{ID: id, Req: req} return lib.RawReq{ID: id, Req: req}
} }

View File

@ -13,10 +13,16 @@ var _ lib.ICaller = (*FriendRecommend)(nil)
// 好友推荐 // 好友推荐
type FriendRecommend struct { type FriendRecommend struct {
lib.Action lib.Action
Desc string
} }
func (a *FriendRecommend) BuildReq(head *pb.UserMessage) lib.ActionReq { func (a *FriendRecommend) ShowInfo() lib.CallerInfo {
return lib.CallerInfo{
Key: "friend.randlist",
Desc: "好友推荐",
}
}
func (a *FriendRecommend) BuildReq(store lib.IStore, head *pb.UserMessage) lib.RawReq {
id := time.Now().UnixNano() id := time.Now().UnixNano()
var req []byte var req []byte
@ -28,5 +34,5 @@ func (a *FriendRecommend) BuildReq(head *pb.UserMessage) lib.ActionReq {
req = data req = data
} }
return lib.ActionReq{ID: id, Req: req} return lib.RawReq{ID: id, Req: req}
} }

3
go.mod
View File

@ -3,8 +3,11 @@ module legu.airobot
go 1.18 go 1.18
require ( require (
fyne.io/fyne v1.4.3
fyne.io/fyne/v2 v2.2.4 fyne.io/fyne/v2 v2.2.4
github.com/gorilla/websocket v1.5.0
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/spf13/cast v1.3.1
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
) )

22
go.sum
View File

@ -37,6 +37,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/fyne v1.4.3 h1:356CnXCiYrrfaLGsB7qLK3c6ktzyh8WR05v/2RBu51I=
fyne.io/fyne v1.4.3/go.mod h1:8kiPBNSDmuplxs9WnKCkaWYqbcXFy0DeAzwa6PBO9Z8=
fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4= fyne.io/fyne/v2 v2.2.4 h1:izyiDUjJYAB7B/MST7M9GDs+mQ0CwDgRZTiVJZQoEe4=
fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA= fyne.io/fyne/v2 v2.2.4/go.mod h1:MBoGuHzLLSXdQOWFAwWhIhYTEMp33zqtGCReSWhaQTA=
fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4= fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 h1:V2IC9t0Zj9Ur6qDbfhUuzVmIvXKFyxZXRJyigUvovs4=
@ -44,6 +46,8 @@ fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93/go.mod h1:oM2AQqGJ1AMo4nNq
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/akavel/rsrc v0.10.2/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@ -85,15 +89,19 @@ github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFN
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E= github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0= github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@ -167,6 +175,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI= github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY= github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY= github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
@ -193,7 +203,9 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI= github.com/jackmordaunt/icns/v2 v2.2.1/go.mod h1:6aYIB9eSzyfHHMKqDf17Xrs1zetQPReAkiUSHzdw4cI=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY= github.com/josephspurrier/goversioninfo v1.4.0/go.mod h1:JWzv5rKQr+MmW+LvM412ToT/IkYDZjaclF2pKDss8IY=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@ -208,6 +220,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20200401173949-526b5363a13a/go.mod h1:ORP3/rB5IsulLEBwQZCJyyV6niqmI7P4EWSmkug+1Ng=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@ -232,7 +245,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -253,10 +268,13 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
@ -318,6 +336,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw= golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -435,6 +454,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -483,6 +503,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -504,6 +525,7 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

View File

@ -1,39 +1,16 @@
package lib package lib
type IAction interface { type IAction interface {
ShowInfo() CallerInfo
} }
type Action struct { type Action struct {
scene *scene // scene *scene
Key string
Desc string
} }
func NewAction() *Action { func NewAction() *Action {
a := &Action{} a := &Action{}
return a return a
} }
//存数据
func (a *Action) Store(key string, data []byte) {
defer a.scene.lock.Unlock()
a.scene.lock.Lock()
a.scene.data[key] = data
}
//取数据
func (a *Action) Get(key string) []byte {
defer a.scene.lock.Unlock()
a.scene.lock.Lock()
return a.scene.data[key]
}
// func (a *BaseAction) Load(action ...IAction) {
// for _, v := range action {
// v.RegisterAction()
// }
// }
// func (a *BaseAction) Reg(idStr string, caller interface{}) {
// // a.actionMap[idStr] = action
// a.ai.callers = append(a.ai.callers, caller)
// }

102
lib/ai.go
View File

@ -1,28 +1,53 @@
package lib package lib
import (
"bytes"
"fmt"
"sync"
"github.com/sirupsen/logrus"
)
type myAI struct { type myAI struct {
robots []*myRobot robots []*Robot
scenes []*scene scenes []*scene
callers []ICaller callers []ICaller
tickets Tickets //票池
robotCount uint32 //机器人数量
lock sync.Mutex //
} }
func NewAI() *myAI { func NewAI(aip AIParam) (*myAI, error) {
ai := &myAI{ logrus.Debug("创建AI")
// action: NewAction(), if err := aip.Check(); err != nil {
scenes: make([]*scene, 0), return nil, err
} }
// ai.action = NewAction(ai) ai := &myAI{
scenes: make([]*scene, 0),
robotCount: aip.RobotCount,
}
ai.init() if err := ai.init(); err != nil {
return nil, err
}
return ai return ai, nil
} }
func (m *myAI) init() { func (m *myAI) init() error {
//初始化机器人容量 var buf bytes.Buffer
// 容量限制 buf.WriteString("初始化AI")
tickets, err := NewTickets(m.robotCount)
if err != nil {
return err
}
m.tickets = tickets
buf.WriteString(fmt.Sprintf("完成 机器人数量:%d", m.robotCount))
logrus.Debug(buf.String())
return nil
} }
//启动时载入所有Caller //启动时载入所有Caller
@ -37,7 +62,7 @@ func (m *myAI) InitCaller(callers ...ICaller) {
} }
// 加入机器人 // 加入机器人
func (m *myAI) AddRobots(num int, scene *scene) { func (m *myAI) AddRobot(scene *scene) {
// //
robot := NewRobot() robot := NewRobot()
robot.SelScene(scene) robot.SelScene(scene)
@ -45,7 +70,7 @@ func (m *myAI) AddRobots(num int, scene *scene) {
} }
// 获取场景下的机器人 // 获取场景下的机器人
func (m *myAI) GetRobots(sceneName string) (robots []*myRobot) { func (m *myAI) GetRobots(sceneName string) (robots []*Robot) {
for _, robot := range m.robots { for _, robot := range m.robots {
scene := robot.GetCurrentScene() scene := robot.GetCurrentScene()
if scene != nil && scene.Name == sceneName { if scene != nil && scene.Name == sceneName {
@ -55,14 +80,51 @@ func (m *myAI) GetRobots(sceneName string) (robots []*myRobot) {
return return
} }
func (m *myAI) Start() { func (m *myAI) CurrentScene() *scene {
//启动默认数量的机器人 for _, v := range m.scenes {
if v.status == STATUS_ENABLE {
return v
}
}
return nil
}
r := NewRobot() func (m *myAI) appendRobot(robot *Robot) {
// defer m.lock.Unlock()
m.lock.Lock()
m.robots = append(m.robots, robot)
}
r.Start() func (m *myAI) Start() bool {
if len(m.scenes) == 0 {
logrus.Warn("还未设置场景")
return false
}
// 只有一个场景是启用状态,所有启动的机器人使用一个场景
scene := m.CurrentScene()
if scene == nil {
logrus.Warn("至少要启用一个场景")
return false
}
// i := uint32(0); i < m.robotCount; i++
go func() {
for {
m.tickets.Take()
go func() {
// m.AddRobot(scene)
robot := NewRobot()
robot.SelScene(scene)
m.appendRobot(robot)
robot.Start()
}()
}
}()
return true
} }
func (m *myAI) Stop() { func (m *myAI) Stop() {

View File

@ -1,11 +1,47 @@
package lib package lib
type ActionReq struct { import "time"
type RawReq struct {
ID int64 ID int64
Req []byte Req []byte
} }
type RawResp struct {
ID int64
Resp []byte
Err error
Elapse time.Duration
}
type CallerInfo struct {
Key string
Desc string
}
const ( const (
//默认的机器人数量 //默认的机器人数量
DefaultRobotNum int = 10 DefaultRobotNum int = 10
) )
//机器人状态
const (
// STATUS_ORIGINAL 代表原始。
STATUS_ORIGINAL uint32 = 0
// STATUS_STARTING 代表正在启动。
STATUS_STARTING uint32 = 1
// STATUS_STARTED 代表已启动。
STATUS_STARTED uint32 = 2
// STATUS_STOPPING 代表正在停止。
STATUS_STOPPING uint32 = 3
// STATUS_STOPPED 代表已停止。
STATUS_STOPPED uint32 = 4
)
// 场景状态
const (
// 启用
STATUS_ENABLE uint32 = 1
// 禁用
STATUS_DISENABLE uint32 = 0
)

View File

@ -4,6 +4,5 @@ import "legu.airobot/pb"
type ICaller interface { type ICaller interface {
IAction IAction
BuildReq(store IStore, head *pb.UserMessage) RawReq
BuildReq(head *pb.UserMessage) ActionReq
} }

View File

@ -2,6 +2,8 @@ package lib
import ( import (
"fmt" "fmt"
"math"
"strconv"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/anypb"
@ -17,3 +19,90 @@ func ProtoMarshal(rsp proto.Message, msg *pb.UserMessage) (ok bool) {
msg.Data = any msg.Data = any
return true return true
} }
func ProtoUnmarshal(msg *pb.UserMessage, req proto.Message) (ok bool) {
err := msg.Data.UnmarshalTo(req)
if err != nil {
fmt.Printf("UnmarshalTo %s.%s %v\n", msg.MainType, msg.SubType, err)
return
}
return true
}
func SubStr(str string, start int, length int) (result string) {
s := []rune(str)
total := len(s)
if total == 0 {
return
}
// 允许从尾部开始计算
if start < 0 {
start = total + start
if start < 0 {
return
}
}
if start > total {
return
}
// 到末尾
if length < 0 {
length = total
}
end := start + length
if end > total {
result = string(s[start:])
} else {
result = string(s[start:end])
}
return
}
func DeleteString(list []string, ele string) []string {
result := make([]string, 0)
for _, v := range list {
if v != ele {
result = append(result, v)
}
}
return result
}
func ConvertFileSize(size int64) string {
if size == 0 {
return "0"
}
kb := float64(size) / float64(1024)
if kb < 1024 {
f := math.Ceil(kb)
return strconv.Itoa(int(f)) + " KB"
}
mb := kb / float64(1024)
if mb < 1024 {
f := math.Ceil(mb)
return strconv.Itoa(int(f)) + " MB"
}
gb := mb / float64(1024)
if gb < 1024 {
f := math.Ceil(mb)
return strconv.Itoa(int(f)) + " G"
}
t := gb / float64(1024)
if t < 1024 {
f := math.Ceil(mb)
return strconv.Itoa(int(f)) + " T"
}
if t >= 1024 {
return "VeryBig"
}
return "0"
}

251
lib/itemlist.go Normal file
View File

@ -0,0 +1,251 @@
package lib
import (
"fmt"
"io/ioutil"
"sort"
"strings"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus"
)
type ItemList struct {
SelItemIds []string //选择的ID 多选
SelItemId string //选择的ID 单选
ItemTotal int //总数
TitleLabel *widget.Label
CachedList List
ListWidget *widget.List
SearchItem []Item //用于暂存搜索结果
OnSelFunc func(id widget.ListItemID)
}
func NewItemList() *ItemList {
return &ItemList{
TitleLabel: &widget.Label{},
CachedList: NewList(""),
}
}
// 重置
func (f *ItemList) Reset() {
f.ItemTotal = 0
f.SelItemIds = []string{}
f.SelItemId = ""
f.CachedList = NewList("")
f.ListWidget.UnselectAll()
f.ListWidget.Refresh()
}
func (f *ItemList) CreateList() *widget.List {
f.ListWidget = widget.NewList(
func() int {
return len(f.CachedList.Items)
},
func() fyne.CanvasObject {
return widget.NewLabel("Template")
},
func(id widget.ListItemID, item fyne.CanvasObject) {
c, _ := item.(*widget.Label)
c.Text = f.CachedList.Items[id].Text
c.Refresh()
},
)
if f.OnSelFunc == nil {
f.ListWidget.OnSelected = func(id widget.ListItemID) {
selId := f.CachedList.Items[id].Id
f.SelItemId = selId
}
} else {
f.ListWidget.OnSelected = f.OnSelFunc
}
return f.ListWidget
}
// 创建默认的列表
func (f *ItemList) CreateDefaultCheckList() *widget.List {
f.ListWidget = widget.NewList(
func() int {
return len(f.CachedList.Items)
},
func() fyne.CanvasObject {
return widget.NewCheck("Template", func(bool) {})
},
func(id widget.ListItemID, item fyne.CanvasObject) {
c, _ := item.(*widget.Check)
_id := f.CachedList.Items[id].Id
c.Text = f.CachedList.Items[id].Text
c.Checked = f.CachedList.Items[id].Checked
c.OnChanged = func(b bool) {
if b {
f.SelItemIds = append(f.SelItemIds, _id)
} else {
f.SelItemIds = DeleteString(f.SelItemIds, _id)
}
f.TitleLabel.SetText(fmt.Sprintf("(%d/%d)", len(f.SelItemIds), f.ItemTotal))
f.CachedList.Items[id].Checked = b
// sort.Sort(f.cachedList)
f.ListWidget.Refresh()
}
c.Refresh()
},
)
return f.ListWidget
}
// 创建下载列表
func (f *ItemList) CreateDownloadList() *widget.List {
f.ListWidget = widget.NewList(
func() int {
return len(f.CachedList.Items)
},
func() fyne.CanvasObject {
chk := widget.NewCheck("Template", func(bool) {})
lb := widget.NewLabel("Template")
items := container.NewHBox(chk, &layout.Spacer{}, lb)
return items
},
func(id widget.ListItemID, item fyne.CanvasObject) {
c, _ := item.(*fyne.Container)
chk := c.Objects[0].(*widget.Check)
data := f.CachedList.Items[id]
chk.Text = data.Text
chk.Checked = data.Checked
chk.OnChanged = func(b bool) {
if b {
f.SelItemIds = append(f.SelItemIds, chk.Text)
} else {
f.SelItemIds = DeleteString(f.SelItemIds, chk.Text)
}
f.CachedList.Items[id].Checked = b
// sort.Sort(f.cachedList)
f.ListWidget.Refresh()
}
lb := c.Objects[2].(*widget.Label)
lb.Text = ConvertFileSize(data.Size)
c.Refresh()
},
)
return f.ListWidget
}
func (f *ItemList) AddItem(item Item) {
f.CachedList.Items = append(f.CachedList.Items, item)
// sort.Sort(f.cachedList)
f.ListWidget.Refresh()
}
func (f *ItemList) AddItemWithText(val string) {
val = strings.TrimSpace(val)
if len(val) == 0 {
return
}
newItem := Item{
Id: val,
Text: val,
Quantity: 1,
Checked: false, //默认不选中
}
f.CachedList.Items = append(f.CachedList.Items, newItem)
sort.Sort(f.CachedList)
f.ListWidget.Refresh()
}
func (f *ItemList) LoadItem(dirPath string) {
f.Reset()
files, err := ioutil.ReadDir(dirPath)
if err != nil {
logrus.Error(err)
return
}
for _, file := range files {
if !file.IsDir() {
f.AddItemWithText(file.Name())
// f.selItemIds = append(f.selItemIds, file.Name())
f.ItemTotal++
// logrus.Debugf("%v", file.Name())
}
}
f.SearchItem = f.CachedList.Items
}
func (f *ItemList) SelectedId() string {
if f.SelItemId != "" {
return f.SelItemId
} else {
if len(f.CachedList.Items) == 1 {
return f.CachedList.Items[0].Id
}
}
return ""
}
// 改变列表项目
func (f *ItemList) ChangeItem(tmpDir, projectDir string) {
f.ItemTotal = 0
f.SelItemIds = []string{}
f.CachedList = NewList("")
tmpFiles, err := ioutil.ReadDir(tmpDir)
if err != nil {
logrus.Error(err)
return
}
defer func() {
if len(tmpFiles) == 0 || len(f.CachedList.Items) == 0 {
logrus.Warnln("没有任何变更的项目")
}
}()
projectFiles, err := ioutil.ReadDir(projectDir)
if err != nil {
logrus.Error(err)
return
}
pfMap := make(map[string]int64)
for _, v := range projectFiles {
if !v.IsDir() {
pfMap[v.Name()] = v.ModTime().Unix()
}
}
for _, file := range tmpFiles {
if !file.IsDir() {
if pfTime, ok := pfMap[file.Name()]; ok {
if file.ModTime().Unix() <= pfTime {
continue
}
}
f.AddItemWithText(file.Name())
// f.SelItemIds = append(f.SelItemIds, file.Name())
f.ItemTotal++
// logrus.Debugf("%v", file.Name())
}
}
}
// 刷新文件数
func (f *ItemList) ChangeFileCount() {
f.TitleLabel.SetText(fmt.Sprintf("(%d/%d)", len(f.SelItemIds), f.ItemTotal))
}
func (f *ItemList) DeleteItem(name string) {
for i, v := range f.CachedList.Items {
if v.Text == name {
f.SelItemIds = DeleteString(f.SelItemIds, v.Text)
f.CachedList.Items = append(f.CachedList.Items[:i], f.CachedList.Items[i+1:]...)
if f.ItemTotal > 0 {
f.ItemTotal--
}
}
}
}

75
lib/list.go Normal file
View File

@ -0,0 +1,75 @@
package lib
import "strings"
var replacer *strings.Replacer
func init() {
trimChars := "0123456789-()/½⅓¼⅕⅙⅐⅛⅑⅔⅖¾⅗⅘"
tcs := make([]string, len(trimChars)*2)
for i, c := range trimChars {
tcs[i*2] = string(c)
tcs[i*2+1] = ""
}
replacer = strings.NewReplacer(tcs...)
}
type List struct {
Name string `json:"name"`
Group string `json:"group"`
Items []Item `json:"items"`
// ID is the Mealie API reference for the list.
ID int `json:"id"`
}
var sortableText map[string]string = map[string]string{}
func (l List) Len() int { return len(l.Items) }
func (l List) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
func (l List) Less(i, j int) bool {
var ok bool
var it, jt string
li := l.Items[i]
lit := li.Text
if it, ok = sortableText[lit]; !ok {
it = replacer.Replace(strings.ToUpper(lit))
it = strings.TrimLeft(it, " ")
sortableText[lit] = it
}
lj := l.Items[j]
ljt := lj.Text
if jt, ok = sortableText[ljt]; !ok {
jt = replacer.Replace(strings.ToUpper(ljt))
jt = strings.TrimLeft(jt, " ")
sortableText[ljt] = jt
}
if !li.Checked {
if lj.Checked {
return true
}
return it < jt
}
if !lj.Checked {
return false
}
return it < jt
}
type Item struct {
Id string `json:"id"`
Title string `json:"title"`
Text string `json:"text"`
Quantity int `json:"quantity"`
Checked bool `json:"checked"`
Size int64 `json:"size"`
Data interface{} `json:"data"`
}
func NewList(name string) List {
return List{
ID: -1,
Name: name,
Items: []Item{},
}
}

View File

@ -1,6 +1,40 @@
package lib package lib
import (
"bytes"
"errors"
"fmt"
"strings"
"github.com/sirupsen/logrus"
)
type AIParam struct {
// 机器人数量
RobotCount uint32
}
type SceneParam struct { type SceneParam struct {
Name string Name string
Desc string Desc string
} }
func (a *AIParam) Check() error {
var errMsgs []string
if a.RobotCount == 0 {
errMsgs = append(errMsgs, "机器人数量至少1个")
}
var buf bytes.Buffer
buf.WriteString("AI 参数校验")
if errMsgs != nil {
errMsg := strings.Join(errMsgs, " ")
buf.WriteString(fmt.Sprintf("未通过 (%s)", errMsg))
logrus.Debug(buf.String())
return errors.New(errMsg)
}
buf.WriteString(
fmt.Sprintf("通过 机器人数量:%v", a.RobotCount))
logrus.Debug(buf.String())
return nil
}

View File

@ -1,6 +1,18 @@
package lib package lib
import "github.com/sirupsen/logrus" import (
"errors"
"fmt"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"legu.airobot/pb"
)
type IRobot interface { type IRobot interface {
// 启动机器人 // 启动机器人
@ -11,25 +23,46 @@ type IRobot interface {
GetCurrentScene() *scene GetCurrentScene() *scene
} }
type myRobot struct { type Robot struct {
scene *scene IStore
scene *scene
conn *websocket.Conn
data map[string][]byte //caller缓存数据
status uint32 //状态
lock sync.Mutex //
} }
func NewRobot() *myRobot { func NewRobot() *Robot {
robot := &myRobot{} robot := &Robot{
data: make(map[string][]byte),
}
return robot return robot
} }
func (m *myRobot) SelScene(scene *scene) { //存数据
func (a *Robot) Store(key string, data []byte) {
defer a.lock.Unlock()
a.lock.Lock()
a.data[key] = data
}
//取数据
func (a *Robot) Get(key string) []byte {
defer a.lock.Unlock()
a.lock.Lock()
return a.data[key]
}
func (m *Robot) SelScene(scene *scene) {
m.scene = scene m.scene = scene
} }
func (m *myRobot) GetCurrentScene() *scene { func (m *Robot) GetCurrentScene() *scene {
return m.scene return m.scene
} }
func (m *myRobot) Start() bool { func (m *Robot) Start() bool {
if m.scene == nil { if m.scene == nil {
logrus.Warn("选择一个测试场景") logrus.Warn("选择一个测试场景")
return false return false
@ -40,6 +73,112 @@ func (m *myRobot) Start() bool {
return false return false
} }
// 创建链接
dialer := &websocket.Dialer{
HandshakeTimeout: 2 * time.Second,
}
conn, _, err := dialer.Dial("", nil)
if err != nil {
logrus.Error(err)
return false
}
m.conn = conn
//检查具备启动的状态
if !atomic.CompareAndSwapUint32(
&m.status, STATUS_ORIGINAL, STATUS_STARTING) {
if !atomic.CompareAndSwapUint32(&m.status, STATUS_STOPPED, STATUS_STARTING) {
return false
}
}
//设置状态为已启动
atomic.StoreUint32(&m.status, STATUS_STARTED)
m.syncCall()
logrus.Debug("机器人运行了") logrus.Debug("机器人运行了")
return true return true
} }
func (m *Robot) syncCall() {
for {
caller, err := m.scene.callerQueue.Pop()
if err != nil {
return
}
req := caller.BuildReq(m, &pb.UserMessage{})
m.callOne(&req)
}
}
func (m *Robot) callOne(rawReq *RawReq) *RawResp {
start := time.Now().UnixNano()
rsp, err := m.call(rawReq)
end := time.Now().UnixNano()
elapsedTime := time.Duration(end - start)
var rawResp RawResp
if err != nil {
errMsg := fmt.Sprintf("Call Error: %s.", err)
rawResp = RawResp{
ID: rawReq.ID,
Err: errors.New(errMsg),
Elapse: elapsedTime}
} else {
rawResp = RawResp{
ID: rawReq.ID,
Resp: rsp,
Elapse: elapsedTime}
}
return &rawResp
}
func (m *Robot) call(rawReq *RawReq) ([]byte, error) {
m.conn.WriteMessage(websocket.BinaryMessage, rawReq.Req)
var (
data []byte
err error
)
for {
_, data, err = m.conn.ReadMessage()
if err != nil {
logrus.Errorf("readMessage err:%v", err)
return nil, err
}
if !m.checkPush(data) {
return data, nil
}
}
}
func (m *Robot) checkPush(data []byte) bool {
msg := &pb.UserMessage{}
if err := proto.Unmarshal(data, msg); err != nil {
logrus.Error("结果解析失败")
return false
}
methodStr := msg.Data.TypeUrl
methodName := SubStr(methodStr, 20, len(methodStr))
if strings.HasSuffix(methodName, "Push") {
if methodName == "NotifyErrorNotifyPush" {
push := &pb.NotifyErrorNotifyPush{}
if !ProtoUnmarshal(msg, push) {
logrus.Error("unmarsh err")
return false
}
logrus.WithField("methodName", methodName).WithField("code", push.Code).Debug("收到错误码")
} else {
logrus.WithField("methodName", methodName).Debug("收到推送")
}
return true
}
return false
}

View File

@ -7,7 +7,8 @@ type scene struct {
Name string Name string
Desc string Desc string
callerQueue *Queue[ICaller] //确定运行的caller队列 callerQueue *Queue[ICaller] //确定运行的caller队列
data map[string][]byte //caller缓存数据
status uint32 //场景状态
lock sync.Mutex lock sync.Mutex
} }
@ -18,8 +19,10 @@ func NewScene(ai *myAI, param SceneParam) *scene {
Name: param.Name, Name: param.Name,
Desc: param.Desc, Desc: param.Desc,
callerQueue: NewQueue[ICaller](), callerQueue: NewQueue[ICaller](),
data: make(map[string][]byte),
} }
ai.scenes = append(ai.scenes, s)
return s return s
} }
@ -41,3 +44,23 @@ func (s *scene) PopCaller() ICaller {
} }
return action return action
} }
// 启用场景
func (s *scene) Enable(name string) {
for _, v := range s.ai.scenes {
if v.Name == name {
v.status = STATUS_ENABLE
}else{
v.status = STATUS_DISENABLE
}
}
}
//禁用场景
func (s *scene) Disable(name string) {
for _, v := range s.ai.scenes {
if v.Name == name {
v.status = STATUS_DISENABLE
}
}
}

6
lib/store.go Normal file
View File

@ -0,0 +1,6 @@
package lib
type IStore interface {
Store(key string, data []byte)
Get(key string) []byte
}

76
lib/tickets.go Normal file
View File

@ -0,0 +1,76 @@
package lib
import (
"errors"
"fmt"
)
// Tickets 表示Goroutine票池的接口。
type Tickets interface {
// 拿走一张票。
Take()
// 归还一张票。
Return()
// 票池是否已被激活。
Active() bool
// 票的总数。
Total() uint32
// 剩余的票数。
Remainder() uint32
}
// myTickets 表示Goroutine票池的实现。
type myTickets struct {
total uint32 // 票的总数。
ticketCh chan struct{} // 票的容器。
active bool // 票池是否已被激活。
}
// NewTickets 会新建一个Goroutine票池。
func NewTickets(total uint32) (Tickets, error) {
gt := myTickets{}
if !gt.init(total) {
errMsg :=
fmt.Sprintf("The goroutine ticket pool can NOT be initialized! (total=%d)\n", total)
return nil, errors.New(errMsg)
}
return &gt, nil
}
func (gt *myTickets) init(total uint32) bool {
if gt.active {
return false
}
if total == 0 {
return false
}
ch := make(chan struct{}, total)
n := int(total)
for i := 0; i < n; i++ {
ch <- struct{}{}
}
gt.ticketCh = ch
gt.total = total
gt.active = true
return true
}
func (gt *myTickets) Take() {
<-gt.ticketCh
}
func (gt *myTickets) Return() {
gt.ticketCh <- struct{}{}
}
func (gt *myTickets) Active() bool {
return gt.active
}
func (gt *myTickets) Total() uint32 {
return gt.total
}
func (gt *myTickets) Remainder() uint32 {
return uint32(len(gt.ticketCh))
}

32
main.go
View File

@ -2,34 +2,52 @@ package main
import ( import (
"fmt" "fmt"
"image/color"
"io" "io"
"os" "os"
"fyne.io/fyne"
"fyne.io/fyne/canvas"
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"legu.airobot/busi/friend" "legu.airobot/busi/friend"
"legu.airobot/lib" "legu.airobot/lib"
"legu.airobot/theme" "legu.airobot/theme"
"legu.airobot/ui"
) )
var callers []lib.ICaller
func init() { func init() {
_ = os.Setenv("FYNE_SCALE", "0.9")
var err error var err error
if err = setupLogger(); err != nil { if err = setupLogger(); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)
} }
registerCalls(
&friend.FriendRecommend{},
&friend.FriendApply{},
)
} }
func main() { func main() {
app := app.NewWithID("aiRobot") app := app.NewWithID("aiRobot")
app.SetIcon(theme.ResourceAppPng) app.SetIcon(theme.ResourceAppPng)
a := lib.NewAI() appUI, err := ui.NewUI(app, callers)
a.InitCaller( if err != nil {
&friend.FriendRecommend{Desc: "好友推荐"}, w := fyne.CurrentApp().NewWindow("错误")
&friend.FriendApply{Desc: "好友申请"}, w.SetContent(canvas.NewText(err.Error(), color.RGBA{255, 0, 0, 255}))
) w.ShowAndRun()
}
win := ui.NewMainWdindow(appUI)
win.CreateWindow(ui.EMPTY_STR, 1499, 800, true)
appUI.Run()
} }
func setupLogger() (err error) { func setupLogger() (err error) {
@ -57,3 +75,7 @@ func setupLogger() (err error) {
} }
return nil return nil
} }
func registerCalls(params ...lib.ICaller) {
callers = append(callers, params...)
}

1
robot.log Normal file
View File

@ -0,0 +1 @@
{"level":"debug","msg":"配置","time":"2022-12-12 00:39:12","场景":[{"id":"551993000","name":"场景1","desc":"2132","callers":[{"id":"551993000_1670771588149928000","name":"232","key":"1"},{"id":"551993000_1670776426358376000","name":"好友推荐","key":"friend.randlist"}],"status":1},{"id":"376159000","name":"场景2"},{"id":"42611000","name":"场景3"}]}

34
storage/config.go Normal file
View File

@ -0,0 +1,34 @@
package storage
//默认配置
func newDefaultConfig() *Config {
return &Config{}
}
type Config struct {
Global *Global `json:"global"`
Scenes []*Scene `json:"scenes"`
}
type Global struct {
UserCount int32 `json:"UserCount,omitempty"` //用户数
SId string `json:"sid,omitempty"` //区服ID
WsAddr string `json:"wsAddr,omitempty"` //websocket addr
IntervalS int32 `json:"intervalS,omitempty"` //间隔时间s
TimeoutMs int32 `json:"timeoutMs,omitempty"` //超时时间
}
type Scene struct {
ID string `json:"id,omitempty"` //场景ID
Name string `json:"name,omitempty"` //场景名称
Desc string `json:"desc,omitempty"` //描述
Callers []*Caller `json:"callers,omitempty"` //调用器列表
Status uint32 `json:"status,omitempty"` //是否启用 默认0未启用 1是启用
}
type Caller struct {
ID string `json:"id,omitempty"` //调用器ID
Name string `json:"name,omitempty"` //调用器名称
Key string `json:"key,omitempty"` //标识
Num int `json:"num,omitempty"` //顺序号
}

29
storage/storage.go Normal file
View File

@ -0,0 +1,29 @@
package storage
import "path/filepath"
const (
storageRootName = "storage"
configFileName = "airobot.json"
ID = "zhaocy.df"
Version = "zhaocy/df/v1"
)
type Storage interface {
Root() string
ConfigStorage
}
type ConfigStorage interface {
LoadConfig() (*Config, error)
StoreConfig(s *Config) error
}
func storageRootPath(s Storage) string {
return filepath.Join(s.Root(), storageRootName)
}
func configPath(s Storage) string {
return filepath.Join(storageRootPath(s), configFileName)
}

117
storage/storage_os.go Normal file
View File

@ -0,0 +1,117 @@
package storage
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
var _ Storage = (*OSStorage)(nil)
type OSStorage struct {
root string
}
func NewOSStorage() (Storage, error) {
urd, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("不能获取默认的根目录 : %w", err)
}
return NewOSStorageRooted(urd)
}
func NewOSStorageRooted(root string) (Storage, error) {
if !filepath.IsAbs(root) {
return nil, fmt.Errorf("root必须是绝对路径:%s", root)
}
storageRoot := filepath.Join(root, ".df")
s := &OSStorage{root: storageRoot}
mighted, err := s.migrateDeprecatedRootStorage()
if mighted {
if err != nil {
return nil, fmt.Errorf("找到不推荐使用的存储,但无法移动到新位置:%w", err)
}
return s, nil
}
err = s.mkdirIfNotExists(storageRootPath(s))
return s, err
}
func (s *OSStorage) Root() string {
return s.root
}
func (s *OSStorage) LoadConfig() (*Config, error) {
configFile := configPath(s)
f, err := os.Open(configFile)
if os.IsNotExist(err) {
return newDefaultConfig(), nil
}
if err != nil {
return newDefaultConfig(), fmt.Errorf("没有读到 URI:%w", err)
}
defer f.Close()
config := &Config{}
err = json.NewDecoder(f).Decode(config)
if err != nil {
return newDefaultConfig(), err
}
return config, nil
}
func (s *OSStorage) StoreConfig(config *Config) error {
configFile := configPath(s)
w, err := s.createFile(configFile)
if err != nil {
return err
}
defer w.Close()
return json.NewEncoder(w).Encode(config)
}
func (s *OSStorage) createFile(name string) (*os.File, error) {
return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
}
func (s *OSStorage) isExist(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
func (s *OSStorage) mkdirIfNotExists(dir string) error {
if s.isExist(dir) {
return nil
}
return os.MkdirAll(dir, 0700)
}
// 合并弃用的根目录
func (s *OSStorage) migrateDeprecatedRootStorage() (bool, error) {
oldRoot, err := os.UserConfigDir()
if err != nil {
return false, err
}
src := filepath.Join(oldRoot, "fyne", ID, "vvv")
_, err = os.Stat(src)
if os.IsNotExist(err) {
return false, err
}
dest := storageRootPath(s)
err = os.Rename(src, dest)
if err != nil {
return false, err
}
return true, nil
}

View File

@ -1,6 +1,7 @@
package test package test
import ( import (
"fmt"
"testing" "testing"
"legu.airobot/busi/friend" "legu.airobot/busi/friend"
@ -8,10 +9,15 @@ import (
) )
func TestAction(t *testing.T) { func TestAction(t *testing.T) {
ai := lib.NewAI() aip := lib.AIParam{
RobotCount: 1,
}
ai, _ := lib.NewAI(aip)
//注册caller //注册caller
friend_recommend := &friend.FriendRecommend{}
friend_recommend.Desc = ""
ai.InitCaller( ai.InitCaller(
&friend.FriendRecommend{Desc: "好友推荐"}, friend_recommend,
) )
// 创建场景 // 创建场景
@ -21,10 +27,10 @@ func TestAction(t *testing.T) {
}) })
//为场景选择caller //为场景选择caller
scene.AddCaller(&friend.FriendRecommend{Desc: "好友推荐"}) scene.AddCaller(&friend.FriendRecommend{})
//加机器人 //加机器人
ai.AddRobots(1, scene) ai.AddRobot(scene)
//运行机器人 //运行机器人
for _, v := range ai.GetRobots("场景1") { for _, v := range ai.GetRobots("场景1") {
@ -32,3 +38,20 @@ func TestAction(t *testing.T) {
} }
} }
func TestA(t *testing.T) {
q := lib.NewQueue[int]()
q.Add(1)
q.Add(2)
// list := q.List()
for {
i, err := q.Pop()
if err != nil {
t.Error(err)
return
}
fmt.Println(i)
}
}

74
ui/common.go Normal file
View File

@ -0,0 +1,74 @@
package ui
import (
"image/color"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/driver/desktop"
fyne_storage "fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
)
// 显示Tip
func ShowTip(content string) {
drv := fyne.CurrentApp().Driver()
if drv, ok := drv.(desktop.Driver); ok {
w := drv.CreateSplashWindow()
w.SetContent(widget.NewLabelWithStyle(content, fyne.TextAlignCenter, fyne.TextStyle{}))
w.Show()
go func() {
time.Sleep(time.Millisecond * 1500)
w.Close()
}()
}
}
func ShowCanvasTip(content string) {
drv := fyne.CurrentApp().Driver()
if drv, ok := drv.(desktop.Driver); ok {
w := drv.CreateSplashWindow()
w.SetContent(canvas.NewText(content, color.RGBA{255, 0, 0, 255}))
w.Show()
go func() {
time.Sleep(time.Millisecond * 1500)
w.Close()
}()
}
}
// 打开目录
func OpenFolder(entry *widget.Entry, w fyne.Window) {
dConf := dialog.NewFolderOpen(func(lu fyne.ListableURI, err error) {
if lu == nil {
return
}
entry.Text = lu.Path()
entry.Refresh()
}, w)
luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI("."))
dConf.SetLocation(luri)
dConf.SetConfirmText("打开")
dConf.SetDismissText("取消")
dConf.Resize(fyne.NewSize(750, 500))
dConf.Show()
}
func OpenFile(entry *widget.Entry, w fyne.Window) {
dConf := dialog.NewFileOpen(func(lu fyne.URIReadCloser, err error) {
if lu == nil {
return
}
entry.SetText(lu.URI().Path())
entry.Refresh()
}, w)
dConf.SetConfirmText("打开")
dConf.SetDismissText("取消")
dConf.SetFilter(fyne_storage.NewExtensionFileFilter([]string{".json"}))
dConf.Resize(fyne.NewSize(750, 500))
dConf.Show()
}

9
ui/lang.go Normal file
View File

@ -0,0 +1,9 @@
package ui
const (
APP_NAME = "AI"
APP_WIN_TITLE = "%s [%s build-%d]"
EMPTY_STR = ""
)

70
ui/mainmenu.go Normal file
View File

@ -0,0 +1,70 @@
package ui
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
type mainMenu struct {
*fyne.MainMenu
sysMenu *fyne.Menu
startMenu *fyne.MenuItem //开始
configMenu *fyne.MenuItem //配置
sceneMenu *fyne.MenuItem //场景
quite *fyne.MenuItem
//工具
toolMenu *fyne.Menu
importCM *fyne.MenuItem //导入配置
exportCM *fyne.MenuItem //导出配置
}
func newMainMenu() *mainMenu {
var mm mainMenu
//退出
mm.quite = fyne.NewMenuItem("退出", globalWindow.quiteHandle)
mm.quite.Icon = theme.LogoutIcon()
mm.quite.IsQuit = true
// 开始
mm.startMenu = fyne.NewMenuItem("开始", globalWindow.startContainer)
mm.startMenu.Icon = theme.FileIcon()
// 配置
mm.configMenu = fyne.NewMenuItem("配置", globalWindow.configContainer)
mm.configMenu.Icon = theme.FileTextIcon()
//场景
mm.sceneMenu = fyne.NewMenuItem("场景", globalWindow.sceneContainer)
mm.sceneMenu.Icon = theme.FileTextIcon()
// AI
mm.sysMenu = fyne.NewMenu("AI")
mm.sysMenu.Items = append(mm.sysMenu.Items,
mm.startMenu,
mm.configMenu,
mm.sceneMenu,
fyne.NewMenuItemSeparator(),
mm.quite)
// 导入配置
mm.importCM = fyne.NewMenuItem("导入配置", globalWindow.ImportConfigWindow)
mm.importCM.Icon = theme.FileApplicationIcon()
mm.exportCM = fyne.NewMenuItem("导出配置", globalWindow.ExportConfigWindow)
mm.exportCM.Icon = theme.FileApplicationIcon()
// 工具
mm.toolMenu = fyne.NewMenu("工具")
mm.toolMenu.Items = append(mm.toolMenu.Items,
mm.importCM,
mm.exportCM,
)
mm.MainMenu = fyne.NewMainMenu(
mm.sysMenu,
mm.toolMenu,
)
return &mm
}

637
ui/mainwindow.go Normal file
View File

@ -0,0 +1,637 @@
package ui
import (
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"path/filepath"
"sort"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus"
"github.com/spf13/cast"
"legu.airobot/lib"
"legu.airobot/storage"
)
var globalWindow *MainWindow
var _ IWindow = (*MainWindow)(nil)
type MainWindow struct {
UIImpl
w fyne.Window
statusbar *statusBar // 状态栏
mainMenu *mainMenu //菜单
}
func NewMainWdindow(ui *UIImpl) IWindow {
mw := &MainWindow{
UIImpl: *ui,
}
globalWindow = mw
return mw
}
func (mw *MainWindow) CreateWindow(_ string, width, height float32, visible bool) {
title := fmt.Sprintf(APP_WIN_TITLE, APP_NAME, mw.app.Metadata().Version, mw.app.Metadata().Build)
w := mw.app.NewWindow(title)
mw.w = w
// 状态栏
mw.statusbar = newStatusBar()
// 菜单
mw.mainMenu = newMainMenu()
mw.w.SetMainMenu(mw.mainMenu.MainMenu)
//content
content := container.NewBorder(nil, mw.statusbar.widget, nil, nil)
mw.w.SetContent(content)
// 默认
mw.startContainer()
w.Resize(fyne.NewSize(width, height))
w.SetMaster()
w.CenterOnScreen()
w.SetCloseIntercept(mw.quiteHandle)
w.Show()
}
func (mw *MainWindow) changeContent(content fyne.CanvasObject) {
mw.w.SetContent(content)
}
// 开始
func (mw *MainWindow) startContainer() {
startBtn := widget.NewButton("启动", nil)
startBtn.Disable()
config := mw.config
// 检查全局配置
if config.Global == nil {
dialog.ShowCustomConfirm("提示", "设置", "取消", widget.NewLabel("还没有任何配置"), func(b bool) {
if !b {
return
}
mw.configContainer()
}, mw.w)
return
}
// 检查场景配置
if config.Scenes == nil {
dialog.ShowCustomConfirm("提示", "创建", "取消", widget.NewLabel("还没有创建任何场景"), func(b bool) {
if !b {
return
}
mw.sceneContainer()
}, mw.w)
return
}
// 检查场景是否启用
isEnable := false
for _, v := range config.Scenes {
if v.Status == lib.STATUS_ENABLE {
isEnable = true
break
}
}
if !isEnable {
dialog.ShowCustomConfirm("提示", "启用", "取消", widget.NewLabel("还没有启用任意场景"), func(b bool) {
if !b {
return
}
mw.sceneContainer()
}, mw.w)
return
}
startBtn.Enable()
startBtn.OnTapped = func() {
ai, err := lib.NewAI(lib.AIParam{RobotCount: uint32(config.Global.UserCount)})
if err != nil {
dialog.ShowError(err, mw.w)
return
}
ai.InitCaller(mw.callers...)
}
content := container.NewCenter(startBtn)
mw.changeContent(content)
}
// 全局配置
func (mw *MainWindow) configContainer() {
config := mw.config.Global
wsAddrEntry := widget.NewEntry()
sidEntry := widget.NewEntry()
userCountEntry := widget.NewEntry()
timeoutEntry := widget.NewEntry()
intervalEntry := widget.NewEntry()
form := widget.NewForm(
widget.NewFormItem("服务地址", wsAddrEntry),
widget.NewFormItem("区服编号", sidEntry),
widget.NewFormItem("用户数", userCountEntry),
widget.NewFormItem("间隔时间(s)", intervalEntry),
widget.NewFormItem("超时时间(ms)", timeoutEntry),
)
loadForm := func() {
wsAddrEntry.Text = config.WsAddr
sidEntry.Text = config.SId
userCountEntry.Text = cast.ToString(config.UserCount)
timeoutEntry.Text = cast.ToString(config.TimeoutMs)
intervalEntry.Text = cast.ToString(config.IntervalS)
}
if config != nil {
loadForm()
}
saveBtn := widget.NewButtonWithIcon("保存", theme.DocumentSaveIcon(), nil)
saveBtn.OnTapped = func() {
global := &storage.Global{
WsAddr: wsAddrEntry.Text,
SId: sidEntry.Text,
UserCount: cast.ToInt32(userCountEntry.Text),
TimeoutMs: cast.ToInt32(timeoutEntry.Text),
IntervalS: cast.ToInt32(intervalEntry.Text),
}
mw.config.Global = global
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
}
btns := container.NewHBox(layout.NewSpacer(), saveBtn)
content := container.NewVBox(form, btns)
mw.changeContent(content)
}
//场景
func (mw *MainWindow) sceneContainer() {
var selSceneName string
// 用例列表
callerItemList := *lib.NewItemList()
callerItemList.ListWidget = callerItemList.CreateList()
callerLoad := func(sceneId string) {
callerItemList.Reset()
for _, v := range mw.config.Scenes {
if v.ID == sceneId {
sort.SliceStable(v.Callers, func(i, j int) bool {
return v.Callers[i].Num < v.Callers[j].Num
})
for _, v2 := range v.Callers {
item := lib.Item{
Id: cast.ToString(v2.ID),
Text: fmt.Sprintf("%d. %v %v", v2.Num, v2.Name, v2.Key),
}
callerItemList.AddItem(item)
}
}
}
}
//场景列表
sceneItemList := *lib.NewItemList()
sceneItemList.OnSelFunc = func(id widget.ListItemID) {
selId := sceneItemList.CachedList.Items[id].Id
selSceneName = sceneItemList.CachedList.Items[id].Text
sceneItemList.SelItemId = selId
callerLoad(selId)
}
sceneItemList.ListWidget = sceneItemList.CreateList()
sceneLoad := func() {
sceneItemList.Reset()
logrus.WithField("场景", mw.config.Scenes).Debug("配置")
for _, v := range mw.config.Scenes {
txt := fmt.Sprintf("%v", v.Name)
if v.Status == lib.STATUS_ENABLE {
txt = fmt.Sprintf("%s *", v.Name)
}
item := lib.Item{
Id: cast.ToString(v.ID),
Text: txt,
}
sceneItemList.AddItem(item)
}
}
// 场景表单
sceneNameEntry := widget.NewEntry()
sceneDescEntry := widget.NewEntry()
sceneItems := []*widget.FormItem{
widget.NewFormItem("名称", sceneNameEntry),
widget.NewFormItem("描述", sceneDescEntry),
}
//重置场景表单
sceneFormReset := func() {
sceneNameEntry.Text = ""
sceneDescEntry.Text = ""
}
// 用例表单
callerNameEntry := widget.NewEntry()
callerKeyEntry := widget.NewEntry()
callerNumEntry := widget.NewEntry()
callerNumEntry.PlaceHolder = "0"
callerItems := []*widget.FormItem{
widget.NewFormItem("名称", callerNameEntry),
widget.NewFormItem("协议", callerKeyEntry),
widget.NewFormItem("序号", callerNumEntry),
}
//重置用例表单
callerFormReset := func() {
callerNameEntry.Text = ""
callerKeyEntry.Text = ""
}
// 场景按钮
newSceneBtn := widget.NewButtonWithIcon("新建场景", theme.FileIcon(), nil)
editSceneBtn := widget.NewButtonWithIcon("编辑场景", theme.DocumentCreateIcon(), nil)
deleSceneBtn := widget.NewButtonWithIcon("删除场景", theme.DeleteIcon(), nil)
enableSceneBtn := widget.NewButton("启用场景", nil)
// 添加用例按钮
newCallerBtn := widget.NewButtonWithIcon("添加用例", theme.FileIcon(), nil)
deleCallerBtn := widget.NewButtonWithIcon("删除用例", theme.DocumentCreateIcon(), nil)
// 刷新场景
refreshBtn := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), nil)
// 工具条
toolbar := container.NewHBox(newSceneBtn, editSceneBtn, deleSceneBtn, enableSceneBtn, widget.NewSeparator(),
newCallerBtn, deleCallerBtn, layout.NewSpacer(), refreshBtn)
//布局
var dynamic fyne.CanvasObject
contentRender := func() {
sceneLoad()
if len(sceneItemList.CachedList.Items) == 0 {
dynamic = container.NewCenter(widget.NewLabel("还没有创建任何场景"))
} else {
split := container.NewHSplit(sceneItemList.ListWidget, callerItemList.ListWidget)
split.Offset = 0.3
dynamic = split
}
content := container.NewBorder(toolbar, nil, nil, nil, dynamic)
mw.changeContent(content)
}
contentRender()
// 刷新场景
refreshBtn.OnTapped = func() {
sceneItemList.Reset()
callerItemList.Reset()
contentRender()
}
//新建场景事件
newSceneBtn.OnTapped = func() {
newSceneWin := dialog.NewForm("新建场景", "确定", "取消", sceneItems, func(b bool) {
if !b {
return
}
scenes_conf := mw.config.Scenes
scenes_conf = append(scenes_conf, &storage.Scene{
ID: cast.ToString(time.Now().Nanosecond()),
Name: sceneNameEntry.Text,
Desc: sceneDescEntry.Text,
})
mw.config.Scenes = scenes_conf
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
sceneFormReset()
contentRender()
}, mw.w)
newSceneWin.Resize(fyne.NewSize(300, 200))
newSceneWin.Show()
}
// 编辑场景事件
editSceneBtn.OnTapped = func() {
selId := sceneItemList.SelItemId
if selId == "" {
ShowTip("请选择一个场景")
return
}
// 加载
var sceneConf *storage.Scene
for _, item := range mw.config.Scenes {
if item.ID == selId {
sceneConf = item
}
}
if sceneConf != nil {
sceneNameEntry.Text = sceneConf.Name
sceneDescEntry.Text = sceneConf.Desc
}
editSceneWin := dialog.NewForm("编辑场景", "确定", "取消", sceneItems, func(b bool) {
if !b {
return
}
for _, item := range mw.config.Scenes {
if item.ID == selId {
item.Name = sceneNameEntry.Text
item.Desc = sceneDescEntry.Text
}
}
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
sceneFormReset()
// contentRender()
}, mw.w)
editSceneWin.Resize(fyne.NewSize(300, 200))
editSceneWin.Show()
}
// 删除场景事件
deleSceneBtn.OnTapped = func() {
selId := sceneItemList.SelectedId()
if selId == "" {
ShowTip("请选择一个场景")
return
}
scenes := mw.config.Scenes
for i, item := range scenes {
if item.ID == selId {
if len(item.Callers) > 0 {
dialog.ShowCustomConfirm("提示", "确定", "取消", widget.NewLabel("该场景下存在用例,确定删除场景【"+item.Name+"】吗?"), func(b bool) {
if !b {
return
}
for y, v := range scenes {
if v.ID == selId {
scenes = append(scenes[:y], scenes[y+1:]...)
}
}
mw.config.Scenes = scenes
callerItemList.Reset()
contentRender()
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
}, mw.w)
} else {
scenes = append(scenes[:i], scenes[i+1:]...)
mw.config.Scenes = scenes
callerItemList.Reset()
contentRender()
}
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
}
}
}
// 启用场景
enableSceneBtn.OnTapped = func() {
selId := sceneItemList.SelectedId()
if selId == "" {
ShowTip("请选择一个场景")
return
}
for _, item := range mw.config.Scenes {
if item.ID == selId {
if len(item.Callers) == 0 {
ShowTip("请至少添加一个用例")
return
}
item.Status = lib.STATUS_ENABLE
} else {
item.Status = lib.STATUS_DISENABLE
}
}
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
sceneItemList.Reset()
}
//添加用例事件
newCallerBtn.OnTapped = func() {
selId := sceneItemList.SelectedId()
if selId == "" {
ShowTip("请选择一个场景")
return
}
//form
callerForm := widget.NewForm(callerItems...)
callerNameEntry.Disable()
callerKeyEntry.Disable()
callerForm.OnSubmit = func() {
scenes_conf := mw.config.Scenes
for _, v := range scenes_conf {
if selId == v.ID {
v.Callers = append(v.Callers, &storage.Caller{
ID: fmt.Sprintf("%s_%d", v.ID, time.Now().UnixNano()),
Name: callerNameEntry.Text,
Key: callerKeyEntry.Text,
Num: cast.ToInt(callerNumEntry.Text),
})
}
}
mw.config.Scenes = scenes_conf
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
callerFormReset()
callerLoad(selId)
}
callerForm.SubmitText = "确定"
//list
registerCallerList := *lib.NewItemList()
registerCallerList.OnSelFunc = func(id widget.ListItemID) {
data := registerCallerList.CachedList.Items[id].Data
info, ok := data.(lib.CallerInfo)
if !ok {
return
}
callerNameEntry.Text = info.Desc
callerKeyEntry.Text = info.Key
callerForm.Refresh()
}
registerCallerList.ListWidget = registerCallerList.CreateList()
registerCallerReload := func() {
for _, v := range mw.UIImpl.callers {
info := v.ShowInfo()
item := lib.Item{
Id: info.Key,
Text: fmt.Sprintf("%s (%s)", info.Desc, info.Key),
Data: info,
}
registerCallerList.AddItem(item)
}
registerCallerList.SearchItem = registerCallerList.CachedList.Items
}
registerCallerReload()
//search
searchEntry := widget.NewEntry()
searchEntry.PlaceHolder = "输入要搜索的用例"
searchEntry.OnChanged = func(s string) {
if s == "" {
registerCallerReload()
} else {
newList := []lib.Item{}
for _, v := range registerCallerList.SearchItem {
if strings.Contains(v.Text, s) {
newList = append(newList, v)
}
}
registerCallerList.CachedList.Items = newList
registerCallerList.ListWidget.Refresh()
}
}
// layout
contentSplit := container.NewHSplit(
container.NewBorder(container.NewBorder(nil, nil, nil, nil, searchEntry),
nil, nil, nil, registerCallerList.ListWidget),
callerForm,
)
contentSplit.Offset = 0.5
title := fmt.Sprintf("新建用例 - %s", selSceneName)
newCallerWin := dialog.NewCustom(title, "关闭", contentSplit, mw.w)
newCallerWin.Resize(fyne.NewSize(800, 600))
newCallerWin.Show()
}
// 删除用例事件
deleCallerBtn.OnTapped = func() {
selSceneId := sceneItemList.SelectedId()
if selSceneId == "" {
ShowTip("请选择一个场景")
return
}
selCallerId := callerItemList.SelectedId()
if selCallerId == "" {
ShowTip("请选择一个用例")
return
}
scenes := mw.config.Scenes
for _, item := range scenes {
if item.ID == selSceneId {
for y, c := range item.Callers {
if c.ID == selCallerId {
item.Callers = append(item.Callers[:y], item.Callers[y+1:]...)
}
}
}
}
mw.config.Scenes = scenes
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
callerLoad(selSceneId)
}
}
// 导出配置
func (mw *MainWindow) ExportConfigWindow() {
dirEntry := widget.NewEntry()
exportItems := []*widget.FormItem{
widget.NewFormItem("保存目录", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FolderIcon(), func() {
OpenFolder(dirEntry, mw.w)
}), dirEntry)),
}
exportWin := dialog.NewForm("导出配置", "确定", "取消", exportItems, func(b bool) {
if !b {
return
}
dirpath := dirEntry.Text
logrus.WithField("目录位置", dirpath).Debug("保存目录")
jsonByte, err := json.Marshal(mw.config)
if err != nil {
logrus.Errorln(err)
return
}
if err := ioutil.WriteFile(filepath.Join(dirpath, "config.json"), jsonByte, fs.ModePerm); err != nil {
logrus.Error(err)
return
}
}, mw.w)
exportWin.Resize(fyne.NewSize(300, 80))
exportWin.Show()
}
// 导入配置
func (mw *MainWindow) ImportConfigWindow() {
filepathEntry := widget.NewEntry()
importItems := []*widget.FormItem{
widget.NewFormItem("配置文件", container.NewBorder(nil, nil, nil, widget.NewButtonWithIcon("", theme.FileIcon(), func() {
OpenFile(filepathEntry, mw.w)
}), filepathEntry)),
}
importWin := dialog.NewForm("导入配置", "确定", "取消", importItems, func(b bool) {
if !b {
return
}
filepath := filepathEntry.Text
logrus.WithField("文件路径", filepath).Debug("导入文件")
byte, err := ioutil.ReadFile(filepath)
if err != nil {
logrus.Error(err)
return
}
if err := json.Unmarshal(byte, mw.config); err != nil {
logrus.Error(err)
return
}
if err := mw.storage.StoreConfig(mw.config); err != nil {
ShowTip(fmt.Sprintf("错误:%v", err.Error()))
return
}
}, mw.w)
importWin.Resize(fyne.NewSize(300, 80))
importWin.Show()
}
// 退出
func (mw *MainWindow) quiteHandle() {
dialog.ShowCustomConfirm("提示", "确定", "取消", widget.NewLabel("确定退出吗"), func(b bool) {
if !b {
return
}
mw.app.Quit()
}, mw.w)
}

26
ui/status_bar.go Normal file
View File

@ -0,0 +1,26 @@
package ui
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
type statusBar struct {
msgLabel *widget.Label
widget *fyne.Container
}
func newStatusBar() *statusBar {
var sb statusBar
sb.msgLabel = widget.NewLabel("")
sb.widget = container.New(layout.NewHBoxLayout(),
sb.msgLabel,
layout.NewSpacer())
return &sb
}
func (sb *statusBar) setMessage(msg string) {
sb.msgLabel.SetText(msg)
}

55
ui/ui.go Normal file
View File

@ -0,0 +1,55 @@
package ui
import (
"os"
"fyne.io/fyne/v2"
"github.com/sirupsen/logrus"
"legu.airobot/lib"
"legu.airobot/storage"
"legu.airobot/theme"
)
type UIImpl struct {
app fyne.App
storage storage.Storage
config *storage.Config
callers []lib.ICaller
}
func NewUI(app fyne.App, callers []lib.ICaller) (*UIImpl, error) {
app.Settings().SetTheme(&theme.MyTheme{})
storage, err := storage.NewOSStorage()
if err != nil {
logrus.Errorf("new storage err:%v", err)
return nil, err
}
// 加载配置
config, err := storage.LoadConfig()
if err != nil {
logrus.Errorf("Load config err:%v", err)
return nil, err
}
return &UIImpl{
app: app,
storage: storage,
config: config,
callers: callers,
}, nil
}
func (ui *UIImpl) Run() {
defer func() {
_ = os.Unsetenv("FYNE_SCALE")
}()
ui.app.Run()
}
func (ui *UIImpl) Stop() {
ui.app.Quit()
}

5
ui/windowinterface.go Normal file
View File

@ -0,0 +1,5 @@
package ui
type IWindow interface {
CreateWindow(title string, width, height float32, visible bool)
}