diff --git a/cmd/v2/configure/structs/Game.TestFlow.go b/cmd/v2/configure/structs/Game.TestFlow.go new file mode 100644 index 000000000..b8946f191 --- /dev/null +++ b/cmd/v2/configure/structs/Game.TestFlow.go @@ -0,0 +1,42 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +package cfg + +type GameTestFlow struct { + _dataMap map[int32]*GameTestFlowData + _dataList []*GameTestFlowData +} + +func NewGameTestFlow(_buf []map[string]interface{}) (*GameTestFlow, error) { + _dataList := make([]*GameTestFlowData, 0, len(_buf)) + dataMap := make(map[int32]*GameTestFlowData) + for _, _ele_ := range _buf { + if _v, err2 := DeserializeGameTestFlowData(_ele_); err2 != nil { + return nil, err2 + } else { + _dataList = append(_dataList, _v) + dataMap[_v.Id] = _v + } + } + return &GameTestFlow{_dataList:_dataList, _dataMap:dataMap}, nil +} + +func (table *GameTestFlow) GetDataMap() map[int32]*GameTestFlowData { + return table._dataMap +} + +func (table *GameTestFlow) GetDataList() []*GameTestFlowData { + return table._dataList +} + +func (table *GameTestFlow) Get(key int32) *GameTestFlowData { + return table._dataMap[key] +} + + diff --git a/cmd/v2/configure/structs/Game.TestFlowData.go b/cmd/v2/configure/structs/Game.TestFlowData.go new file mode 100644 index 000000000..c41d92c1a --- /dev/null +++ b/cmd/v2/configure/structs/Game.TestFlowData.go @@ -0,0 +1,41 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +package cfg + +import "errors" + +type GameTestFlowData struct { + Id int32 + Msg string + Route string + Params string +} + +const TypeId_GameTestFlowData = -1087831770 + +func (*GameTestFlowData) GetTypeId() int32 { + return -1087831770 +} + +func (_v *GameTestFlowData)Deserialize(_buf map[string]interface{}) (err error) { + { var _ok_ bool; var _tempNum_ float64; if _tempNum_, _ok_ = _buf["id"].(float64); !_ok_ { err = errors.New("id error"); return }; _v.Id = int32(_tempNum_) } + { var _ok_ bool; if _v.Msg, _ok_ = _buf["msg"].(string); !_ok_ { err = errors.New("msg error"); return } } + { var _ok_ bool; if _v.Route, _ok_ = _buf["route"].(string); !_ok_ { err = errors.New("route error"); return } } + { var _ok_ bool; if _v.Params, _ok_ = _buf["params"].(string); !_ok_ { err = errors.New("params error"); return } } + return +} + +func DeserializeGameTestFlowData(_buf map[string]interface{}) (*GameTestFlowData, error) { + v := &GameTestFlowData{} + if err := v.Deserialize(_buf); err == nil { + return v, nil + } else { + return nil, err + } +} diff --git a/cmd/v2/configure/structs/Tables.go b/cmd/v2/configure/structs/Tables.go new file mode 100644 index 000000000..077e3ee0a --- /dev/null +++ b/cmd/v2/configure/structs/Tables.go @@ -0,0 +1,31 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +package cfg + +type JsonLoader func(string) ([]map[string]interface{}, error) + +type Tables struct { + TestFlow *GameTestFlow +} + +func NewTables(loader JsonLoader) (*Tables, error) { + var err error + var buf []map[string]interface{} + + tables := &Tables{} + + if buf, err = loader("game_testflow") ; err != nil { + return nil, err + } + if tables.TestFlow, err = NewGameTestFlow(buf) ; err != nil { + return nil, err + } + + return tables, nil +} diff --git a/cmd/v2/game_testflow.json b/cmd/v2/game_testflow.json new file mode 100644 index 000000000..ddd783702 --- /dev/null +++ b/cmd/v2/game_testflow.json @@ -0,0 +1,44 @@ +[ + { + "id": 1, + "msg": "功能列表", + "route": "sys.funclist", + "params": "{}" + }, + { + "id": 2, + "msg": "", + "route": "", + "params": "" + }, + { + "id": 3, + "msg": "", + "route": "", + "params": "" + }, + { + "id": 4, + "msg": "", + "route": "", + "params": "" + }, + { + "id": 5, + "msg": "", + "route": "", + "params": "" + }, + { + "id": 6, + "msg": "", + "route": "", + "params": "" + }, + { + "id": 7, + "msg": "", + "route": "", + "params": "" + } +] \ No newline at end of file diff --git a/cmd/v2/lib/assistant.go b/cmd/v2/lib/assistant.go new file mode 100644 index 000000000..b7ebb9935 --- /dev/null +++ b/cmd/v2/lib/assistant.go @@ -0,0 +1,255 @@ +package lib + +import ( + "context" + "errors" + "fmt" + "go_dreamfactory/pb" + "math" + "sync/atomic" + "time" + + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" +) + +type assistant struct { + timeout time.Duration //处理超时时间 + lps uint32 //每秒请求量 + duration time.Duration //持续时间 + concurrency uint32 //并发量 + + ctx context.Context + callCount int64 //调用次数,每次启动时重置 + + goPool GoPool //携程池 + cancelFunc context.CancelFunc //取消 + caller Handler //处理器 + status uint32 //状态 + resultCh chan *CallResult //调用结果 +} + +func NewAssistant(pm ParamMgr) (Aiassistant, error) { + + if err := pm.Check(); err != nil { + return nil, err + } + + a := &assistant{ + timeout: pm.Timeout, + lps: pm.Lps, + duration: pm.Duration, + caller: pm.Caller, + status: STATUS_ORIGINAL, + resultCh: pm.ResultCh, + } + if err := a.init(); err != nil { + return nil, err + } + return a, nil +} + +func (a *assistant) init() error { + logrus.Info("AI助手初始化") + //并发量的计算 + //并发量 ≈ 超时时间 / 发送的间隔时间 + var total = int64(a.timeout)/int64(1e9/a.lps) + 1 + if total > math.MaxInt32 { + total = math.MaxInt32 + } + + a.concurrency = uint32(total) + gp, err := NewGoPool(a.concurrency) + if err != nil { + return err + } + + a.goPool = gp + logrus.WithField("并发量", a.concurrency).Info("AI助手初始化完成 并发量 ") + return nil +} + +func (a *assistant) callOne(req *RawReq) *RawResp { + atomic.AddInt64(&a.callCount, 1) + if req == nil { + return &RawResp{ID: -1, Err: errors.New("无效的请求")} + } + var rawResp RawResp + start := time.Now().UnixNano() + resp, err := a.caller.Call(req.Req, a.timeout) + end := time.Now().UnixNano() + elapsedTime := time.Duration(end - start) + if err != nil { + errMsg := fmt.Sprintf("调用失败: %v", err) + rawResp = RawResp{ + ID: req.ID, + Err: errors.New(errMsg), + Elapse: elapsedTime, + } + } else { + rawResp = RawResp{ + ID: req.ID, + Resp: resp, + Elapse: elapsedTime, + } + } + return &rawResp +} + +// 异步调用接口 +func (a *assistant) asyncCall() { + a.goPool.Take() + go func() { + defer func() { + a.goPool.Return() + }() + + req := a.caller.BuildReq() + //调用状态 0未调用 1调用结束 2调用超时 + var callStatus uint32 + + // 超时处理 + timer := time.AfterFunc(a.timeout, func() { + if !atomic.CompareAndSwapUint32(&callStatus, 0, 2) { + return + } + + result := &CallResult{ + Id: req.ID, + Req: req, + Code: RES_CODE_CALL_TIMEOUT, + Message: fmt.Sprintf("超时,期望< %v", a.timeout), + Elapse: a.timeout, + } + a.sendResult(result) + }) + resp := a.callOne(&req) + logrus.WithField("耗时", resp.Elapse).Debug("实际耗时") + if !atomic.CompareAndSwapUint32(&callStatus, 0, 1) { + return + } + + timer.Stop() + + var result *CallResult + if resp.Err != nil { + result = &CallResult{ + Id: req.ID, + Req: req, + Code: RES_CODE_ERROR_CALL, + Message: resp.Err.Error(), + Elapse: resp.Elapse, + } + } else { + result = a.caller.Check(req, *resp) + result.Elapse = resp.Elapse + } + a.sendResult(result) + }() + +} + +// 停止发送 +func (a *assistant) prepareStop(ctxErr error) { + logrus.WithField("cause", ctxErr).Info("准备停止") + atomic.CompareAndSwapUint32(&a.status, STATUS_STARTED, STATUS_STOPPING) + logrus.Info("关闭结果通道") + close(a.resultCh) + atomic.StoreUint32(&a.status, STATUS_STOPPED) +} + +// 发送请求即调用接口 +func (a *assistant) handleReq(tick <-chan time.Time) { + for { + select { + case <-a.ctx.Done(): + a.prepareStop(a.ctx.Err()) + return + default: + } + a.asyncCall() + if a.lps > 0 { + select { + case <-tick: + case <-a.ctx.Done(): + a.prepareStop(a.ctx.Err()) + return + } + } + } +} + +func (a *assistant) sendResult(result *CallResult) bool { + if atomic.LoadUint32(&a.status) != STATUS_STARTED { + return false + } + select { + case a.resultCh <- result: + return true + default: + return false + } +} + +//注册账号 +func (a *assistant) registUser() { + +} + +//登录账号 +func (a *assistant) login() { + +} + +// 启动AI助手 +func (a *assistant) Start() bool { + logrus.Infoln("AI助手启动") + + // 节流 周期性向目标发送 + var ticker <-chan time.Time + if a.lps > 0 { + //间隔时间 + interval := time.Duration(1e9 / a.lps) + logrus.Infof("启动节流控制 间隔: %v", interval) + ticker = time.Tick(interval) + } + + // 初始化上下文和设置取消函数 + a.ctx, a.cancelFunc = context.WithTimeout(context.Background(), a.duration) + + // 重置调用次数 + a.callCount = 0 + + // 设置状态为已启动 + atomic.StoreUint32(&a.status, STATUS_STARTED) + + go func() { + logrus.Infoln("请求处理...") + a.handleReq(ticker) + logrus.Infof("停止 调用次数:%d", a.callCount) + }() + + return true +} + +// 手动停止 +func (a *assistant) Stop() error { + return nil +} + +func (a *assistant) ShowResult() { + + for r := range a.resultCh { + if r.Code != RES_CODE_SUCCESS { + logrus.WithField("result", r.Code).Debug("失败的结果") + continue + } + msg := &pb.UserMessage{} + // logrus.Debugf("结果字节长度 %d", len(r.Resp.Resp)) + if err := proto.Unmarshal(r.Resp.Resp, msg); err != nil { + logrus.Error("结果解析失败") + continue + } + logrus.WithFields(logrus.Fields{"mainType": msg.MainType, "subType": msg.SubType}).Debug("读取结果") + } +} diff --git a/cmd/v2/lib/base.go b/cmd/v2/lib/base.go new file mode 100644 index 000000000..96836b094 --- /dev/null +++ b/cmd/v2/lib/base.go @@ -0,0 +1,40 @@ +package lib + +import "time" + +type RawReq struct { + ID int64 + Req []byte +} + +type RawResp struct { + ID int64 + Resp []byte + Err error + Elapse time.Duration +} + +type ResCode int + +const ( + RES_CODE_SUCCESS ResCode = 0 //成功 + RES_CODE_CALL_TIMEOUT = 1001 //调用超时 + RES_CODE_ERROR_CALL = 2001 // 调用错误 + RES_CODE_ERROR_RESPONSE = 2002 // 响应错误 + RES_CODE_ERROR_CALEE = 2003 // 被测软件内部错误 + RES_CODE_FATAL_CALL = 3001 // 调用过程中发生了致命错误! +) + +//助手执行状态 +const ( + // 原始状态 + STATUS_ORIGINAL uint32 = 0 + // 正在启动 + STATUS_STARTING uint32 = 1 + //已启动 + STATUS_STARTED uint32 = 2 + //正在停止 + STATUS_STOPPING uint32 = 3 + // 已停止 + STATUS_STOPPED uint32 = 4 +) diff --git a/cmd/v2/lib/common/constant.go b/cmd/v2/lib/common/constant.go index e7a7e56a2..cdd9ecfe0 100644 --- a/cmd/v2/lib/common/constant.go +++ b/cmd/v2/lib/common/constant.go @@ -6,6 +6,12 @@ type ( WindowAction int64 ) +const ( + MainType = "mainType" + SubType = "subType" + Params = "params" +) + const ( WindowAspect_Normal WindowAspect = iota WindowAspect_FullScreen diff --git a/cmd/v2/lib/common/lang.go b/cmd/v2/lib/common/lang.go index bb0f3ed4b..462fe4eec 100644 --- a/cmd/v2/lib/common/lang.go +++ b/cmd/v2/lib/common/lang.go @@ -71,6 +71,7 @@ package common // zh const ( APP_NAME = "机器人" + // app 子标题 [0.0.1 build-1] 应用名称 APP_WIN_TITLE = "%s [%s build-%d] %s" WELCOME_MSG = "欢迎使用工具箱" @@ -94,6 +95,11 @@ const ( TOOLBAR_WEL = "欢迎" TOOLBAR_TERM = "同步配置" TOOLBAR_PB = "protobuf" + TOOLBAR_AUTO = "自动化" + + TOOLBAR_PERF_TIP = "使用说明" + TOOLBAR_PERF_CONF = "配置" + TOOLBAR_PERF_PB = "协议" //monitor APP_MONITOR_TITLE_ID = "编号" diff --git a/cmd/v2/lib/common/log.go b/cmd/v2/lib/common/log.go new file mode 100644 index 000000000..8bc99cdff --- /dev/null +++ b/cmd/v2/lib/common/log.go @@ -0,0 +1,10 @@ +package common + +type LogData struct { + Level string `json:"level"` + MainType string `json:"mainType"` + SubType string `json:"subType"` + Time string `json:"time"` + Msg string `json:"msg"` + Params interface{} `json:"params"` +} diff --git a/cmd/v2/lib/common/utils.go b/cmd/v2/lib/common/utils.go index b524d658e..9afc4134d 100644 --- a/cmd/v2/lib/common/utils.go +++ b/cmd/v2/lib/common/utils.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io" + "io/ioutil" "math" "os" "path/filepath" @@ -156,3 +157,15 @@ func ConvertFileSize(size int64) string { return "0" } + +func Loader(file string) ([]map[string]interface{}, error) { + if bytes, err := ioutil.ReadFile("./" + file + ".json"); err != nil { + return nil, err + } else { + jsonData := make([]map[string]interface{}, 0) + if err = json.Unmarshal(bytes, &jsonData); err != nil { + return nil, err + } + return jsonData, nil + } +} diff --git a/cmd/v2/lib/core.go b/cmd/v2/lib/core.go new file mode 100644 index 000000000..be87da718 --- /dev/null +++ b/cmd/v2/lib/core.go @@ -0,0 +1,7 @@ +package lib + +type Aiassistant interface { + Start() bool + Stop() error + ShowResult() +} diff --git a/cmd/v2/lib/gopool.go b/cmd/v2/lib/gopool.go new file mode 100644 index 000000000..8cd894a54 --- /dev/null +++ b/cmd/v2/lib/gopool.go @@ -0,0 +1,60 @@ +package lib + +import "errors" + +type GoPool interface { + Take() + Return() + Total() uint32 + Remainder() uint32 +} + +var _ GoPool = (*myGoPool)(nil) + +type myGoPool struct { + total uint32 //总数 + poolCh chan struct{} //池子容量 + active bool //是否激活 +} + +func NewGoPool(total uint32) (GoPool, error) { + gp := myGoPool{} + if !gp.init(total) { + return nil, errors.New("携程池初始化失败") + } + return &gp, nil +} + +func (gp *myGoPool) init(total uint32) bool { + if gp.active { + return false + } + if total == 0 { + return false + } + + ch := make(chan struct{}, total) + n := int(total) + for i := 0; i < n; i++ { + ch <- struct{}{} + } + + gp.poolCh = ch + gp.total = total + gp.active = true + return true +} + +func (gp *myGoPool) Take() { + <-gp.poolCh +} + +func (gp *myGoPool) Return() { + gp.poolCh <- struct{}{} +} + +func (gp *myGoPool) Total() uint32 { return gp.total } + +func (gp *myGoPool) Remainder() uint32 { + return uint32(len(gp.poolCh)) +} diff --git a/cmd/v2/lib/handler.go b/cmd/v2/lib/handler.go new file mode 100644 index 000000000..34db1c27d --- /dev/null +++ b/cmd/v2/lib/handler.go @@ -0,0 +1,23 @@ +package lib + +import "time" + +// 处理器接口 +type Handler interface { + // 处理请求 + BuildReq() RawReq + //调用 + Call(req []byte, timeoutNS time.Duration) ([]byte, error) + // 检查响应 + Check(req RawReq, rsp RawResp) *CallResult +} + +// 调用结果 +type CallResult struct { + Id int64 + Req RawReq + Resp RawResp + Code ResCode + Message string + Elapse time.Duration +} diff --git a/cmd/v2/lib/param.go b/cmd/v2/lib/param.go new file mode 100644 index 000000000..76c6d7aaf --- /dev/null +++ b/cmd/v2/lib/param.go @@ -0,0 +1,57 @@ +package lib + +import ( + "bytes" + "errors" + "fmt" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +type ParamMgr struct { + Timeout time.Duration + Lps uint32 + Duration time.Duration + Caller Handler //调用器 + ResultCh chan *CallResult //调用结果 +} + +func (param *ParamMgr) Check() error { + var errMsgs []string + + if param.Caller == nil { + errMsgs = append(errMsgs, "无效的调用器") + } + + if param.Timeout == 0 { + errMsgs = append(errMsgs, "参数Timeout无效") + } + + if param.Duration == 0 { + errMsgs = append(errMsgs, "参数Duration无效") + } + + if param.Lps == 0 { + errMsgs = append(errMsgs, "参数lps无效") + } + + if param.ResultCh == nil { + errMsgs = append(errMsgs, "结果通过未实例化") + } + + var buf bytes.Buffer + buf.WriteString("校验参数") + if errMsgs != nil { + errMsg := strings.Join(errMsgs, " ") + buf.WriteString(fmt.Sprintf("没有通过 %s", errMsg)) + logrus.Infoln(buf.String()) + return errors.New(errMsg) + } + + buf.WriteString(fmt.Sprintf("通过. (timeoutNS=%s, lps=%d, durationNS=%s)", + param.Timeout, param.Lps, param.Duration)) + logrus.Infoln(buf.String()) + return nil +} diff --git a/cmd/v2/lib/storage/config.go b/cmd/v2/lib/storage/config.go new file mode 100644 index 000000000..759770ce0 --- /dev/null +++ b/cmd/v2/lib/storage/config.go @@ -0,0 +1,26 @@ +package storage + +//默认配置 +func newDefaultConfig() *Config { + return &Config{ + Pressure: PressureConfig{ + TimeoutMs: 50, + Concurrency: 10, + DurationS: 5, + }, + UserCount: 100, + } +} + +type Config struct { + Pressure PressureConfig `json:"Pressure,omitempty"` + UserCount int32 `json:"UserCount,omitempty"` //用户数 + WsAddr string `json:"wsAddr,omitempty"` //websocket addr +} + +//压测配置 +type PressureConfig struct { + TimeoutMs int32 `json:"timeout,omitempty"` //超时时间 毫秒 + Concurrency int32 `json:"concurrency,omitempty"` //并发量 + DurationS int32 `json:"duration,omitempty"` //持续时间 秒 +} diff --git a/cmd/v2/lib/storage/storage.go b/cmd/v2/lib/storage/storage.go new file mode 100644 index 000000000..3b9a68fbd --- /dev/null +++ b/cmd/v2/lib/storage/storage.go @@ -0,0 +1,29 @@ +package storage + +import "path/filepath" + +const ( + storageRootName = "storage" + configFileName = "config.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) +} diff --git a/cmd/v2/lib/storage/storage_os.go b/cmd/v2/lib/storage/storage_os.go new file mode 100644 index 000000000..f363b41fd --- /dev/null +++ b/cmd/v2/lib/storage/storage_os.go @@ -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 +} diff --git a/cmd/v2/main.go b/cmd/v2/main.go index 71d718cd1..feaf4ba7d 100644 --- a/cmd/v2/main.go +++ b/cmd/v2/main.go @@ -8,6 +8,7 @@ import ( "go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/theme" "go_dreamfactory/cmd/v2/ui" + "image/color" "io" "log" "os" @@ -17,6 +18,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/widget" @@ -67,7 +69,13 @@ func main() { // create a new ui app := app.NewWithID("df-toolkit") app.SetIcon(theme.ResourceAppPng) - appUI := ui.NewUI(app, configService, connService, pttService, obs) + + appUI, err := ui.NewUI(app, configService, connService, pttService, obs) + if err != nil { + w := fyne.CurrentApp().NewWindow("错误") + w.SetContent(canvas.NewText(err.Error(), color.RGBA{255, 0, 0, 255})) + w.ShowAndRun() + } // logLifecycle(app) //创建enter @@ -78,7 +86,7 @@ func main() { checkVersion(app, w) } - w.SetContent(container.NewGridWithColumns(2, + w.SetContent(container.NewGridWithColumns(3, widget.NewButton("工具", func() { toolWindow := ui.NewToolWindow(appUI, w) toolWindow.CreateWindow(common.APP_NAME, 1499, 800, true) @@ -88,7 +96,13 @@ func main() { mainWindow := ui.NewMainWindow(appUI, w) mainWindow.CreateWindow(common.APP_NAME, 1499, 800, true) w.Hide() - }))) + }), + widget.NewButton("自动化测试", func() { + perfWindow := ui.NewPerfWindow(appUI, w) + perfWindow.CreateWindow(common.APP_NAME, 600, 800, true) + w.Hide() + }), + )) w.SetFixedSize(true) w.Resize(fyne.NewSize(400, 200)) w.CenterOnScreen() @@ -96,7 +110,7 @@ func main() { app.Quit() }) - logrus.WithField("version", app.Metadata().Version).Info("app starting") + logrus.WithField("version", app.Metadata().Version).Debug("app starting") w.Show() appUI.Run() } @@ -144,12 +158,13 @@ func setupConfig() (err error) { } func setupLogger() (err error) { - logrus.SetFormatter(&logrus.TextFormatter{ - DisableColors: true, - FullTimestamp: true, + logrus.SetFormatter(&logrus.JSONFormatter{ + TimestampFormat: "2006-01-02 15:04:05", + // DisableColors: true, + // FullTimestamp: true, }) - logrus.SetLevel(logrus.DebugLevel) + logrus.SetLevel(logrus.InfoLevel) logrus.SetOutput(os.Stdout) //设置output,默认为stderr,可以为任何io.Writer,比如文件*os.File diff --git a/cmd/v2/resources/config.yaml b/cmd/v2/resources/config.yaml index a1f10234a..2dfa47a48 100644 --- a/cmd/v2/resources/config.yaml +++ b/cmd/v2/resources/config.yaml @@ -7,7 +7,7 @@ services: - service: sid: "dfz" name: 赵长远 - url: ws://10.0.0.238:7891/gateway + url: ws://10.0.5.145:7891/gateway - service: sid: "df01" name: 内网 diff --git a/cmd/v2/service/assistant_test.go b/cmd/v2/service/assistant_test.go new file mode 100644 index 000000000..c6ad4680f --- /dev/null +++ b/cmd/v2/service/assistant_test.go @@ -0,0 +1,28 @@ +package service + +import ( + "go_dreamfactory/cmd/v2/lib" + "testing" + "time" +) + +func TestStart(t *testing.T) { + wsAddr := "ws://10.0.5.215:7891/gateway" + param := lib.ParamMgr{ + Caller: NewWsCli(wsAddr), + Timeout: 2 * time.Second, + Lps: uint32(10), + Duration: 3 * time.Second, + ResultCh: make(chan *lib.CallResult, 50), + } + a, err := lib.NewAssistant(param) + if err != nil { + t.Fatalf("AI助手初始化错误: %v", err) + t.FailNow() + } + + t.Log("AI助手启动...") + a.Start() + + a.ShowResult() +} diff --git a/cmd/v2/service/pttService.go b/cmd/v2/service/pttService.go index 12db6a407..d03dd5b8a 100644 --- a/cmd/v2/service/pttService.go +++ b/cmd/v2/service/pttService.go @@ -71,14 +71,17 @@ func (p *PttServiceImpl) SendToClient(mainType, subType string, rsp proto.Messag func (p *PttServiceImpl) Login(sid, account string) (code pb.ErrorCode) { head := &pb.UserMessage{MainType: string(comm.ModuleUser), SubType: user.UserSubTypeLogin} head.Sec = common.BuildSecStr(sid, account) - if err := p.connService.SendMsg(head, &pb.UserLoginReq{ + req := &pb.UserLoginReq{ Account: account, Sid: sid, - }); err != nil { + } + if err := p.connService.SendMsg(head, req); err != nil { code = pb.ErrorCode_SystemError logrus.WithField("err", err).Error("Login") return } + logrus.WithFields(logrus.Fields{common.MainType: string(comm.ModuleUser), common.SubType: user.UserSubTypeLogin, + common.Params: req}).Info("登录") return } diff --git a/cmd/v2/service/wsCli.go b/cmd/v2/service/wsCli.go new file mode 100644 index 000000000..6b0e7bf2e --- /dev/null +++ b/cmd/v2/service/wsCli.go @@ -0,0 +1,160 @@ +package service + +import ( + "go_dreamfactory/cmd/v2/lib/common" + "go_dreamfactory/cmd/v2/lib" + "go_dreamfactory/comm" + "go_dreamfactory/pb" + "strings" + "time" + + "github.com/Pallinder/go-randomdata" + "github.com/gorilla/websocket" + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/proto" +) + +type WsCli struct { + addr string +} + +func NewWsCli(addr string) lib.Handler { + return &WsCli{addr: addr} +} + +func (cli *WsCli) loginReq() ([]byte, error) { + head := &pb.UserMessage{MainType: "user", SubType: "login"} + sid := "dfz" + account := randomdata.SillyName() + head.Sec = common.BuildSecStr(sid, account) + if comm.ProtoMarshal(&pb.UserLoginReq{ + Sid: sid, + Account: account, + }, head) { + data, err := proto.Marshal(head) + if err != nil { + return nil, err + } + + return data, nil + } + return nil, nil +} + +// 检查登录相应 +func (cli *WsCli) checkLoginResp(data []byte) bool { + msg := &pb.UserMessage{} + if err := proto.Unmarshal(data, msg); err != nil { + logrus.Error("结果解析失败") + return false + } + if msg.MainType == "user" && msg.SubType == "login" { + rsp := &pb.UserLoginResp{} + if !comm.ProtoUnmarshal(msg, rsp) { + logrus.Error("unmarshal err") + return false + } + + if rsp.Data != nil { + if rsp.Data.Uid != "" { + logrus.WithField("uid", rsp.Data.Uid).Debug("登录响应") + return true + } + } + return true + } + return false +} + +// 检查推送(包括错误推送) +func (cli *WsCli) checkPush(data []byte) bool { + msg := &pb.UserMessage{} + if err := proto.Unmarshal(data, msg); err != nil { + logrus.Error("结果解析失败") + return false + } + methodStr := msg.Data.TypeUrl + methodName := common.SubStr(methodStr, 20, len(methodStr)) + + if strings.HasSuffix(methodName, "Push") { + if methodName == "NotifyErrorNotifyPush" { + logrus.WithField("methodName", methodName).Debug("收到错误码") + } else { + logrus.WithField("methodName", methodName).Debug("收到推送") + } + return true + } + return false +} + +func (cli *WsCli) BuildReq() lib.RawReq { + id := time.Now().UnixNano() + + // TODO: 读流程表配置,确定请求UserMessage + b, err := cli.loginReq() + if err != nil { + panic(err) + } + rawReq := lib.RawReq{ID: id, Req: b} + return rawReq +} + +func (cli *WsCli) Call(req []byte, timeout time.Duration) ([]byte, error) { + dialer := &websocket.Dialer{ + HandshakeTimeout: timeout, + } + conn, _, err := dialer.Dial(cli.addr, nil) + if err != nil { + logrus.Errorf("websocket conn err:%v", err) + return nil, err + } + + go func() { + timer := time.NewTimer(2 * time.Second) + for { + timer.Reset(2 * time.Second) + <-timer.C + if err := conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + break + } + } + }() + + // 向连接写数据 + conn.WriteMessage(websocket.BinaryMessage, req) + + // 读数据 + var res []byte + for { + _, data, err := conn.ReadMessage() + if err != nil { + logrus.Errorf("readMessage err:%v", err) + break + } + + if cli.checkLoginResp(data) { + return data,nil + } else { + if !cli.checkPush(data) { + logrus.Debug("登录失败") + } + } + } + + return res, nil +} + +func (cli *WsCli) Check(req lib.RawReq, resp lib.RawResp) *lib.CallResult { + var result lib.CallResult + result.Id = resp.ID + result.Req = req + result.Resp = resp + //TODO 解析结果 + msg := &pb.UserMessage{} + if err := proto.Unmarshal(resp.Resp, msg); err != nil { + logrus.Error("结果解析失败") + return &result + } + logrus.WithFields(logrus.Fields{"msg": msg}).Debug("检查结果") + return &result +} diff --git a/cmd/v2/ui/app_auto.go b/cmd/v2/ui/app_auto.go new file mode 100644 index 000000000..599c78482 --- /dev/null +++ b/cmd/v2/ui/app_auto.go @@ -0,0 +1,84 @@ +package ui + +import ( + "go_dreamfactory/cmd/v2/lib/common" + "go_dreamfactory/cmd/v2/service" + "go_dreamfactory/cmd/v2/service/observer" + "io/ioutil" + "path/filepath" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" + "fyne.io/fyne/v2/theme" + "github.com/sirupsen/logrus" +) + +//半自动化 依据采集的数据 +type appAuto struct { + appAdapter + + obs observer.Observer + + caseList func() + itemList *common.ItemList +} + +// 读文件 +func (app *appAuto) loadData() error { + bytes, err := ioutil.ReadFile(filepath.Join(".", "robot.log")) + if err != nil { + return err + } + + logrus.Debug(bytes) + return nil +} + +func (app *appAuto) LazyInit(service service.PttService, obs observer.Observer) error { + app.obs = obs + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_AUTO, theme.StorageIcon(), nil) + + content := container.NewMax() + content.Objects = []fyne.CanvasObject{} + + // 初始化列表 + app.itemList = common.NewItemList() + app.itemList.ItemList = app.itemList.CreateList() + + // 读取采集的数据文件 + app.caseList = func() { + app.loadData() + } + + defer func() { + app.caseList() + }() + + // layout + // panel := container.NewVSplit(container.NewBorder(app.monitorHeader, nil, nil, nil, app.monitorList), resPanel) + // panel.Offset = 0.8 + content.Objects = append(content.Objects) + app.tabItem.Content = content + return nil +} + +func (a *appAuto) OpenDefault() string { + return common.TOOLBAR_AUTO +} + +func (a *appAuto) GetAppName() string { + return "自动化" +} + +func (a appAuto) OnClose() bool { + return false +} + +func (this *appAuto) ShortCut() fyne.Shortcut { + return &desktop.CustomShortcut{KeyName: fyne.Key3, Modifier: desktop.AltModifier} +} + +func (this *appAuto) Icon() fyne.Resource { + return theme.StorageIcon() +} diff --git a/cmd/v2/ui/app_container.go b/cmd/v2/ui/app_container.go index db11238fb..c3657aa95 100644 --- a/cmd/v2/ui/app_container.go +++ b/cmd/v2/ui/app_container.go @@ -61,10 +61,10 @@ func (at *appContainer) openApp(app appInterface) error { } // open default app -func (at *appContainer) openDefaultApp() (string, error) { +func (at *appContainer) openDefaultApp(appName string) (string, error) { var firstTab *container.TabItem for _, app := range at.ai { - if app.OpenDefault() { + if app.OpenDefault() == appName { if err := at.initApp(app); err != nil { return app.GetAppName(), err } diff --git a/cmd/v2/ui/app_interface.go b/cmd/v2/ui/app_interface.go index 528467a31..46543ec0c 100644 --- a/cmd/v2/ui/app_interface.go +++ b/cmd/v2/ui/app_interface.go @@ -4,34 +4,47 @@ import ( "go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service/observer" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" ) type appInterface interface { LazyInit(service service.PttService, obs observer.Observer) error GetTabItem() *container.TabItem GetAppName() string - OpenDefault() bool + OpenDefault() string + GetUI() interface{} OnClose() bool + ShortCut() fyne.Shortcut + Icon() fyne.Resource } var ( appRegister = []appInterface{ &appMonitor{}, &appTester{}, - } + &appAuto{}, - toolRegister = []appInterface{ &appWelcome{}, &appGen{}, &appPbGen{}, &appLock{}, &appTerm{}, + + &perfWelcome{}, + &perfConf{}, + &perfPb{}, } ) type appAdapter struct { tabItem *container.TabItem + ai []appInterface +} + +func (a appAdapter) ShortCut() fyne.Shortcut { + panic("implement Shortcut") } func (a appAdapter) LazyInit() error { @@ -42,8 +55,12 @@ func (a appAdapter) GetAppName() string { panic("implement GetAppName()") } -func (a appAdapter) OpenDefault() bool { - return false +func (a appAdapter) GetUI() interface{} { + panic("implement GetUI()") +} + +func (a appAdapter) OpenDefault() string { + return "" } func (a appAdapter) GetTabItem() *container.TabItem { @@ -53,3 +70,7 @@ func (a appAdapter) GetTabItem() *container.TabItem { func (a appAdapter) OnClose() bool { return true } + +func (a appAdapter) Icon() fyne.Resource { + return theme.FyneLogo() +} diff --git a/cmd/v2/ui/app_monitor.go b/cmd/v2/ui/app_monitor.go index 664f2f6e3..73d73a91b 100644 --- a/cmd/v2/ui/app_monitor.go +++ b/cmd/v2/ui/app_monitor.go @@ -10,6 +10,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -28,38 +29,39 @@ type appMonitor struct { monitorData *model.PushModelList } -func (this *appMonitor) LazyInit(service service.PttService, obs observer.Observer) error { - this.obs = obs - this.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_MONITOR, theme.MediaVideoIcon(), nil) +func (app *appMonitor) LazyInit(service service.PttService, obs observer.Observer) error { + app.obs = obs + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_MONITOR, theme.MediaVideoIcon(), nil) content := container.NewMax() content.Objects = []fyne.CanvasObject{} // panel for output log - this.logPanel = widget.NewMultiLineEntry() - this.logPanel.Wrapping = fyne.TextWrapWord + app.logPanel = widget.NewMultiLineEntry() + app.logPanel.Wrapping = fyne.TextWrapWord //clear button clearBtn := widget.NewButtonWithIcon(common.APP_TESTCASE_BTN_CLEARLOG, theme.DeleteIcon(), func() { - this.logPanel.SetText("") - this.monitorBinding.Set([]interface{}{}) + app.logPanel.SetText("") + app.monitorBinding.Set([]interface{}{}) }) - resPanel := container.NewBorder(container.NewHBox(clearBtn, layout.NewSpacer()), nil, nil, nil, this.logPanel) + resPanel := container.NewBorder(container.NewHBox(clearBtn, layout.NewSpacer()), nil, nil, nil, app.logPanel) - this.monitorBinding = binding.NewUntypedList() - this.monitorData = model.NewPushModelList() - this.createMonitorList() + app.monitorBinding = binding.NewUntypedList() + app.monitorData = model.NewPushModelList() + app.createMonitorList() // layout - panel := container.NewVSplit(container.NewBorder(this.monitorHeader, nil, nil, nil, this.monitorList), resPanel) + panel := container.NewVSplit(container.NewBorder(app.monitorHeader, nil, nil, nil, app.monitorList), resPanel) + panel.Offset = 0.8 content.Objects = append(content.Objects, panel) - this.tabItem.Content = content - this.Run() + app.tabItem.Content = content + app.Run() return nil } -func (a *appMonitor) OpenDefault() bool { - return true +func (a *appMonitor) OpenDefault() string { + return common.TOOLBAR_MONITOR } func (a *appMonitor) GetAppName() string { @@ -71,12 +73,12 @@ func (a appMonitor) OnClose() bool { } // monitor list data -func (this *appMonitor) Run() { - this.obs.AddListener(observer.EVENT_APP_MONI, observer.Listener{ +func (app *appMonitor) Run() { + app.obs.AddListener(observer.EVENT_APP_MONI, observer.Listener{ OnNotify: func(d interface{}, args ...interface{}) { data := d.(*model.PushModel) - this.monitorData.DataList = append(this.monitorData.DataList, data) - this.reloadMonitorData() + app.monitorData.DataList = append(app.monitorData.DataList, data) + app.reloadMonitorData() common.ShowCanvasTip("收到新的数据推送,请打开[推送]页面") }, }) @@ -142,3 +144,11 @@ func (this *appMonitor) createMonitorList() { } } + +func (this *appMonitor) ShortCut() fyne.Shortcut { + return &desktop.CustomShortcut{KeyName: fyne.Key3, Modifier: desktop.AltModifier} +} + +func (this *appMonitor) Icon() fyne.Resource { + return theme.MediaVideoIcon() +} diff --git a/cmd/v2/ui/app_test.go b/cmd/v2/ui/app_test.go new file mode 100644 index 000000000..447c3bff0 --- /dev/null +++ b/cmd/v2/ui/app_test.go @@ -0,0 +1,18 @@ +package ui + +import ( + "fmt" + cfg "go_dreamfactory/cmd/v2/configure/structs" + "go_dreamfactory/cmd/v2/lib/common" + "testing" +) + +func TestGjson(t *testing.T) { + if tables, err := cfg.NewTables(common.Loader); err != nil { + println(err.Error()) + } else { + for _, v := range tables.TestFlow.GetDataList() { + fmt.Println(v.Msg) + } + } +} diff --git a/cmd/v2/ui/app_testcase.go b/cmd/v2/ui/app_testcase.go index ccf722f8c..fa43eed70 100644 --- a/cmd/v2/ui/app_testcase.go +++ b/cmd/v2/ui/app_testcase.go @@ -11,6 +11,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -41,7 +42,7 @@ func (a *appTester) LazyInit(service service.PttService, obs observer.Observer) intro := widget.NewLabel("") intro.Wrapping = fyne.TextWrapWord caseContent := container.NewBorder( - container.NewVBox(title, widget.NewSeparator(), intro), nil, nil, nil, content) + container.NewVBox(container.NewHBox(title, widget.NewSeparator(), widget.NewButton("压测", nil)), widget.NewSeparator(), intro), nil, nil, nil, content) setNav := func(t *model.TestCase) { defer a.progress.Hide() @@ -170,3 +171,15 @@ func (a *appTester) makeNav(setNav func(testCase *model.TestCase)) fyne.CanvasOb } return container.NewBorder(nil, nil, nil, nil, tree) } + +func (a *appTester) ShortCut() fyne.Shortcut { + return &desktop.CustomShortcut{KeyName: fyne.Key2, Modifier: desktop.AltModifier} +} + +func (a *appTester) Icon() fyne.Resource { + return theme.DocumentIcon() +} + +func(a *appTester) Pressure() bool{ + return true +} diff --git a/cmd/v2/ui/main_menu.go b/cmd/v2/ui/main_menu.go index 96316d982..8fb148aa4 100644 --- a/cmd/v2/ui/main_menu.go +++ b/cmd/v2/ui/main_menu.go @@ -2,30 +2,70 @@ package ui import ( "fyne.io/fyne/v2" + "fyne.io/fyne/v2/theme" + "github.com/sirupsen/logrus" ) type mainMenu struct { *fyne.MainMenu + sysMenu *fyne.Menu + appMenus []*fyne.MenuItem - helpMenu *fyne.Menu - sysLog *fyne.MenuItem + settingMenu *fyne.Menu + testingMenu *fyne.MenuItem + + quite *fyne.MenuItem + + helpMenu *fyne.Menu + sysLog *fyne.MenuItem // aboutSelf *fyne.MenuItem } -// func newMainMenu() *mainMenu { -// var mm mainMenu +func newMainMenu() *mainMenu { + var mm mainMenu -// // help -// mm.sysLog = fyne.NewMenuItem("Show Log", func() { -// newLogViewer().Win.Show() -// }) -// mm.helpMenu = fyne.NewMenu("Help", -// mm.sysLog, -// // mm.aboutSelf, -// ) + // system + mm.appMenus = make([]*fyne.MenuItem, len(appRegister)) + for i, app := range appRegister { + app := app + mm.appMenus[i] = fyne.NewMenuItem(app.GetAppName(), func() { + err := globalWin.at.openApp(app) + if err != nil { + logrus.Errorf("打开 %s, err:%v", app.GetAppName(), err) + } + }) + mm.appMenus[i].Shortcut = app.ShortCut() + mm.appMenus[i].Icon = app.Icon() + } -// mm.MainMenu = fyne.NewMainMenu( -// mm.helpMenu, -// ) -// return &mm -// } + mm.quite = fyne.NewMenuItem("退出", globalWin.quiteHandle) + mm.quite.Icon = theme.LogoutIcon() + mm.quite.IsQuit = true + mm.sysMenu = fyne.NewMenu("应用", mm.appMenus...) + mm.sysMenu.Items = append(mm.sysMenu.Items, fyne.NewMenuItemSeparator(), mm.quite) + + // setting + mm.testingMenu = fyne.NewMenuItem("压测配置", func() { + newTestConfigWindow().Win.Show() + }) + mm.testingMenu.Icon = theme.SettingsIcon() + mm.settingMenu = fyne.NewMenu("设置", + mm.testingMenu, + ) + + // help + mm.sysLog = fyne.NewMenuItem("日志", func() { + newLogViewer().Win.Show() + }) + mm.helpMenu = fyne.NewMenu("帮助", + mm.sysLog, + // mm.aboutSelf, + ) + + mm.MainMenu = fyne.NewMainMenu( + mm.sysMenu, + mm.settingMenu, + // mm.helpMenu, + ) + return &mm +} diff --git a/cmd/v2/ui/mainwindow.go b/cmd/v2/ui/mainwindow.go index 18fdda765..deb88b42d 100644 --- a/cmd/v2/ui/mainwindow.go +++ b/cmd/v2/ui/mainwindow.go @@ -7,6 +7,7 @@ import ( "go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/comm" + "go_dreamfactory/lego/sys/log" "go_dreamfactory/modules/user" "go_dreamfactory/pb" "strings" @@ -20,11 +21,11 @@ import ( "github.com/Pallinder/go-randomdata" "github.com/sirupsen/logrus" "github.com/spf13/cast" + "golang.design/x/hotkey" ) var ( globalWin *MainWindowImpl - toolWin *ToolWindowImpl ) type MainWindow interface { @@ -34,12 +35,13 @@ type MainWindow interface { type MainWindowImpl struct { UIImpl WindowDefaultOptions - parent fyne.Window - w fyne.Window - tb *toolBar //工具条 - toys *toys // side - sb *statusBar //状态栏 - at *appContainer //tabs + parent fyne.Window + w fyne.Window + tb *toolBar //工具条 + toys *toys // side + sb *statusBar //状态栏 + at *appContainer //tabs + mm *mainMenu //菜单 } func NewMainWindow(ui *UIImpl, parent fyne.Window) MainWindow { @@ -81,27 +83,35 @@ func (ui *MainWindowImpl) createWindowContainer() { // tool bar toolbar := widget.NewToolbar( widget.NewToolbarAction(theme.MediaVideoIcon(), func() { - openApp2(common.TOOLBAR_MONITOR) + openApp(ui.at, common.TOOLBAR_MONITOR) }), widget.NewToolbarAction(theme.DocumentIcon(), func() { - openApp2(common.TOOLBAR_TESTER) + openApp(ui.at, common.TOOLBAR_TESTER) + }), + widget.NewToolbarAction(theme.StorageIcon(), func() { + openApp(ui.at, common.TOOLBAR_AUTO) }), widget.NewToolbarSpacer(), - widget.NewToolbarAction(theme.FileIcon(), func() { - newLogViewer().Win.Show() - }), + // widget.NewToolbarAction(theme.FileIcon(), func() { + // newLogViewer().Win.Show() + // }), ) ui.tb = newToolBar(toolbar) // Fun Toys ui.toys = newToys(ui.obs) + // Main Menu + ui.mm = newMainMenu() + ui.w.SetMainMenu(ui.mm.MainMenu) + // main app tabs ui.at = newAppContainer(appRegister, ui.pttService, ui.obs) content := container.NewBorder(ui.tb.toolbar, ui.sb.widget, nil, ui.toys.widget, ui.at) ui.w.SetContent(content) ui.w.CenterOnScreen() ui.w.SetCloseIntercept(ui.quiteHandle) + ui.registerShortCut() } func (ui *MainWindowImpl) SetStatusMsg(msg string) { @@ -115,7 +125,35 @@ func (ui *MainWindowImpl) quiteHandle() { } ui.app.Quit() }, ui.w) +} +func (ui *MainWindowImpl) registerShortCut() { + for _, myApp := range appRegister { + ap := myApp + sc := ap.ShortCut() + ui.w.Canvas().AddShortcut(sc, func(_ fyne.Shortcut) { + err := globalWin.at.openApp(ap) + if err != nil { + log.Errorf("open app %v err:%v", ap.GetAppName(), err) + } + }) + } + + go func() { + hk := hotkey.New([]hotkey.Modifier{hotkey.ModCtrl, hotkey.ModShift}, hotkey.KeyZ) + if err := hk.Register(); err != nil { + log.Errorf("%v", err) + return + } + defer hk.Unregister() + for range hk.Keydown() { + // if globalWin.wStat.shown { + // globalWin.hideWin() + // } else { + // globalWin.showWin() + // } + } + }() } // CreateWindow .... @@ -169,6 +207,7 @@ func (ui *MainWindowImpl) createChooseServerPopUp(w fyne.Window) error { func (ui *MainWindowImpl) createChooseServerWindow( title string, ch chan string) fyne.Window { + w := fyne.CurrentApp().NewWindow(title) // choose server button func makeButton := func(s *service.ServiceConf, parent fyne.Window) *widget.Button { btn := widget.NewButton(s.Name, func() { @@ -183,11 +222,12 @@ func (ui *MainWindowImpl) createChooseServerWindow( dialog.ShowError(err, parent) } else { ch <- fmt.Sprintf("%s:%s", s.SId, s.Name) + w.Close() } }) return btn } - w := fyne.CurrentApp().NewWindow(title) + sGrid := container.NewGridWithColumns(2) config := ui.configService.GetConfig() if config != nil { @@ -225,7 +265,7 @@ func (ui *MainWindowImpl) createLoginWin(sid, sname string) { if account.Text != "" { logrus.WithField("account", account.Text).Debug("submit login") ui.createWindowContainer() - appName, err := ui.at.openDefaultApp() + appName, err := ui.at.openDefaultApp(common.TOOLBAR_MONITOR) if err != nil { logrus.WithField("appName", appName).Error(err) } diff --git a/cmd/v2/ui/perf_conf.go b/cmd/v2/ui/perf_conf.go new file mode 100644 index 000000000..b7cb52fa2 --- /dev/null +++ b/cmd/v2/ui/perf_conf.go @@ -0,0 +1,114 @@ +package ui + +import ( + "go_dreamfactory/cmd/v2/lib" + "go_dreamfactory/cmd/v2/lib/common" + "go_dreamfactory/cmd/v2/lib/storage" + "go_dreamfactory/cmd/v2/service" + "go_dreamfactory/cmd/v2/service/observer" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/sirupsen/logrus" + "github.com/spf13/cast" +) + +type perfConf struct { + appAdapter + + obs observer.Observer + conf *storage.Config +} + +func (app *perfConf) LazyInit(ptService service.PttService, obs observer.Observer) error { + app.obs = obs + app.conf = perfWin.UIImpl.config + + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_PERF_CONF, theme.ContentCopyIcon(), nil) + content := container.NewMax() + content.Objects = []fyne.CanvasObject{} + + // 压测form + wsAddrEntry := widget.NewEntry() + wsAddrEntry.PlaceHolder = "服务地址" + wsAddrEntry.Text = app.conf.WsAddr + + timeoutEntry := widget.NewEntry() + timeoutEntry.PlaceHolder = "毫秒数" + timeoutEntry.Text = cast.ToString(app.conf.Pressure.TimeoutMs) + + lpsEntry := widget.NewEntry() + lpsEntry.PlaceHolder = "并发数量" + lpsEntry.Text = cast.ToString(app.conf.Pressure.Concurrency) + + durationEntry := widget.NewEntry() + durationEntry.PlaceHolder = "秒数" + durationEntry.Text = cast.ToString(app.conf.Pressure.DurationS) + + userCountEntry := widget.NewEntry() + userCountEntry.PlaceHolder = "自动创建的用户数" + userCountEntry.Text = cast.ToString(app.conf.UserCount) + + form := widget.NewForm( + widget.NewFormItem("超时(ms)", timeoutEntry), + widget.NewFormItem("并发量", lpsEntry), + widget.NewFormItem("持续时间(s)", durationEntry), + widget.NewFormItem("用户数", userCountEntry), + ) + form.OnSubmit = func() { + pressure := app.conf.Pressure + pressure.TimeoutMs = cast.ToInt32(timeoutEntry.Text) + pressure.Concurrency = cast.ToInt32(lpsEntry.Text) + pressure.DurationS = cast.ToInt32(durationEntry.Text) + app.conf.Pressure = pressure + app.conf.UserCount = cast.ToInt32(userCountEntry.Text) + app.conf.WsAddr = wsAddrEntry.Text + if err := perfWin.UIImpl.storage.StoreConfig(app.conf); err != nil { + logrus.Error(err) + } + + // new assistant + app.createAssistant() + + //next + openApp(perfWin.tabs, common.TOOLBAR_PERF_PB) + } + form.SubmitText = "下一步" + form.OnCancel = func() { + timeoutEntry.Text = "" + lpsEntry.Text = "" + durationEntry.Text = "" + userCountEntry.Text = "" + wsAddrEntry.Text = "" + form.Refresh() + } + form.CancelText = "重置" + + content.Objects = append(content.Objects, form) + app.tabItem.Content = content + return nil +} + +func (a *perfConf) GetAppName() string { + return common.TOOLBAR_PERF_CONF +} + +func (a *perfConf) createAssistant() { + param := lib.ParamMgr{ + Caller: service.NewWsCli(a.conf.WsAddr), + Timeout: time.Duration(a.conf.Pressure.TimeoutMs) * time.Second, + Lps: uint32(a.conf.Pressure.Concurrency), + Duration: time.Duration(a.conf.Pressure.DurationS) * time.Second, + ResultCh: make(chan *lib.CallResult, 50), + } + assist, err := lib.NewAssistant(param) + if err != nil { + logrus.Errorf("AI助手初始化错误: %v", err) + return + } + logrus.WithField("assistant", assist).Info("AI助手") + perfWin.assistant = assist +} diff --git a/cmd/v2/ui/perf_pb.go b/cmd/v2/ui/perf_pb.go new file mode 100644 index 000000000..f6a1588f7 --- /dev/null +++ b/cmd/v2/ui/perf_pb.go @@ -0,0 +1,91 @@ +package ui + +import ( + "fmt" + cfg "go_dreamfactory/cmd/v2/configure/structs" + "go_dreamfactory/cmd/v2/lib/common" + "go_dreamfactory/cmd/v2/lib/storage" + "go_dreamfactory/cmd/v2/service" + "go_dreamfactory/cmd/v2/service/observer" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/sirupsen/logrus" + "github.com/spf13/cast" +) + +type perfPb struct { + appAdapter + + obs observer.Observer + + itemList common.ItemList + + pbList func() //协议列表 + conf *storage.Config +} + +func (app *perfPb) LazyInit(ptService service.PttService, obs observer.Observer) error { + app.obs = obs + app.conf = perfWin.UIImpl.config + + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_PERF_PB, theme.ContentCopyIcon(), nil) + content := container.NewMax() + content.Objects = []fyne.CanvasObject{} + + app.itemList = *common.NewItemList() + app.itemList.ItemList = app.itemList.CreateList() + + app.pbList = func() { + if tables, err := cfg.NewTables(common.Loader); err != nil { + println(err.Error()) + } else { + for _, v := range tables.TestFlow.GetDataList() { + item := common.Item{ + Id: cast.ToString(v.Id), + Text: fmt.Sprintf("%-6d %-20s %-20s %s", v.Id, v.Msg, v.Route, v.Params), + Data: v, + } + app.itemList.AddItem(item) + } + } + } + defer app.pbList() + + // 刷新按钮 + refeshBtn := widget.NewButtonWithIcon("", theme.ViewRefreshIcon(), func() { + app.itemList.Reset() + app.pbList() + }) + + // next按钮 + nextBtn := widget.NewButtonWithIcon("执行", theme.ConfirmIcon(), func() { + for _, item := range app.itemList.CachedList.Items { + if data, ok := item.Data.(*cfg.GameTestFlowData); ok { + logrus.Infof("%v %v", data.Route, data.Params) + } + } + }) + + //layout + c := container.NewBorder(container.NewHBox(refeshBtn), container.NewHBox(layout.NewSpacer(), nextBtn), nil, nil, app.itemList.ItemList) + content.Objects = append(content.Objects, c) + app.tabItem.Content = content + return nil +} + +func (a *perfPb) GetAppName() string { + return common.TOOLBAR_PERF_PB +} + +func (a *perfPb) run() { + if perfWin.assistant == nil { + return + } + perfWin.assistant.Start() + + perfWin.assistant.ShowResult() +} diff --git a/cmd/v2/ui/perf_welcome.go b/cmd/v2/ui/perf_welcome.go new file mode 100644 index 000000000..c0f4e2012 --- /dev/null +++ b/cmd/v2/ui/perf_welcome.go @@ -0,0 +1,58 @@ +package ui + +import ( + "go_dreamfactory/cmd/v2/lib/common" + "go_dreamfactory/cmd/v2/service" + "go_dreamfactory/cmd/v2/service/observer" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" +) + +type perfWelcome struct { + appAdapter + + obs observer.Observer +} + +func (app *perfWelcome) LazyInit(service service.PttService, obs observer.Observer) error { + app.obs = obs + + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_PERF_TIP, theme.ContentCopyIcon(), nil) + content := container.NewMax() + content.Objects = []fyne.CanvasObject{} + + wel := widget.NewRichTextFromMarkdown("# 自动化性能测试工具使用说明" + + ` + * 基于Luban工具生成协议文件(json格式) + `) + for i := range wel.Segments { + if seg, ok := wel.Segments[i].(*widget.TextSegment); ok { + seg.Style.Alignment = fyne.TextAlignLeading + } + } + + goBtn := widget.NewButton("开始测试 >>", func() { + openApp(perfWin.tabs, common.TOOLBAR_PERF_CONF) + }) + app.tabItem.Content = container.NewCenter( + container.NewVBox( + wel, + goBtn, + )) + return nil +} + +func (a *perfWelcome) OpenDefault() string { + return common.TOOLBAR_PERF_TIP +} + +func (a *perfWelcome) GetAppName() string { + return common.TOOLBAR_PERF_TIP +} + +func (a *perfWelcome) OnClose() bool { + return false +} diff --git a/cmd/v2/ui/perfwindow.go b/cmd/v2/ui/perfwindow.go new file mode 100644 index 000000000..ed0b2be48 --- /dev/null +++ b/cmd/v2/ui/perfwindow.go @@ -0,0 +1,85 @@ +package ui + +import ( + "fmt" + "go_dreamfactory/cmd/v2/lib" + "go_dreamfactory/cmd/v2/lib/common" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" + "fyne.io/fyne/v2/widget" + "github.com/sirupsen/logrus" +) + +var perfWin *PerfWindowImpl + +type PerfWindow interface { + WindowInterface +} + +type PerfWindowImpl struct { + UIImpl + parent fyne.Window + w fyne.Window + statusbar *statusBar //状态栏 + tabs *appContainer //tabs + toolbar *toolBar //工具条 + assistant lib.Aiassistant +} + +func NewPerfWindow(ui *UIImpl, parent fyne.Window) PerfWindow { + + pw := &PerfWindowImpl{ + UIImpl: *ui, + parent: parent, + } + + perfWin = pw + return pw +} + +func (ui *PerfWindowImpl) GetWin() WindowInterface { + return perfWin +} + +func (ui *PerfWindowImpl) CreateWindow(_ string, width, height float32, _ bool) { + title := fmt.Sprintf(common.APP_WIN_TITLE, "自动化性能测试工具", ui.app.Metadata().Version, ui.app.Metadata().Build, common.APP_NAME) + w := ui.app.NewWindow(title) + ui.AddWindow("main", w) + ui.w = w + + w.Resize(fyne.NewSize(width, height)) + w.CenterOnScreen() + w.SetCloseIntercept(func() { + ui.parent.Show() + }) + + ui.statusbar = newStatusBar() + toolbar := widget.NewToolbar( + widget.NewToolbarAction(theme.MediaVideoIcon(), func() { + openApp(ui.tabs, common.TOOLBAR_PERF_TIP) + }), + widget.NewToolbarAction(theme.MediaVideoIcon(), func() { + openApp(ui.tabs, common.TOOLBAR_PERF_CONF) + }), + ) + ui.toolbar = newToolBar(toolbar) + + ui.tabs = newAppContainer(appRegister, ui.pttService, ui.obs) + content := container.NewBorder(ui.toolbar.toolbar, ui.statusbar.widget, nil, nil, ui.tabs) + ui.w.SetContent(content) + + defer func() { + appName, err := ui.tabs.openDefaultApp(common.TOOLBAR_PERF_TIP) + if err != nil { + logrus.WithField("appName", appName).Error(err) + } + }() + + w.SetCloseIntercept(func() { + ui.parent.Show() + w.Close() + }) + w.Show() +} diff --git a/cmd/v2/ui/testconfig_win.go b/cmd/v2/ui/testconfig_win.go new file mode 100644 index 000000000..c9098a322 --- /dev/null +++ b/cmd/v2/ui/testconfig_win.go @@ -0,0 +1,71 @@ +package ui + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/widget" + "github.com/sirupsen/logrus" + "github.com/spf13/cast" +) + +type TestConfigWindow struct { + computer *widget.Button + + Win fyne.Window +} + +func newTestConfigWindow() *TestConfigWindow { + var configWin TestConfigWindow + conf := globalWin.UIImpl.config + + // 压测form + timeoutEntry := widget.NewEntry() + timeoutEntry.PlaceHolder = "毫秒数" + timeoutEntry.Text = cast.ToString(conf.Pressure.TimeoutMs) + + lpsEntry := widget.NewEntry() + lpsEntry.PlaceHolder = "并发数量" + lpsEntry.Text = cast.ToString(conf.Pressure.Concurrency) + + durationEntry := widget.NewEntry() + durationEntry.PlaceHolder = "秒数" + durationEntry.Text = cast.ToString(conf.Pressure.DurationS) + + form := widget.NewForm( + widget.NewFormItem("超时(ms)", timeoutEntry), + widget.NewFormItem("并发量", lpsEntry), + widget.NewFormItem("持续时间(s)", durationEntry), + ) + form.OnSubmit = func() { + pressure := conf.Pressure + pressure.TimeoutMs = cast.ToInt32(timeoutEntry.Text) + pressure.Concurrency = 10 + pressure.DurationS = 2 + conf.Pressure = pressure + if err := globalWin.UIImpl.storage.StoreConfig(conf); err != nil { + logrus.Error(err) + } + } + form.SubmitText = "确定" + + // 计算器 + concurrencyEntry := widget.NewEntry() + computerForm := widget.NewForm( + widget.NewFormItem("并发", concurrencyEntry), + ) + computerForm.OnSubmit = func() { + } + computerForm.SubmitText = "确定" + + // layout + configWin.Win = fyne.CurrentApp().NewWindow("压测配置") + //压测配置 + settingLayout := container.NewBorder(widget.NewLabel("压测配置"), nil, nil, nil, form) + // computer + computerLayout := container.NewBorder(widget.NewLabel("并发量计算"), nil, nil, nil, computerForm) + configWin.Win.SetContent(container.NewGridWithRows(2, settingLayout, computerLayout)) + configWin.Win.Resize(fyne.NewSize(800, 600)) + configWin.Win.CenterOnScreen() + + return &configWin +} diff --git a/cmd/v2/ui/tool_bar.go b/cmd/v2/ui/tool_bar.go index ff3041dfc..5506ab184 100644 --- a/cmd/v2/ui/tool_bar.go +++ b/cmd/v2/ui/tool_bar.go @@ -20,10 +20,10 @@ func newToolBar(items *widget.Toolbar) *toolBar { } // open app2 -func openApp2(name string) { +func openApp(ac *appContainer, name string) { for _, app := range appRegister { if app.GetAppName() == name { - err := globalWin.at.openApp(app) + err := ac.openApp(app) if err != nil { logrus.Error(fmt.Errorf("%s %v", app.GetAppName(), err)) } @@ -31,13 +31,4 @@ func openApp2(name string) { } } -func openApp1(name string) { - for _, app := range toolRegister { - if app.GetAppName() == name { - err := toolWin.at.openApp(app) - if err != nil { - logrus.Error(fmt.Errorf("%s %v", app.GetAppName(), err)) - } - } - } -} + diff --git a/cmd/v2/ui/tool_luban.go b/cmd/v2/ui/tool_luban.go index 28f5e8f8f..a60a7952d 100644 --- a/cmd/v2/ui/tool_luban.go +++ b/cmd/v2/ui/tool_luban.go @@ -15,7 +15,7 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/storage" + fyne_storage "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/sirupsen/logrus" @@ -401,7 +401,7 @@ func openFolder(entry *widget.Entry, w fyne.Window) { entry.Text = lu.Path() entry.Refresh() }, w) - luri, _ := storage.ListerForURI(storage.NewFileURI(".")) + luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI(".")) dConf.SetLocation(luri) dConf.SetConfirmText("打开") dConf.SetDismissText("取消") @@ -419,7 +419,7 @@ func openFile(entry *widget.Entry, w fyne.Window) { }, w) dConf.SetConfirmText("打开") dConf.SetDismissText("取消") - dConf.SetFilter(storage.NewExtensionFileFilter([]string{".exe"})) + dConf.SetFilter(fyne_storage.NewExtensionFileFilter([]string{".exe"})) dConf.Resize(fyne.NewSize(750, 500)) dConf.Show() } diff --git a/cmd/v2/ui/tool_pb.go b/cmd/v2/ui/tool_pb.go index 9076b3379..f40443e01 100644 --- a/cmd/v2/ui/tool_pb.go +++ b/cmd/v2/ui/tool_pb.go @@ -17,7 +17,7 @@ import ( "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" - "fyne.io/fyne/v2/storage" + fyne_storage "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" "github.com/sirupsen/logrus" @@ -47,7 +47,7 @@ func (this *appPbGen) LazyInit(ptService service.PttService, obs observer.Observ entry.Text = lu.Path() entry.Refresh() }, toolWin.w) - luri, _ := storage.ListerForURI(storage.NewFileURI(".")) + luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI(".")) dConf.SetLocation(luri) dConf.SetConfirmText("打开") dConf.SetDismissText("取消") diff --git a/cmd/v2/ui/tool_term.go b/cmd/v2/ui/tool_term.go index 3a12d63d9..74bd4fbd3 100644 --- a/cmd/v2/ui/tool_term.go +++ b/cmd/v2/ui/tool_term.go @@ -40,19 +40,19 @@ type appTerm struct { downloadList *common.ItemList //download列表 } -func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observer) error { - this.obs = obs - this.sshService = &service.SSHService{} - this.jsonList = common.NewItemList() +func (app *appTerm) LazyInit(ptService service.PttService, obs observer.Observer) error { + app.obs = obs + app.sshService = &service.SSHService{} + app.jsonList = common.NewItemList() //progress - this.cProgress = widget.NewProgressBarInfinite() - this.cProgress.Hide() + app.cProgress = widget.NewProgressBarInfinite() + app.cProgress.Hide() - this.upProgress = widget.NewProgressBar() - this.upProgress.Hide() + app.upProgress = widget.NewProgressBar() + app.upProgress.Hide() - this.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_TERM, theme.MailSendIcon(), nil) + app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_TERM, theme.MailSendIcon(), nil) content := container.NewMax() content.Objects = []fyne.CanvasObject{} @@ -198,8 +198,8 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe // 连接 connBtn.OnTapped = func() { - this.cProgress.Show() - this.cProgress.Start() + app.cProgress.Show() + app.cProgress.Start() ciphers := []string{} if ip.Text == "" { @@ -230,21 +230,21 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe } connBtn.Disable() - err := this.sshService.Connect(userName.Text, password.Text, ip.Text, "", cast.ToInt(port.Text), ciphers) + err := app.sshService.Connect(userName.Text, password.Text, ip.Text, "", cast.ToInt(port.Text), ciphers) if err != nil { dialog.ShowError(err, toolWin.w) - this.cProgress.Stop() - this.cProgress.Hide() + app.cProgress.Stop() + app.cProgress.Hide() connBtn.Enable() return } else { - this.jsonList.LoadItem(localDir.Text) + app.jsonList.LoadItem(localDir.Text) - this.cProgress.Stop() - this.cProgress.Hide() + app.cProgress.Stop() + app.cProgress.Hide() disConnBtn.Enable() syncBtn.Enable() - this.upProgress.Hide() + app.upProgress.Hide() allCancelBtn.Show() allSelBtn.Hide() refreshBtn.Enable() @@ -260,7 +260,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe allSelBtn.Hide() allCancelBtn.Show() }() - this.jsonList.LoadItem(localDir.Text) + app.jsonList.LoadItem(localDir.Text) } refreshBtn.OnTapped = reloadItem @@ -268,7 +268,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe disConnBtn.Disable() disConnBtn.OnTapped = func() { defer func() { - this.jsonList.Reset() + app.jsonList.Reset() connBtn.Enable() disConnBtn.Disable() syncBtn.Disable() @@ -277,7 +277,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe refreshBtn.Disable() dlBtn.Disable() }() - this.sshService.Close() + app.sshService.Close() } //资源管理器 @@ -348,42 +348,42 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe syncNext := func() { defer func() { syncBtn.Enable() - this.upProgress.Hide() + app.upProgress.Hide() reloadItem() dialog.ShowInformation("提示", "所有文件均上传完毕,需等1-2分钟待文件热更", toolWin.w) }() syncBtn.Disable() - this.upProgress.Show() - this.upProgress.SetValue(0) + app.upProgress.Show() + app.upProgress.SetValue(0) - len := len(this.jsonList.SelItemIds) + len := len(app.jsonList.SelItemIds) num := 0.0 increment := func(wg *sync.WaitGroup) { num += float64(1) / float64(len) - this.upProgress.SetValue(num) + app.upProgress.SetValue(num) wg.Done() } - for _, fileName := range this.jsonList.SelItemIds { - this.endProgress.Add(1) + for _, fileName := range app.jsonList.SelItemIds { + app.endProgress.Add(1) go func(fn string) { // time.Sleep(time.Second * time.Duration(rand.Intn(3))) - if err := this.sshService.ScpCopy(filepath.Join(localDir.Text, fn), remoteDir.Text); err != nil { + if err := app.sshService.ScpCopy(filepath.Join(localDir.Text, fn), remoteDir.Text); err != nil { logrus.WithField("err", err).Error("同步json") common.ShowTip(err.Error()) } - increment(&this.endProgress) + increment(&app.endProgress) // 移除已上传的 - this.jsonList.DeleteItem(fn) + app.jsonList.DeleteItem(fn) common.ShowTip(fmt.Sprintf("%s 成功上传", fn)) }(fileName) } - this.endProgress.Wait() - this.upProgress.SetValue(1) + app.endProgress.Wait() + app.upProgress.SetValue(1) } syncBtn.OnTapped = func() { - if this.sshService.Client == nil { + if app.sshService.Client == nil { dialog.ShowError(errors.New("请先连接终端"), toolWin.w) return } @@ -395,7 +395,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe svnBtn.FocusGained() return } else { - if len(this.jsonList.SelItemIds) == 0 { + if len(app.jsonList.SelItemIds) == 0 { common.ShowTip("没有选择任何文件,或尝试点击【刷新】") return } @@ -419,13 +419,13 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe // SVN更新 svnNext := func() { defer func() { - this.cProgress.Hide() - this.cProgress.Stop() + app.cProgress.Hide() + app.cProgress.Stop() svnBtn.Enable() }() svnBtn.Disable() - this.cProgress.Show() - this.cProgress.Start() + app.cProgress.Show() + app.cProgress.Start() commandStr := `%s -h %s -j cfg -- -d %s --input_data_dir %s --output_data_dir %s --gen_types data_json -s server` arg := fmt.Sprintf(commandStr, filepath.Join(workDir.Text, lubanCli.Text), @@ -465,12 +465,12 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe allCancelBtn.Hide() allSelBtn.Show() }() - for i, v := range this.jsonList.CachedList.Items { - this.jsonList.CachedList.Items[i].Checked = true - this.jsonList.SelItemIds = append(this.jsonList.SelItemIds, v.Text) - this.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) + for i, v := range app.jsonList.CachedList.Items { + app.jsonList.CachedList.Items[i].Checked = true + app.jsonList.SelItemIds = append(app.jsonList.SelItemIds, v.Text) + app.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) } - this.jsonList.ItemList.Refresh() + app.jsonList.ItemList.Refresh() } // 全选 @@ -479,18 +479,18 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe allCancelBtn.Show() allSelBtn.Hide() }() - this.jsonList.SelItemIds = []string{} - for i, v := range this.jsonList.CachedList.Items { - this.jsonList.CachedList.Items[i].Checked = false - this.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) + app.jsonList.SelItemIds = []string{} + for i, v := range app.jsonList.CachedList.Items { + app.jsonList.CachedList.Items[i].Checked = false + app.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) } - this.jsonList.ItemList.Refresh() + app.jsonList.ItemList.Refresh() } // 搜索 searchEntry.PlaceHolder = "搜索" searchEntry.OnChanged = func(s string) { - if this.sshService.Client == nil { + if app.sshService.Client == nil { dialog.ShowError(errors.New("请先连接终端"), toolWin.w) return } @@ -499,13 +499,13 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe } else { // go func() { newList := []common.Item{} - for _, v := range this.jsonList.SearchItem { + for _, v := range app.jsonList.SearchItem { if strings.Contains(v.Text, s) { newList = append(newList, v) } } - this.jsonList.CachedList.Items = newList - this.jsonList.ItemList.Refresh() + app.jsonList.CachedList.Items = newList + app.jsonList.ItemList.Refresh() // }() } } @@ -513,7 +513,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe // 下载日志 dlBtn.Disable() dlBtn.OnTapped = func() { - w := this.createDownloadWindow() + w := app.createDownloadWindow() w.Show() w.SetCloseIntercept(func() { dlBtn.Enable() @@ -523,19 +523,19 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe } // 创建json列表 - this.jsonList.ItemList = this.jsonList.CreateDefaultCheckList() + app.jsonList.ItemList = app.jsonList.CreateDefaultCheckList() btns1 := container.NewHBox(helpBtn1, dlBtn, &layout.Spacer{}, saveBtn1, connBtn, disConnBtn) btns2 := container.NewHBox(helpBtn2, &layout.Spacer{}, saveBtn2, excelBtn, svnBtn) split := container.NewHSplit(container.NewVBox(canvas.NewText("---只能在非上产环境!!!同步Json的操作仅限于数值热更,非结构热更---", color.RGBA{255, 0, 0, 255}), configForm, - btns1, svnForm, btns2, this.cProgress), container.NewBorder( + btns1, svnForm, btns2, app.cProgress), container.NewBorder( container.NewBorder(nil, nil, container.NewHBox(allCancelBtn, allSelBtn, syncBtn, refreshBtn), container.NewHBox(explorBtn), searchEntry), - nil, nil, nil, this.jsonList.ItemList)) + nil, nil, nil, app.jsonList.ItemList)) split.Offset = 0.45 - content.Objects = append(content.Objects, container.NewBorder(nil, this.upProgress, nil, nil, split)) + content.Objects = append(content.Objects, container.NewBorder(nil, app.upProgress, nil, nil, split)) - this.tabItem.Content = content + app.tabItem.Content = content return nil } diff --git a/cmd/v2/ui/tool_welcome.go b/cmd/v2/ui/tool_welcome.go index e0dd8567e..2b466ea12 100644 --- a/cmd/v2/ui/tool_welcome.go +++ b/cmd/v2/ui/tool_welcome.go @@ -52,8 +52,8 @@ func (a *appWelcome) GetAppName() string { return common.TOOLBAR_WEL } -func (a *appWelcome) OpenDefault() bool { - return true +func (a *appWelcome) OpenDefault() string { + return common.TOOLBAR_WEL } func (a *appWelcome) ShortCut() fyne.Shortcut { diff --git a/cmd/v2/ui/toolwindow.go b/cmd/v2/ui/toolwindow.go index e1598c410..db83edfd0 100644 --- a/cmd/v2/ui/toolwindow.go +++ b/cmd/v2/ui/toolwindow.go @@ -10,7 +10,7 @@ import ( "fyne.io/fyne/v2/widget" "github.com/sirupsen/logrus" ) - +var toolWin *ToolWindowImpl type ToolWindow interface { WindowInterface } @@ -36,19 +36,19 @@ func NewToolWindow(ui *UIImpl, parent fyne.Window) ToolWindow { toolbar := widget.NewToolbar( widget.NewToolbarAction(theme.ContentCopyIcon(), func() { - openApp1(common.TOOLBAR_GEN) + openApp(mw.at,common.TOOLBAR_GEN) }), widget.NewToolbarAction(theme.ContentAddIcon(), func() { - openApp1(common.TOOLBAR_PB) + openApp(mw.at,common.TOOLBAR_PB) }), widget.NewToolbarAction(theme.DownloadIcon(), func() { - openApp1(common.TOOLBAR_SEC) + openApp(mw.at,common.TOOLBAR_SEC) }), widget.NewToolbarAction(theme.MailSendIcon(), func() { - openApp1(common.TOOLBAR_TERM) + openApp(mw.at,common.TOOLBAR_TERM) }), widget.NewToolbarSpacer(), @@ -59,11 +59,16 @@ func NewToolWindow(ui *UIImpl, parent fyne.Window) ToolWindow { mw.tb = newToolBar(toolbar) - mw.at = newAppContainer(toolRegister, nil, ui.obs) + mw.at = newAppContainer(appRegister, nil, ui.obs) return mw } +func (ui *ToolWindowImpl) GetWin() WindowInterface { + return toolWin +} + + func (ui *ToolWindowImpl) CreateWindow(title string, width, height float32, _ bool) { w := ui.app.NewWindow(fmt.Sprintf("工具箱 %s %s", title, ui.app.Metadata().Version)) ui.AddWindow("tool", w) @@ -73,7 +78,7 @@ func (ui *ToolWindowImpl) CreateWindow(title string, width, height float32, _ bo content := container.NewBorder(ui.tb.toolbar, ui.sb.widget, nil, nil, ui.at) ui.w.SetContent(content) - appName, err := ui.at.openDefaultApp() + appName, err := ui.at.openDefaultApp(common.TOOLBAR_WEL) if err != nil { logrus.WithField("appName", appName).Error(err) } diff --git a/cmd/v2/ui/ui.go b/cmd/v2/ui/ui.go index 38e00ed11..57ef7a3eb 100644 --- a/cmd/v2/ui/ui.go +++ b/cmd/v2/ui/ui.go @@ -1,6 +1,7 @@ package ui import ( + "go_dreamfactory/cmd/v2/lib/storage" "go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/theme" @@ -9,6 +10,7 @@ import ( "fyne.io/fyne/v2" "github.com/BabySid/gobase" + "github.com/sirupsen/logrus" ) var uid string @@ -27,6 +29,8 @@ type UIImpl struct { pttService service.PttService configService service.ConfigService obs observer.Observer + storage storage.Storage + config *storage.Config } func NewUI(app fyne.App, @@ -34,8 +38,21 @@ func NewUI(app fyne.App, connService service.ConnService, pttService service.PttService, obs observer.Observer, -) *UIImpl { +) (*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, windows: make(map[string]fyne.Window), @@ -44,7 +61,9 @@ func NewUI(app fyne.App, connService: connService, pttService: pttService, obs: obs, - } + storage: storage, + config: config, + }, nil } func (ui *UIImpl) AddWindow(name string, w fyne.Window) { diff --git a/cmd/v2/ui/views/baseformview.go b/cmd/v2/ui/views/baseformview.go index 1f3479b2f..914653343 100644 --- a/cmd/v2/ui/views/baseformview.go +++ b/cmd/v2/ui/views/baseformview.go @@ -17,15 +17,15 @@ type BaseformView struct { service service.PttService } -func (this *BaseformView) Init(service service.PttService, obs observer.Observer, w fyne.Window, res *widget.Entry) { - this.service = service - this.obs = obs - this.w = w - this.res = res - this.form = widget.NewForm() - this.form.SubmitText = common.BUTTON_OK +func (view *BaseformView) Init(service service.PttService, obs observer.Observer, w fyne.Window, res *widget.Entry) { + view.service = service + view.obs = obs + view.w = w + view.res = res + view.form = widget.NewForm() + view.form.SubmitText = common.BUTTON_OK } -func (this *BaseformView) Load() { - this.form.OnSubmit() +func (view *BaseformView) Load() { + view.form.OnSubmit() } diff --git a/cmd/v2/ui/views/sociaty_create.go b/cmd/v2/ui/views/sociaty_create.go index 40a453a90..3302c19b6 100644 --- a/cmd/v2/ui/views/sociaty_create.go +++ b/cmd/v2/ui/views/sociaty_create.go @@ -17,7 +17,7 @@ type SociatyCreateView struct { BaseformView } -func (this *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject { +func (app *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject { sociatyName := widget.NewEntry() notice := widget.NewMultiLineEntry() icon := widget.NewEntry() @@ -26,26 +26,29 @@ func (this *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject { applyLv := widget.NewEntry() applyLv.Text = "1" //默认 - this.form.AppendItem(widget.NewFormItem("公会名称", sociatyName)) - this.form.AppendItem(widget.NewFormItem("公告", notice)) - this.form.AppendItem(widget.NewFormItem("图标", icon)) - this.form.AppendItem(widget.NewFormItem("审批", isApplyCheck)) - this.form.AppendItem(widget.NewFormItem("入会等级", applyLv)) + app.form.AppendItem(widget.NewFormItem("公会名称", sociatyName)) + app.form.AppendItem(widget.NewFormItem("公告", notice)) + app.form.AppendItem(widget.NewFormItem("图标", icon)) + app.form.AppendItem(widget.NewFormItem("审批", isApplyCheck)) + app.form.AppendItem(widget.NewFormItem("入会等级", applyLv)) - this.form.OnSubmit = func() { + app.form.OnSubmit = func() { + req := &pb.SociatyCreateReq{ + Name: sociatyName.Text, + Icon: icon.Text, + Notice: notice.Text, + IsApplyCheck: isApplyCheck.Checked, + ApplyLv: cast.ToInt32(applyLv.Text), + } if err := service.GetPttService().SendToClient( string(comm.ModuleSociaty), sociaty.SociatySubTypeCreate, - &pb.SociatyCreateReq{ - Name: sociatyName.Text, - Icon: icon.Text, - Notice: notice.Text, - IsApplyCheck: isApplyCheck.Checked, - ApplyLv: cast.ToInt32(applyLv.Text), - }); err != nil { + req, + ); err != nil { logrus.Error(err) } + logrus.WithFields(logrus.Fields{"mainType": string(comm.ModuleSociaty), "subType": sociaty.SociatySubTypeCreate, "params": req}).Info("创建工会") } - this.form.SubmitText = "创建" - return this.form + app.form.SubmitText = "创建" + return app.form } diff --git a/cmd/v2/ui/views/sys_funclist.go b/cmd/v2/ui/views/sys_funclist.go index a147fa628..5365ff1e1 100644 --- a/cmd/v2/ui/views/sys_funclist.go +++ b/cmd/v2/ui/views/sys_funclist.go @@ -15,14 +15,16 @@ type SysFuncListView struct { func (this *SysFuncListView) CreateView(t *model.TestCase) fyne.CanvasObject { this.form.OnSubmit = func() { + req := &pb.SysFuncListReq{} if err := service.GetPttService().SendToClient( t.MainType, t.SubType, - &pb.SysFuncListReq{}, + req, ); err != nil { logrus.Error(err) return } + logrus.WithFields(logrus.Fields{"mainType": t.MainType, "subType": t.SubType, "params": req}).Info("功能开启列表") } return this.form } diff --git a/go.mod b/go.mod index 2461bd9c8..ce90ee710 100644 --- a/go.mod +++ b/go.mod @@ -178,6 +178,7 @@ require ( go.opentelemetry.io/otel/trace v1.6.3 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/zap v1.17.0 // indirect + golang.design/x/hotkey v0.4.0 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect diff --git a/go.sum b/go.sum index 77b371f61..c40139098 100644 --- a/go.sum +++ b/go.sum @@ -845,6 +845,8 @@ go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.design/x/hotkey v0.4.0 h1:jmY6QJdakEdYn0KBm48IZRw3emBpDXRhIWUHqPVsWBY= +golang.design/x/hotkey v0.4.0/go.mod h1:M8SGcwFYHnKRa83FpTFQoZvPO5vVT+kWPztFqTQKmXA= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=