自动化工具

This commit is contained in:
zhaocy 2022-12-06 09:28:37 +08:00
parent f302428e60
commit ec75fee2b1
47 changed files with 1864 additions and 183 deletions

View File

@ -0,0 +1,42 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
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]
}

View File

@ -0,0 +1,41 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
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
}
}

View File

@ -0,0 +1,31 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
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
}

44
cmd/v2/game_testflow.json Normal file
View File

@ -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": ""
}
]

255
cmd/v2/lib/assistant.go Normal file
View File

@ -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("读取结果")
}
}

40
cmd/v2/lib/base.go Normal file
View File

@ -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
)

View File

@ -6,6 +6,12 @@ type (
WindowAction int64 WindowAction int64
) )
const (
MainType = "mainType"
SubType = "subType"
Params = "params"
)
const ( const (
WindowAspect_Normal WindowAspect = iota WindowAspect_Normal WindowAspect = iota
WindowAspect_FullScreen WindowAspect_FullScreen

View File

@ -71,6 +71,7 @@ package common
// zh // zh
const ( const (
APP_NAME = "机器人" APP_NAME = "机器人"
// app 子标题 [0.0.1 build-1] 应用名称 // app 子标题 [0.0.1 build-1] 应用名称
APP_WIN_TITLE = "%s [%s build-%d] %s" APP_WIN_TITLE = "%s [%s build-%d] %s"
WELCOME_MSG = "欢迎使用工具箱" WELCOME_MSG = "欢迎使用工具箱"
@ -94,6 +95,11 @@ const (
TOOLBAR_WEL = "欢迎" TOOLBAR_WEL = "欢迎"
TOOLBAR_TERM = "同步配置" TOOLBAR_TERM = "同步配置"
TOOLBAR_PB = "protobuf" TOOLBAR_PB = "protobuf"
TOOLBAR_AUTO = "自动化"
TOOLBAR_PERF_TIP = "使用说明"
TOOLBAR_PERF_CONF = "配置"
TOOLBAR_PERF_PB = "协议"
//monitor //monitor
APP_MONITOR_TITLE_ID = "编号" APP_MONITOR_TITLE_ID = "编号"

10
cmd/v2/lib/common/log.go Normal file
View File

@ -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"`
}

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io" "io"
"io/ioutil"
"math" "math"
"os" "os"
"path/filepath" "path/filepath"
@ -156,3 +157,15 @@ func ConvertFileSize(size int64) string {
return "0" 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
}
}

7
cmd/v2/lib/core.go Normal file
View File

@ -0,0 +1,7 @@
package lib
type Aiassistant interface {
Start() bool
Stop() error
ShowResult()
}

60
cmd/v2/lib/gopool.go Normal file
View File

@ -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))
}

23
cmd/v2/lib/handler.go Normal file
View File

@ -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
}

57
cmd/v2/lib/param.go Normal file
View File

@ -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
}

View File

@ -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"` //持续时间 秒
}

View File

@ -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)
}

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

@ -8,6 +8,7 @@ import (
"go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/service/observer"
"go_dreamfactory/cmd/v2/theme" "go_dreamfactory/cmd/v2/theme"
"go_dreamfactory/cmd/v2/ui" "go_dreamfactory/cmd/v2/ui"
"image/color"
"io" "io"
"log" "log"
"os" "os"
@ -17,6 +18,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/app" "fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@ -67,7 +69,13 @@ func main() {
// create a new ui // create a new ui
app := app.NewWithID("df-toolkit") app := app.NewWithID("df-toolkit")
app.SetIcon(theme.ResourceAppPng) 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) // logLifecycle(app)
//创建enter //创建enter
@ -78,7 +86,7 @@ func main() {
checkVersion(app, w) checkVersion(app, w)
} }
w.SetContent(container.NewGridWithColumns(2, w.SetContent(container.NewGridWithColumns(3,
widget.NewButton("工具", func() { widget.NewButton("工具", func() {
toolWindow := ui.NewToolWindow(appUI, w) toolWindow := ui.NewToolWindow(appUI, w)
toolWindow.CreateWindow(common.APP_NAME, 1499, 800, true) toolWindow.CreateWindow(common.APP_NAME, 1499, 800, true)
@ -88,7 +96,13 @@ func main() {
mainWindow := ui.NewMainWindow(appUI, w) mainWindow := ui.NewMainWindow(appUI, w)
mainWindow.CreateWindow(common.APP_NAME, 1499, 800, true) mainWindow.CreateWindow(common.APP_NAME, 1499, 800, true)
w.Hide() w.Hide()
}))) }),
widget.NewButton("自动化测试", func() {
perfWindow := ui.NewPerfWindow(appUI, w)
perfWindow.CreateWindow(common.APP_NAME, 600, 800, true)
w.Hide()
}),
))
w.SetFixedSize(true) w.SetFixedSize(true)
w.Resize(fyne.NewSize(400, 200)) w.Resize(fyne.NewSize(400, 200))
w.CenterOnScreen() w.CenterOnScreen()
@ -96,7 +110,7 @@ func main() {
app.Quit() app.Quit()
}) })
logrus.WithField("version", app.Metadata().Version).Info("app starting") logrus.WithField("version", app.Metadata().Version).Debug("app starting")
w.Show() w.Show()
appUI.Run() appUI.Run()
} }
@ -144,12 +158,13 @@ func setupConfig() (err error) {
} }
func setupLogger() (err error) { func setupLogger() (err error) {
logrus.SetFormatter(&logrus.TextFormatter{ logrus.SetFormatter(&logrus.JSONFormatter{
DisableColors: true, TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true, // DisableColors: true,
// FullTimestamp: true,
}) })
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.InfoLevel)
logrus.SetOutput(os.Stdout) logrus.SetOutput(os.Stdout)
//设置output,默认为stderr,可以为任何io.Writer比如文件*os.File //设置output,默认为stderr,可以为任何io.Writer比如文件*os.File

View File

@ -7,7 +7,7 @@ services:
- service: - service:
sid: "dfz" sid: "dfz"
name: 赵长远 name: 赵长远
url: ws://10.0.0.238:7891/gateway url: ws://10.0.5.145:7891/gateway
- service: - service:
sid: "df01" sid: "df01"
name: 内网 name: 内网

View File

@ -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()
}

View File

@ -71,14 +71,17 @@ func (p *PttServiceImpl) SendToClient(mainType, subType string, rsp proto.Messag
func (p *PttServiceImpl) Login(sid, account string) (code pb.ErrorCode) { func (p *PttServiceImpl) Login(sid, account string) (code pb.ErrorCode) {
head := &pb.UserMessage{MainType: string(comm.ModuleUser), SubType: user.UserSubTypeLogin} head := &pb.UserMessage{MainType: string(comm.ModuleUser), SubType: user.UserSubTypeLogin}
head.Sec = common.BuildSecStr(sid, account) head.Sec = common.BuildSecStr(sid, account)
if err := p.connService.SendMsg(head, &pb.UserLoginReq{ req := &pb.UserLoginReq{
Account: account, Account: account,
Sid: sid, Sid: sid,
}); err != nil { }
if err := p.connService.SendMsg(head, req); err != nil {
code = pb.ErrorCode_SystemError code = pb.ErrorCode_SystemError
logrus.WithField("err", err).Error("Login") logrus.WithField("err", err).Error("Login")
return return
} }
logrus.WithFields(logrus.Fields{common.MainType: string(comm.ModuleUser), common.SubType: user.UserSubTypeLogin,
common.Params: req}).Info("登录")
return return
} }

160
cmd/v2/service/wsCli.go Normal file
View File

@ -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
}

84
cmd/v2/ui/app_auto.go Normal file
View File

@ -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()
}

View File

@ -61,10 +61,10 @@ func (at *appContainer) openApp(app appInterface) error {
} }
// open default app // open default app
func (at *appContainer) openDefaultApp() (string, error) { func (at *appContainer) openDefaultApp(appName string) (string, error) {
var firstTab *container.TabItem var firstTab *container.TabItem
for _, app := range at.ai { for _, app := range at.ai {
if app.OpenDefault() { if app.OpenDefault() == appName {
if err := at.initApp(app); err != nil { if err := at.initApp(app); err != nil {
return app.GetAppName(), err return app.GetAppName(), err
} }

View File

@ -4,34 +4,47 @@ import (
"go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service"
"go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/service/observer"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
) )
type appInterface interface { type appInterface interface {
LazyInit(service service.PttService, obs observer.Observer) error LazyInit(service service.PttService, obs observer.Observer) error
GetTabItem() *container.TabItem GetTabItem() *container.TabItem
GetAppName() string GetAppName() string
OpenDefault() bool OpenDefault() string
GetUI() interface{}
OnClose() bool OnClose() bool
ShortCut() fyne.Shortcut
Icon() fyne.Resource
} }
var ( var (
appRegister = []appInterface{ appRegister = []appInterface{
&appMonitor{}, &appMonitor{},
&appTester{}, &appTester{},
} &appAuto{},
toolRegister = []appInterface{
&appWelcome{}, &appWelcome{},
&appGen{}, &appGen{},
&appPbGen{}, &appPbGen{},
&appLock{}, &appLock{},
&appTerm{}, &appTerm{},
&perfWelcome{},
&perfConf{},
&perfPb{},
} }
) )
type appAdapter struct { type appAdapter struct {
tabItem *container.TabItem tabItem *container.TabItem
ai []appInterface
}
func (a appAdapter) ShortCut() fyne.Shortcut {
panic("implement Shortcut")
} }
func (a appAdapter) LazyInit() error { func (a appAdapter) LazyInit() error {
@ -42,8 +55,12 @@ func (a appAdapter) GetAppName() string {
panic("implement GetAppName()") panic("implement GetAppName()")
} }
func (a appAdapter) OpenDefault() bool { func (a appAdapter) GetUI() interface{} {
return false panic("implement GetUI()")
}
func (a appAdapter) OpenDefault() string {
return ""
} }
func (a appAdapter) GetTabItem() *container.TabItem { func (a appAdapter) GetTabItem() *container.TabItem {
@ -53,3 +70,7 @@ func (a appAdapter) GetTabItem() *container.TabItem {
func (a appAdapter) OnClose() bool { func (a appAdapter) OnClose() bool {
return true return true
} }
func (a appAdapter) Icon() fyne.Resource {
return theme.FyneLogo()
}

View File

@ -10,6 +10,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding" "fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@ -28,38 +29,39 @@ type appMonitor struct {
monitorData *model.PushModelList monitorData *model.PushModelList
} }
func (this *appMonitor) LazyInit(service service.PttService, obs observer.Observer) error { func (app *appMonitor) LazyInit(service service.PttService, obs observer.Observer) error {
this.obs = obs app.obs = obs
this.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_MONITOR, theme.MediaVideoIcon(), nil) app.tabItem = container.NewTabItemWithIcon(common.TOOLBAR_MONITOR, theme.MediaVideoIcon(), nil)
content := container.NewMax() content := container.NewMax()
content.Objects = []fyne.CanvasObject{} content.Objects = []fyne.CanvasObject{}
// panel for output log // panel for output log
this.logPanel = widget.NewMultiLineEntry() app.logPanel = widget.NewMultiLineEntry()
this.logPanel.Wrapping = fyne.TextWrapWord app.logPanel.Wrapping = fyne.TextWrapWord
//clear button //clear button
clearBtn := widget.NewButtonWithIcon(common.APP_TESTCASE_BTN_CLEARLOG, theme.DeleteIcon(), func() { clearBtn := widget.NewButtonWithIcon(common.APP_TESTCASE_BTN_CLEARLOG, theme.DeleteIcon(), func() {
this.logPanel.SetText("") app.logPanel.SetText("")
this.monitorBinding.Set([]interface{}{}) 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() app.monitorBinding = binding.NewUntypedList()
this.monitorData = model.NewPushModelList() app.monitorData = model.NewPushModelList()
this.createMonitorList() app.createMonitorList()
// layout // 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) content.Objects = append(content.Objects, panel)
this.tabItem.Content = content app.tabItem.Content = content
this.Run() app.Run()
return nil return nil
} }
func (a *appMonitor) OpenDefault() bool { func (a *appMonitor) OpenDefault() string {
return true return common.TOOLBAR_MONITOR
} }
func (a *appMonitor) GetAppName() string { func (a *appMonitor) GetAppName() string {
@ -71,12 +73,12 @@ func (a appMonitor) OnClose() bool {
} }
// monitor list data // monitor list data
func (this *appMonitor) Run() { func (app *appMonitor) Run() {
this.obs.AddListener(observer.EVENT_APP_MONI, observer.Listener{ app.obs.AddListener(observer.EVENT_APP_MONI, observer.Listener{
OnNotify: func(d interface{}, args ...interface{}) { OnNotify: func(d interface{}, args ...interface{}) {
data := d.(*model.PushModel) data := d.(*model.PushModel)
this.monitorData.DataList = append(this.monitorData.DataList, data) app.monitorData.DataList = append(app.monitorData.DataList, data)
this.reloadMonitorData() app.reloadMonitorData()
common.ShowCanvasTip("收到新的数据推送,请打开[推送]页面") 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()
}

18
cmd/v2/ui/app_test.go Normal file
View File

@ -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)
}
}
}

View File

@ -11,6 +11,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@ -41,7 +42,7 @@ func (a *appTester) LazyInit(service service.PttService, obs observer.Observer)
intro := widget.NewLabel("") intro := widget.NewLabel("")
intro.Wrapping = fyne.TextWrapWord intro.Wrapping = fyne.TextWrapWord
caseContent := container.NewBorder( 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) { setNav := func(t *model.TestCase) {
defer a.progress.Hide() 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) 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
}

View File

@ -2,30 +2,70 @@ package ui
import ( import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
"github.com/sirupsen/logrus"
) )
type mainMenu struct { type mainMenu struct {
*fyne.MainMenu *fyne.MainMenu
sysMenu *fyne.Menu
appMenus []*fyne.MenuItem
settingMenu *fyne.Menu
testingMenu *fyne.MenuItem
quite *fyne.MenuItem
helpMenu *fyne.Menu helpMenu *fyne.Menu
sysLog *fyne.MenuItem sysLog *fyne.MenuItem
// aboutSelf *fyne.MenuItem // aboutSelf *fyne.MenuItem
} }
// func newMainMenu() *mainMenu { func newMainMenu() *mainMenu {
// var mm mainMenu var mm mainMenu
// // help // system
// mm.sysLog = fyne.NewMenuItem("Show Log", func() { mm.appMenus = make([]*fyne.MenuItem, len(appRegister))
// newLogViewer().Win.Show() for i, app := range appRegister {
// }) app := app
// mm.helpMenu = fyne.NewMenu("Help", mm.appMenus[i] = fyne.NewMenuItem(app.GetAppName(), func() {
// mm.sysLog, err := globalWin.at.openApp(app)
// // mm.aboutSelf, 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.quite = fyne.NewMenuItem("退出", globalWin.quiteHandle)
// mm.helpMenu, mm.quite.Icon = theme.LogoutIcon()
// ) mm.quite.IsQuit = true
// return &mm 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
}

View File

@ -7,6 +7,7 @@ import (
"go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service"
"go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/service/observer"
"go_dreamfactory/comm" "go_dreamfactory/comm"
"go_dreamfactory/lego/sys/log"
"go_dreamfactory/modules/user" "go_dreamfactory/modules/user"
"go_dreamfactory/pb" "go_dreamfactory/pb"
"strings" "strings"
@ -20,11 +21,11 @@ import (
"github.com/Pallinder/go-randomdata" "github.com/Pallinder/go-randomdata"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/cast" "github.com/spf13/cast"
"golang.design/x/hotkey"
) )
var ( var (
globalWin *MainWindowImpl globalWin *MainWindowImpl
toolWin *ToolWindowImpl
) )
type MainWindow interface { type MainWindow interface {
@ -40,6 +41,7 @@ type MainWindowImpl struct {
toys *toys // side toys *toys // side
sb *statusBar //状态栏 sb *statusBar //状态栏
at *appContainer //tabs at *appContainer //tabs
mm *mainMenu //菜单
} }
func NewMainWindow(ui *UIImpl, parent fyne.Window) MainWindow { func NewMainWindow(ui *UIImpl, parent fyne.Window) MainWindow {
@ -81,27 +83,35 @@ func (ui *MainWindowImpl) createWindowContainer() {
// tool bar // tool bar
toolbar := widget.NewToolbar( toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.MediaVideoIcon(), func() { widget.NewToolbarAction(theme.MediaVideoIcon(), func() {
openApp2(common.TOOLBAR_MONITOR) openApp(ui.at, common.TOOLBAR_MONITOR)
}), }),
widget.NewToolbarAction(theme.DocumentIcon(), func() { 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.NewToolbarSpacer(),
widget.NewToolbarAction(theme.FileIcon(), func() { // widget.NewToolbarAction(theme.FileIcon(), func() {
newLogViewer().Win.Show() // newLogViewer().Win.Show()
}), // }),
) )
ui.tb = newToolBar(toolbar) ui.tb = newToolBar(toolbar)
// Fun Toys // Fun Toys
ui.toys = newToys(ui.obs) ui.toys = newToys(ui.obs)
// Main Menu
ui.mm = newMainMenu()
ui.w.SetMainMenu(ui.mm.MainMenu)
// main app tabs // main app tabs
ui.at = newAppContainer(appRegister, ui.pttService, ui.obs) ui.at = newAppContainer(appRegister, ui.pttService, ui.obs)
content := container.NewBorder(ui.tb.toolbar, ui.sb.widget, nil, ui.toys.widget, ui.at) content := container.NewBorder(ui.tb.toolbar, ui.sb.widget, nil, ui.toys.widget, ui.at)
ui.w.SetContent(content) ui.w.SetContent(content)
ui.w.CenterOnScreen() ui.w.CenterOnScreen()
ui.w.SetCloseIntercept(ui.quiteHandle) ui.w.SetCloseIntercept(ui.quiteHandle)
ui.registerShortCut()
} }
func (ui *MainWindowImpl) SetStatusMsg(msg string) { func (ui *MainWindowImpl) SetStatusMsg(msg string) {
@ -115,7 +125,35 @@ func (ui *MainWindowImpl) quiteHandle() {
} }
ui.app.Quit() ui.app.Quit()
}, ui.w) }, 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 .... // CreateWindow ....
@ -169,6 +207,7 @@ func (ui *MainWindowImpl) createChooseServerPopUp(w fyne.Window) error {
func (ui *MainWindowImpl) createChooseServerWindow( func (ui *MainWindowImpl) createChooseServerWindow(
title string, title string,
ch chan string) fyne.Window { ch chan string) fyne.Window {
w := fyne.CurrentApp().NewWindow(title)
// choose server button func // choose server button func
makeButton := func(s *service.ServiceConf, parent fyne.Window) *widget.Button { makeButton := func(s *service.ServiceConf, parent fyne.Window) *widget.Button {
btn := widget.NewButton(s.Name, func() { btn := widget.NewButton(s.Name, func() {
@ -183,11 +222,12 @@ func (ui *MainWindowImpl) createChooseServerWindow(
dialog.ShowError(err, parent) dialog.ShowError(err, parent)
} else { } else {
ch <- fmt.Sprintf("%s:%s", s.SId, s.Name) ch <- fmt.Sprintf("%s:%s", s.SId, s.Name)
w.Close()
} }
}) })
return btn return btn
} }
w := fyne.CurrentApp().NewWindow(title)
sGrid := container.NewGridWithColumns(2) sGrid := container.NewGridWithColumns(2)
config := ui.configService.GetConfig() config := ui.configService.GetConfig()
if config != nil { if config != nil {
@ -225,7 +265,7 @@ func (ui *MainWindowImpl) createLoginWin(sid, sname string) {
if account.Text != "" { if account.Text != "" {
logrus.WithField("account", account.Text).Debug("submit login") logrus.WithField("account", account.Text).Debug("submit login")
ui.createWindowContainer() ui.createWindowContainer()
appName, err := ui.at.openDefaultApp() appName, err := ui.at.openDefaultApp(common.TOOLBAR_MONITOR)
if err != nil { if err != nil {
logrus.WithField("appName", appName).Error(err) logrus.WithField("appName", appName).Error(err)
} }

114
cmd/v2/ui/perf_conf.go Normal file
View File

@ -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
}

91
cmd/v2/ui/perf_pb.go Normal file
View File

@ -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()
}

58
cmd/v2/ui/perf_welcome.go Normal file
View File

@ -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
}

85
cmd/v2/ui/perfwindow.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -20,10 +20,10 @@ func newToolBar(items *widget.Toolbar) *toolBar {
} }
// open app2 // open app2
func openApp2(name string) { func openApp(ac *appContainer, name string) {
for _, app := range appRegister { for _, app := range appRegister {
if app.GetAppName() == name { if app.GetAppName() == name {
err := globalWin.at.openApp(app) err := ac.openApp(app)
if err != nil { if err != nil {
logrus.Error(fmt.Errorf("%s %v", app.GetAppName(), err)) 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))
}
}
}
}

View File

@ -15,7 +15,7 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout" "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/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -401,7 +401,7 @@ func openFolder(entry *widget.Entry, w fyne.Window) {
entry.Text = lu.Path() entry.Text = lu.Path()
entry.Refresh() entry.Refresh()
}, w) }, w)
luri, _ := storage.ListerForURI(storage.NewFileURI(".")) luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI("."))
dConf.SetLocation(luri) dConf.SetLocation(luri)
dConf.SetConfirmText("打开") dConf.SetConfirmText("打开")
dConf.SetDismissText("取消") dConf.SetDismissText("取消")
@ -419,7 +419,7 @@ func openFile(entry *widget.Entry, w fyne.Window) {
}, w) }, w)
dConf.SetConfirmText("打开") dConf.SetConfirmText("打开")
dConf.SetDismissText("取消") dConf.SetDismissText("取消")
dConf.SetFilter(storage.NewExtensionFileFilter([]string{".exe"})) dConf.SetFilter(fyne_storage.NewExtensionFileFilter([]string{".exe"}))
dConf.Resize(fyne.NewSize(750, 500)) dConf.Resize(fyne.NewSize(750, 500))
dConf.Show() dConf.Show()
} }

View File

@ -17,7 +17,7 @@ import (
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout" "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/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -47,7 +47,7 @@ func (this *appPbGen) LazyInit(ptService service.PttService, obs observer.Observ
entry.Text = lu.Path() entry.Text = lu.Path()
entry.Refresh() entry.Refresh()
}, toolWin.w) }, toolWin.w)
luri, _ := storage.ListerForURI(storage.NewFileURI(".")) luri, _ := fyne_storage.ListerForURI(fyne_storage.NewFileURI("."))
dConf.SetLocation(luri) dConf.SetLocation(luri)
dConf.SetConfirmText("打开") dConf.SetConfirmText("打开")
dConf.SetDismissText("取消") dConf.SetDismissText("取消")

View File

@ -40,19 +40,19 @@ type appTerm struct {
downloadList *common.ItemList //download列表 downloadList *common.ItemList //download列表
} }
func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observer) error { func (app *appTerm) LazyInit(ptService service.PttService, obs observer.Observer) error {
this.obs = obs app.obs = obs
this.sshService = &service.SSHService{} app.sshService = &service.SSHService{}
this.jsonList = common.NewItemList() app.jsonList = common.NewItemList()
//progress //progress
this.cProgress = widget.NewProgressBarInfinite() app.cProgress = widget.NewProgressBarInfinite()
this.cProgress.Hide() app.cProgress.Hide()
this.upProgress = widget.NewProgressBar() app.upProgress = widget.NewProgressBar()
this.upProgress.Hide() 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 := container.NewMax()
content.Objects = []fyne.CanvasObject{} content.Objects = []fyne.CanvasObject{}
@ -198,8 +198,8 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
// 连接 // 连接
connBtn.OnTapped = func() { connBtn.OnTapped = func() {
this.cProgress.Show() app.cProgress.Show()
this.cProgress.Start() app.cProgress.Start()
ciphers := []string{} ciphers := []string{}
if ip.Text == "" { if ip.Text == "" {
@ -230,21 +230,21 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
} }
connBtn.Disable() 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 { if err != nil {
dialog.ShowError(err, toolWin.w) dialog.ShowError(err, toolWin.w)
this.cProgress.Stop() app.cProgress.Stop()
this.cProgress.Hide() app.cProgress.Hide()
connBtn.Enable() connBtn.Enable()
return return
} else { } else {
this.jsonList.LoadItem(localDir.Text) app.jsonList.LoadItem(localDir.Text)
this.cProgress.Stop() app.cProgress.Stop()
this.cProgress.Hide() app.cProgress.Hide()
disConnBtn.Enable() disConnBtn.Enable()
syncBtn.Enable() syncBtn.Enable()
this.upProgress.Hide() app.upProgress.Hide()
allCancelBtn.Show() allCancelBtn.Show()
allSelBtn.Hide() allSelBtn.Hide()
refreshBtn.Enable() refreshBtn.Enable()
@ -260,7 +260,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
allSelBtn.Hide() allSelBtn.Hide()
allCancelBtn.Show() allCancelBtn.Show()
}() }()
this.jsonList.LoadItem(localDir.Text) app.jsonList.LoadItem(localDir.Text)
} }
refreshBtn.OnTapped = reloadItem refreshBtn.OnTapped = reloadItem
@ -268,7 +268,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
disConnBtn.Disable() disConnBtn.Disable()
disConnBtn.OnTapped = func() { disConnBtn.OnTapped = func() {
defer func() { defer func() {
this.jsonList.Reset() app.jsonList.Reset()
connBtn.Enable() connBtn.Enable()
disConnBtn.Disable() disConnBtn.Disable()
syncBtn.Disable() syncBtn.Disable()
@ -277,7 +277,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
refreshBtn.Disable() refreshBtn.Disable()
dlBtn.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() { syncNext := func() {
defer func() { defer func() {
syncBtn.Enable() syncBtn.Enable()
this.upProgress.Hide() app.upProgress.Hide()
reloadItem() reloadItem()
dialog.ShowInformation("提示", "所有文件均上传完毕,需等1-2分钟待文件热更", toolWin.w) dialog.ShowInformation("提示", "所有文件均上传完毕,需等1-2分钟待文件热更", toolWin.w)
}() }()
syncBtn.Disable() syncBtn.Disable()
this.upProgress.Show() app.upProgress.Show()
this.upProgress.SetValue(0) app.upProgress.SetValue(0)
len := len(this.jsonList.SelItemIds) len := len(app.jsonList.SelItemIds)
num := 0.0 num := 0.0
increment := func(wg *sync.WaitGroup) { increment := func(wg *sync.WaitGroup) {
num += float64(1) / float64(len) num += float64(1) / float64(len)
this.upProgress.SetValue(num) app.upProgress.SetValue(num)
wg.Done() wg.Done()
} }
for _, fileName := range this.jsonList.SelItemIds { for _, fileName := range app.jsonList.SelItemIds {
this.endProgress.Add(1) app.endProgress.Add(1)
go func(fn string) { go func(fn string) {
// time.Sleep(time.Second * time.Duration(rand.Intn(3))) // 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") logrus.WithField("err", err).Error("同步json")
common.ShowTip(err.Error()) common.ShowTip(err.Error())
} }
increment(&this.endProgress) increment(&app.endProgress)
// 移除已上传的 // 移除已上传的
this.jsonList.DeleteItem(fn) app.jsonList.DeleteItem(fn)
common.ShowTip(fmt.Sprintf("%s 成功上传", fn)) common.ShowTip(fmt.Sprintf("%s 成功上传", fn))
}(fileName) }(fileName)
} }
this.endProgress.Wait() app.endProgress.Wait()
this.upProgress.SetValue(1) app.upProgress.SetValue(1)
} }
syncBtn.OnTapped = func() { syncBtn.OnTapped = func() {
if this.sshService.Client == nil { if app.sshService.Client == nil {
dialog.ShowError(errors.New("请先连接终端"), toolWin.w) dialog.ShowError(errors.New("请先连接终端"), toolWin.w)
return return
} }
@ -395,7 +395,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
svnBtn.FocusGained() svnBtn.FocusGained()
return return
} else { } else {
if len(this.jsonList.SelItemIds) == 0 { if len(app.jsonList.SelItemIds) == 0 {
common.ShowTip("没有选择任何文件,或尝试点击【刷新】") common.ShowTip("没有选择任何文件,或尝试点击【刷新】")
return return
} }
@ -419,13 +419,13 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
// SVN更新 // SVN更新
svnNext := func() { svnNext := func() {
defer func() { defer func() {
this.cProgress.Hide() app.cProgress.Hide()
this.cProgress.Stop() app.cProgress.Stop()
svnBtn.Enable() svnBtn.Enable()
}() }()
svnBtn.Disable() svnBtn.Disable()
this.cProgress.Show() app.cProgress.Show()
this.cProgress.Start() 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` 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, arg := fmt.Sprintf(commandStr,
filepath.Join(workDir.Text, lubanCli.Text), filepath.Join(workDir.Text, lubanCli.Text),
@ -465,12 +465,12 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
allCancelBtn.Hide() allCancelBtn.Hide()
allSelBtn.Show() allSelBtn.Show()
}() }()
for i, v := range this.jsonList.CachedList.Items { for i, v := range app.jsonList.CachedList.Items {
this.jsonList.CachedList.Items[i].Checked = true app.jsonList.CachedList.Items[i].Checked = true
this.jsonList.SelItemIds = append(this.jsonList.SelItemIds, v.Text) app.jsonList.SelItemIds = append(app.jsonList.SelItemIds, v.Text)
this.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) 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() allCancelBtn.Show()
allSelBtn.Hide() allSelBtn.Hide()
}() }()
this.jsonList.SelItemIds = []string{} app.jsonList.SelItemIds = []string{}
for i, v := range this.jsonList.CachedList.Items { for i, v := range app.jsonList.CachedList.Items {
this.jsonList.CachedList.Items[i].Checked = false app.jsonList.CachedList.Items[i].Checked = false
this.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil)) app.jsonList.ItemList.UpdateItem(i, widget.NewCheck(v.Text, nil))
} }
this.jsonList.ItemList.Refresh() app.jsonList.ItemList.Refresh()
} }
// 搜索 // 搜索
searchEntry.PlaceHolder = "搜索" searchEntry.PlaceHolder = "搜索"
searchEntry.OnChanged = func(s string) { searchEntry.OnChanged = func(s string) {
if this.sshService.Client == nil { if app.sshService.Client == nil {
dialog.ShowError(errors.New("请先连接终端"), toolWin.w) dialog.ShowError(errors.New("请先连接终端"), toolWin.w)
return return
} }
@ -499,13 +499,13 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
} else { } else {
// go func() { // go func() {
newList := []common.Item{} newList := []common.Item{}
for _, v := range this.jsonList.SearchItem { for _, v := range app.jsonList.SearchItem {
if strings.Contains(v.Text, s) { if strings.Contains(v.Text, s) {
newList = append(newList, v) newList = append(newList, v)
} }
} }
this.jsonList.CachedList.Items = newList app.jsonList.CachedList.Items = newList
this.jsonList.ItemList.Refresh() app.jsonList.ItemList.Refresh()
// }() // }()
} }
} }
@ -513,7 +513,7 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
// 下载日志 // 下载日志
dlBtn.Disable() dlBtn.Disable()
dlBtn.OnTapped = func() { dlBtn.OnTapped = func() {
w := this.createDownloadWindow() w := app.createDownloadWindow()
w.Show() w.Show()
w.SetCloseIntercept(func() { w.SetCloseIntercept(func() {
dlBtn.Enable() dlBtn.Enable()
@ -523,19 +523,19 @@ func (this *appTerm) LazyInit(ptService service.PttService, obs observer.Observe
} }
// 创建json列表 // 创建json列表
this.jsonList.ItemList = this.jsonList.CreateDefaultCheckList() app.jsonList.ItemList = app.jsonList.CreateDefaultCheckList()
btns1 := container.NewHBox(helpBtn1, dlBtn, &layout.Spacer{}, saveBtn1, connBtn, disConnBtn) btns1 := container.NewHBox(helpBtn1, dlBtn, &layout.Spacer{}, saveBtn1, connBtn, disConnBtn)
btns2 := container.NewHBox(helpBtn2, &layout.Spacer{}, saveBtn2, excelBtn, svnBtn) btns2 := container.NewHBox(helpBtn2, &layout.Spacer{}, saveBtn2, excelBtn, svnBtn)
split := container.NewHSplit(container.NewVBox(canvas.NewText("---只能在非上产环境!!!同步Json的操作仅限于数值热更,非结构热更---", color.RGBA{255, 0, 0, 255}), configForm, 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), 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 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 return nil
} }

View File

@ -52,8 +52,8 @@ func (a *appWelcome) GetAppName() string {
return common.TOOLBAR_WEL return common.TOOLBAR_WEL
} }
func (a *appWelcome) OpenDefault() bool { func (a *appWelcome) OpenDefault() string {
return true return common.TOOLBAR_WEL
} }
func (a *appWelcome) ShortCut() fyne.Shortcut { func (a *appWelcome) ShortCut() fyne.Shortcut {

View File

@ -10,7 +10,7 @@ import (
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var toolWin *ToolWindowImpl
type ToolWindow interface { type ToolWindow interface {
WindowInterface WindowInterface
} }
@ -36,19 +36,19 @@ func NewToolWindow(ui *UIImpl, parent fyne.Window) ToolWindow {
toolbar := widget.NewToolbar( toolbar := widget.NewToolbar(
widget.NewToolbarAction(theme.ContentCopyIcon(), func() { widget.NewToolbarAction(theme.ContentCopyIcon(), func() {
openApp1(common.TOOLBAR_GEN) openApp(mw.at,common.TOOLBAR_GEN)
}), }),
widget.NewToolbarAction(theme.ContentAddIcon(), func() { widget.NewToolbarAction(theme.ContentAddIcon(), func() {
openApp1(common.TOOLBAR_PB) openApp(mw.at,common.TOOLBAR_PB)
}), }),
widget.NewToolbarAction(theme.DownloadIcon(), func() { widget.NewToolbarAction(theme.DownloadIcon(), func() {
openApp1(common.TOOLBAR_SEC) openApp(mw.at,common.TOOLBAR_SEC)
}), }),
widget.NewToolbarAction(theme.MailSendIcon(), func() { widget.NewToolbarAction(theme.MailSendIcon(), func() {
openApp1(common.TOOLBAR_TERM) openApp(mw.at,common.TOOLBAR_TERM)
}), }),
widget.NewToolbarSpacer(), widget.NewToolbarSpacer(),
@ -59,11 +59,16 @@ func NewToolWindow(ui *UIImpl, parent fyne.Window) ToolWindow {
mw.tb = newToolBar(toolbar) mw.tb = newToolBar(toolbar)
mw.at = newAppContainer(toolRegister, nil, ui.obs) mw.at = newAppContainer(appRegister, nil, ui.obs)
return mw return mw
} }
func (ui *ToolWindowImpl) GetWin() WindowInterface {
return toolWin
}
func (ui *ToolWindowImpl) CreateWindow(title string, width, height float32, _ bool) { func (ui *ToolWindowImpl) CreateWindow(title string, width, height float32, _ bool) {
w := ui.app.NewWindow(fmt.Sprintf("工具箱 %s %s", title, ui.app.Metadata().Version)) w := ui.app.NewWindow(fmt.Sprintf("工具箱 %s %s", title, ui.app.Metadata().Version))
ui.AddWindow("tool", w) 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, content := container.NewBorder(ui.tb.toolbar, ui.sb.widget,
nil, nil, ui.at) nil, nil, ui.at)
ui.w.SetContent(content) ui.w.SetContent(content)
appName, err := ui.at.openDefaultApp() appName, err := ui.at.openDefaultApp(common.TOOLBAR_WEL)
if err != nil { if err != nil {
logrus.WithField("appName", appName).Error(err) logrus.WithField("appName", appName).Error(err)
} }

View File

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"go_dreamfactory/cmd/v2/lib/storage"
"go_dreamfactory/cmd/v2/service" "go_dreamfactory/cmd/v2/service"
"go_dreamfactory/cmd/v2/service/observer" "go_dreamfactory/cmd/v2/service/observer"
"go_dreamfactory/cmd/v2/theme" "go_dreamfactory/cmd/v2/theme"
@ -9,6 +10,7 @@ import (
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"github.com/BabySid/gobase" "github.com/BabySid/gobase"
"github.com/sirupsen/logrus"
) )
var uid string var uid string
@ -27,6 +29,8 @@ type UIImpl struct {
pttService service.PttService pttService service.PttService
configService service.ConfigService configService service.ConfigService
obs observer.Observer obs observer.Observer
storage storage.Storage
config *storage.Config
} }
func NewUI(app fyne.App, func NewUI(app fyne.App,
@ -34,8 +38,21 @@ func NewUI(app fyne.App,
connService service.ConnService, connService service.ConnService,
pttService service.PttService, pttService service.PttService,
obs observer.Observer, obs observer.Observer,
) *UIImpl { ) (*UIImpl, error) {
app.Settings().SetTheme(&theme.MyTheme{}) 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{ return &UIImpl{
app: app, app: app,
windows: make(map[string]fyne.Window), windows: make(map[string]fyne.Window),
@ -44,7 +61,9 @@ func NewUI(app fyne.App,
connService: connService, connService: connService,
pttService: pttService, pttService: pttService,
obs: obs, obs: obs,
} storage: storage,
config: config,
}, nil
} }
func (ui *UIImpl) AddWindow(name string, w fyne.Window) { func (ui *UIImpl) AddWindow(name string, w fyne.Window) {

View File

@ -17,15 +17,15 @@ type BaseformView struct {
service service.PttService service service.PttService
} }
func (this *BaseformView) Init(service service.PttService, obs observer.Observer, w fyne.Window, res *widget.Entry) { func (view *BaseformView) Init(service service.PttService, obs observer.Observer, w fyne.Window, res *widget.Entry) {
this.service = service view.service = service
this.obs = obs view.obs = obs
this.w = w view.w = w
this.res = res view.res = res
this.form = widget.NewForm() view.form = widget.NewForm()
this.form.SubmitText = common.BUTTON_OK view.form.SubmitText = common.BUTTON_OK
} }
func (this *BaseformView) Load() { func (view *BaseformView) Load() {
this.form.OnSubmit() view.form.OnSubmit()
} }

View File

@ -17,7 +17,7 @@ type SociatyCreateView struct {
BaseformView BaseformView
} }
func (this *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject { func (app *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject {
sociatyName := widget.NewEntry() sociatyName := widget.NewEntry()
notice := widget.NewMultiLineEntry() notice := widget.NewMultiLineEntry()
icon := widget.NewEntry() icon := widget.NewEntry()
@ -26,26 +26,29 @@ func (this *SociatyCreateView) CreateView(t *model.TestCase) fyne.CanvasObject {
applyLv := widget.NewEntry() applyLv := widget.NewEntry()
applyLv.Text = "1" //默认 applyLv.Text = "1" //默认
this.form.AppendItem(widget.NewFormItem("公会名称", sociatyName)) app.form.AppendItem(widget.NewFormItem("公会名称", sociatyName))
this.form.AppendItem(widget.NewFormItem("公告", notice)) app.form.AppendItem(widget.NewFormItem("公告", notice))
this.form.AppendItem(widget.NewFormItem("图标", icon)) app.form.AppendItem(widget.NewFormItem("图标", icon))
this.form.AppendItem(widget.NewFormItem("审批", isApplyCheck)) app.form.AppendItem(widget.NewFormItem("审批", isApplyCheck))
this.form.AppendItem(widget.NewFormItem("入会等级", applyLv)) app.form.AppendItem(widget.NewFormItem("入会等级", applyLv))
this.form.OnSubmit = func() { app.form.OnSubmit = func() {
if err := service.GetPttService().SendToClient( req := &pb.SociatyCreateReq{
string(comm.ModuleSociaty),
sociaty.SociatySubTypeCreate,
&pb.SociatyCreateReq{
Name: sociatyName.Text, Name: sociatyName.Text,
Icon: icon.Text, Icon: icon.Text,
Notice: notice.Text, Notice: notice.Text,
IsApplyCheck: isApplyCheck.Checked, IsApplyCheck: isApplyCheck.Checked,
ApplyLv: cast.ToInt32(applyLv.Text), ApplyLv: cast.ToInt32(applyLv.Text),
}); err != nil { }
if err := service.GetPttService().SendToClient(
string(comm.ModuleSociaty),
sociaty.SociatySubTypeCreate,
req,
); err != nil {
logrus.Error(err) logrus.Error(err)
} }
logrus.WithFields(logrus.Fields{"mainType": string(comm.ModuleSociaty), "subType": sociaty.SociatySubTypeCreate, "params": req}).Info("创建工会")
} }
this.form.SubmitText = "创建" app.form.SubmitText = "创建"
return this.form return app.form
} }

View File

@ -15,14 +15,16 @@ type SysFuncListView struct {
func (this *SysFuncListView) CreateView(t *model.TestCase) fyne.CanvasObject { func (this *SysFuncListView) CreateView(t *model.TestCase) fyne.CanvasObject {
this.form.OnSubmit = func() { this.form.OnSubmit = func() {
req := &pb.SysFuncListReq{}
if err := service.GetPttService().SendToClient( if err := service.GetPttService().SendToClient(
t.MainType, t.MainType,
t.SubType, t.SubType,
&pb.SysFuncListReq{}, req,
); err != nil { ); err != nil {
logrus.Error(err) logrus.Error(err)
return return
} }
logrus.WithFields(logrus.Fields{"mainType": t.MainType, "subType": t.SubType, "params": req}).Info("功能开启列表")
} }
return this.form return this.form
} }

1
go.mod
View File

@ -178,6 +178,7 @@ require (
go.opentelemetry.io/otel/trace v1.6.3 // indirect go.opentelemetry.io/otel/trace v1.6.3 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/zap v1.17.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/crypto v0.0.0-20220829220503-c86fa9a7ed90
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect golang.org/x/image v0.0.0-20220601225756-64ec528b34cd // indirect
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect

2
go.sum
View File

@ -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 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= 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/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-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=