超时统计修改

This commit is contained in:
wh_zcy 2022-12-16 11:09:19 +08:00
parent d8a8f55e65
commit fa07f06abc
9 changed files with 217 additions and 99 deletions

View File

@ -30,7 +30,8 @@ func (l *LoginScene) Run(robot lib.IRobot) error {
} }
rsp := &pb.UserLoginResp{} rsp := &pb.UserLoginResp{}
if code := robot.SendMsg("user", "login", req, rsp); code == pb.ErrorCode_Success { code := robot.SendMsg("user", "login", req, rsp)
if code == pb.ErrorCode_Success {
robot.Store("user.login", rsp) robot.Store("user.login", rsp)
} }
return nil return nil

View File

@ -15,7 +15,7 @@ import (
"legu.airobot/storage" "legu.airobot/storage"
) )
type myAI struct { type MyAI struct {
robots []*Robot robots []*Robot
scenes []*scene scenes []*scene
iscenes []IScene iscenes []IScene
@ -29,13 +29,12 @@ type myAI struct {
ReportMap map[int]map[string]*Statistics //测试报告 key1:场景 key2:协议 ReportMap map[int]map[string]*Statistics //测试报告 key1:场景 key2:协议
} }
func NewAI(aip AIParam) (*myAI, error) { func NewAI(aip AIParam) (*MyAI, error) {
if err := aip.Check(); err != nil { if err := aip.Check(); err != nil {
return nil, err return nil, err
} }
ai := &myAI{ ai := &MyAI{
Obs: NewObserver(),
scenes: make([]*scene, 0), scenes: make([]*scene, 0),
config: aip.Config, config: aip.Config,
iscenes: aip.Scenes, iscenes: aip.Scenes,
@ -49,7 +48,7 @@ func NewAI(aip AIParam) (*myAI, error) {
return ai, nil return ai, nil
} }
func (m *myAI) init() error { func (m *MyAI) init() error {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteString("初始化AI") buf.WriteString("初始化AI")
@ -66,13 +65,13 @@ func (m *myAI) init() error {
return nil return nil
} }
func (m *myAI) AppendRobot(robot *Robot) { func (m *MyAI) AppendRobot(robot *Robot) {
defer m.lock.Unlock() defer m.lock.Unlock()
m.lock.Lock() m.lock.Lock()
m.robots = append(m.robots, robot) m.robots = append(m.robots, robot)
} }
func (m *myAI) Start() bool { func (m *MyAI) Start() bool {
if len(m.config.Scenes) == 0 { if len(m.config.Scenes) == 0 {
logrus.Warn("还未设置场景") logrus.Warn("还未设置场景")
return false return false
@ -85,35 +84,20 @@ func (m *myAI) Start() bool {
robot := NewRobot(m) robot := NewRobot(m)
robot.SetScenes() robot.SetScenes()
m.AppendRobot(robot) m.AppendRobot(robot)
robot.Start()
atomic.AddUint32(&m.useCountTotal, 1) atomic.AddUint32(&m.useCountTotal, 1)
m.Obs.Notify(EVENT_ROBOT, int32(1))
robot.Start()
}() }()
} }
}() }()
go func() {
start := time.Now()
for {
total := atomic.LoadUint32(&m.useCountTotal)
if total == m.config.Global.UserCountTotal {
elipse := time.Since(start)
logrus.Debug("开始生成测试报告")
m.MergeResult()
m.genReport(elipse)
m.Obs.Notify(EVENT_PROGRESS, int32(0))
break
}
}
}()
return true return true
} }
func (m *myAI) Stop() { func (m *MyAI) Stop() {
} }
func (m *myAI) MergeResult() { func (m *MyAI) MergeResult() {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
n := make(map[int]map[string]*Statistics) n := make(map[int]map[string]*Statistics)
@ -148,6 +132,7 @@ func (m *myAI) MergeResult() {
statis.MinElapse = min statis.MinElapse = min
statis.Route = a.Route statis.Route = a.Route
statis.SceneName = a.SceneName statis.SceneName = a.SceneName
statis.TimeoutCount = (a.TimeoutCount + b.TimeoutCount)
if n[i][k] == nil { if n[i][k] == nil {
n[i] = make(map[string]*Statistics) n[i] = make(map[string]*Statistics)
} }
@ -179,7 +164,7 @@ func (m *myAI) MergeResult() {
m.ReportMap = n m.ReportMap = n
} }
func (m *myAI) genReport(elipse time.Duration) { func (m *MyAI) GenReport(elipse time.Duration) {
var msgs []string var msgs []string
var i []int var i []int
for key, _ := range m.ReportMap { for key, _ := range m.ReportMap {
@ -188,8 +173,8 @@ func (m *myAI) genReport(elipse time.Duration) {
sort.Ints(i) sort.Ints(i)
for _, routes := range i { for _, routes := range i {
for r, d := range m.ReportMap[routes] { for r, d := range m.ReportMap[routes] {
msgs = append(msgs, fmt.Sprintf("【%s】协议:%s 调用次数:%d 总耗时:%v 平均耗时:%v 最大耗时:%v 最小耗时:%v", msgs = append(msgs, fmt.Sprintf("【%s】协议:%s 调用次数:%d 总耗时:%v 平均耗时:%v 最大耗时:%v 最小耗时:%v 超时次数:%v",
d.SceneName, r, d.CallCount, d.ElapseTotal.String(), d.AvgElapse.String(), d.MaxElapse.String(), d.MinElapse.String())) d.SceneName, r, d.CallCount, d.ElapseTotal.String(), d.AvgElapse.String(), d.MaxElapse.String(), d.MinElapse.String(), d.TimeoutCount))
} }
} }
record := strings.Join(msgs, "\n") record := strings.Join(msgs, "\n")

View File

@ -1,6 +1,8 @@
package lib package lib
import "time" import (
"time"
)
type LoginParam struct { type LoginParam struct {
Account string `json:"account"` Account string `json:"account"`
@ -30,18 +32,20 @@ type CallResult struct {
SceneName string SceneName string
MainType string MainType string
SubType string SubType string
Code int32
Elapse time.Duration // 耗时 Elapse time.Duration // 耗时
Num int Num int
} }
type Statistics struct { type Statistics struct {
ElapseTotal time.Duration //总耗时 ElapseTotal time.Duration //总耗时
MaxElapse time.Duration //最大耗时 MaxElapse time.Duration //最大耗时
MinElapse time.Duration //最小耗时 MinElapse time.Duration //最小耗时
AvgElapse time.Duration //平均耗时 AvgElapse time.Duration //平均耗时
CallCount int64 //调用次数 CallCount int64 //调用次数
Route string //协议名称 Route string //协议名称
SceneName string //场景名称 SceneName string //场景名称
TimeoutCount int32 //超时次数
} }
const ( const (

View File

@ -3,4 +3,7 @@ package lib
const ( const (
EVENT_FLAG = "dispatch" EVENT_FLAG = "dispatch"
EVENT_PROGRESS = "progress" EVENT_PROGRESS = "progress"
EVENT_REPORT = "report"
EVENT_ROBOT = "robot"
EVENT_CLOSECON = "closeCon"
) )

View File

@ -27,14 +27,15 @@ type IRobot interface {
type Robot struct { type Robot struct {
IStore IStore
ai *myAI ai *MyAI
scene IScene //场景 scene IScene //场景
account string //账号 account string //账号
sid string //区服编号 sid string //区服编号
conn *websocket.Conn //连接对象 conn *websocket.Conn //连接对象
data map[string]interface{} //机器人缓存数据 data map[string]interface{} //机器人缓存数据
status uint32 //状态 status uint32 //状态
cache sync.Mutex //缓存锁 cacheLock sync.Mutex //缓存锁
resLock sync.Mutex //结果处理锁
sceneQueue *Queue[IScene] //场景队列 sceneQueue *Queue[IScene] //场景队列
config *storage.Config //配置 config *storage.Config //配置
resultCh chan *CallResult //请求结果通道 resultCh chan *CallResult //请求结果通道
@ -42,7 +43,7 @@ type Robot struct {
elipseTotal time.Duration //场景总耗时 elipseTotal time.Duration //场景总耗时
} }
func NewRobot(ai *myAI) *Robot { func NewRobot(ai *MyAI) *Robot {
robot := &Robot{ robot := &Robot{
ai: ai, ai: ai,
data: make(map[string]interface{}), data: make(map[string]interface{}),
@ -52,25 +53,35 @@ func NewRobot(ai *myAI) *Robot {
ReportMap: make(map[int]map[string]*Statistics), ReportMap: make(map[int]map[string]*Statistics),
} }
robot.Store("sid", ai.config.Global.SId) robot.Store("sid", ai.config.Global.SId)
ai.Obs.AddListener(EVENT_CLOSECON, Listener{
OnNotify: func(data interface{}, args ...interface{}) {
d := data.(bool)
if d {
robot.prepareToStop()
robot.conn.Close()
}
},
})
return robot return robot
} }
//存数据 //存数据
func (a *Robot) Store(key string, data interface{}) { func (a *Robot) Store(key string, data interface{}) {
defer a.cache.Unlock() defer a.cacheLock.Unlock()
a.cache.Lock() a.cacheLock.Lock()
a.data[key] = data a.data[key] = data
} }
//取数据 //取数据
func (a *Robot) Get(key string) interface{} { func (a *Robot) Get(key string) interface{} {
defer a.cache.Unlock() defer a.cacheLock.Unlock()
a.cache.Lock() a.cacheLock.Lock()
return a.data[key] return a.data[key]
} }
// 发送消息 // 发送消息
func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.Message) pb.ErrorCode { func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.Message) (code pb.ErrorCode) {
start := time.Now() start := time.Now()
defer func() { defer func() {
t := time.Since(start) t := time.Since(start)
@ -86,7 +97,9 @@ func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.M
SubType: subType, SubType: subType,
Elapse: t, Elapse: t,
Num: r.getSortNum(name), Num: r.getSortNum(name),
Code: int32(code),
}) })
}() }()
head := &pb.UserMessage{MainType: mainType, SubType: subType} head := &pb.UserMessage{MainType: mainType, SubType: subType}
@ -106,12 +119,42 @@ func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.M
// logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType, "req": req}).Debug("发送消息") // logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType, "req": req}).Debug("发送消息")
for { for {
// 调用状态0-未调用或调用中1-调用完成2-调用超时。
var callStatus uint32
timer := time.AfterFunc(time.Duration(r.config.Global.TimeoutMs*int32(time.Millisecond)), func() {
if !atomic.CompareAndSwapUint32(&callStatus, 0, 2) {
return
}
logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType,
"大于": r.config.Global.TimeoutMs}).Error("超时了")
var name string
if r.scene != nil {
name = r.scene.Info().Name
}
r.SendResult(&CallResult{
SceneName: name,
MainType: mainType,
SubType: subType,
Elapse: time.Duration(r.config.Global.TimeoutMs),
Num: r.getSortNum(name),
Code: int32(pb.ErrorCode_TimestampTimeout),
})
})
_, data, err := r.conn.ReadMessage() _, data, err := r.conn.ReadMessage()
if err != nil { if err != nil {
logrus.WithField("err", err).Error("读数据异常") logrus.WithField("err", err).Error("读数据异常")
r.ai.Obs.Notify(EVENT_REPORT, true)
r.conn.Close()
break break
} }
if !atomic.CompareAndSwapUint32(&callStatus, 0, 1) {
return
}
timer.Stop()
msg := &pb.UserMessage{} msg := &pb.UserMessage{}
if err := proto.Unmarshal(data, msg); err != nil { if err := proto.Unmarshal(data, msg); err != nil {
logrus.Error("pb解析失败") logrus.Error("pb解析失败")
@ -120,15 +163,20 @@ func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.M
if msg.MainType == mainType && msg.SubType == subType { if msg.MainType == mainType && msg.SubType == subType {
if !ProtoUnmarshal(msg, rsp) { if !ProtoUnmarshal(msg, rsp) {
logrus.Error("pb解析失败")
break break
} }
return pb.ErrorCode_Success return
} else if msg.MainType == "notify" && msg.SubType == "errornotify" { } else if msg.MainType == "notify" && msg.SubType == "errornotify" {
rsp := &pb.NotifyErrorNotifyPush{} rsp := &pb.NotifyErrorNotifyPush{}
if !ProtoUnmarshal(msg, rsp) { if !ProtoUnmarshal(msg, rsp) {
logrus.Error("pb解析失败")
break break
} }
return rsp.Code code = rsp.Code
return
} else {
// logrus.Debugf("推送 %s.%s", msg.MainType, msg.SubType)
} }
} }
} }
@ -189,6 +237,10 @@ func (m *Robot) Start() bool {
m.conn = conn m.conn = conn
if err := m.conn.SetReadDeadline(time.Now().Add(20 * time.Second)); err != nil {
logrus.Error("SetReadDeadline %v", err)
}
//检查具备启动的状态 //检查具备启动的状态
if !atomic.CompareAndSwapUint32( if !atomic.CompareAndSwapUint32(
&m.status, STATUS_ORIGINAL, STATUS_STARTING) { &m.status, STATUS_ORIGINAL, STATUS_STARTING) {
@ -207,7 +259,11 @@ func (m *Robot) Start() bool {
} }
func (m *Robot) Stop() bool { func (m *Robot) Stop() bool {
return false for {
if _, err := m.sceneQueue.Pop(); err != nil {
return true
}
}
} }
func (m *Robot) syncCall() { func (m *Robot) syncCall() {
@ -217,7 +273,7 @@ func (m *Robot) syncCall() {
err, ok := interface{}(p).(error) err, ok := interface{}(p).(error)
var errMsg string var errMsg string
if ok { if ok {
errMsg = fmt.Sprintf("调用时Panic! (error: %s)", err) errMsg = fmt.Sprintf("业务模块异常! (error: %s)", err)
} else { } else {
errMsg = fmt.Sprintf("调用时Panic! (clue: %#v)", p) errMsg = fmt.Sprintf("调用时Panic! (clue: %#v)", p)
} }
@ -283,46 +339,59 @@ func (m *Robot) processResult() {
min = r.Elapse min = r.Elapse
} }
statis := &Statistics{ statis := &Statistics{
Route: head, Route: head,
SceneName: r.SceneName, SceneName: r.SceneName,
ElapseTotal: route.ElapseTotal + r.Elapse, }
MaxElapse: max, if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
MinElapse: min, statis.TimeoutCount += route.TimeoutCount
CallCount: route.CallCount + 1, } else {
statis.ElapseTotal = route.ElapseTotal + r.Elapse
statis.MaxElapse = max
statis.MinElapse = min
statis.CallCount += 1
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
} }
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
routes[head] = statis routes[head] = statis
} else { } else {
statis := &Statistics{ statis := &Statistics{
Route: head, Route: head,
SceneName: r.SceneName, SceneName: r.SceneName,
ElapseTotal: r.Elapse, CallCount: 1,
MaxElapse: r.Elapse, }
MinElapse: r.Elapse, if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
CallCount: 1, statis.TimeoutCount = 1
} else {
statis.ElapseTotal = r.Elapse
statis.MaxElapse = r.Elapse
statis.MinElapse = r.Elapse
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
} }
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
routes[head] = statis routes[head] = statis
} }
m.ReportMap[r.Num] = routes m.ReportMap[r.Num] = routes
} else { } else {
route := make(map[string]*Statistics) route := make(map[string]*Statistics)
statis := &Statistics{ statis := &Statistics{
Route: head, Route: head,
SceneName: r.SceneName, SceneName: r.SceneName,
ElapseTotal: r.Elapse, CallCount: 1,
MaxElapse: r.Elapse, }
MinElapse: r.Elapse, if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
CallCount: 1, statis.TimeoutCount = 1
} else {
statis.ElapseTotal = r.Elapse
statis.MaxElapse = r.Elapse
statis.MinElapse = r.Elapse
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
} }
avg := (statis.MaxElapse + statis.MinElapse) / 2
statis.AvgElapse = avg
route[head] = statis route[head] = statis
m.ReportMap[r.Num] = route m.ReportMap[r.Num] = route
} }
} }
} }
func (m *Robot) printIgnoredResult(result *CallResult, cause string) { func (m *Robot) printIgnoredResult(result *CallResult, cause string) {

View File

@ -10,7 +10,7 @@ type IScene interface {
} }
type scene struct { type scene struct {
ai *myAI ai *MyAI
Name string Name string
Desc string Desc string
callerQueue *Queue[ICaller] //确定运行的caller队列 callerQueue *Queue[ICaller] //确定运行的caller队列
@ -20,7 +20,7 @@ type scene struct {
} }
// 创建场景 // 创建场景
func NewScene(ai *myAI, param SceneParam) *scene { func NewScene(ai *MyAI, param SceneParam) *scene {
s := &scene{ s := &scene{
ai: ai, ai: ai,
Name: param.Name, Name: param.Name,

View File

@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"math" "math"
"testing" "testing"
"time"
"github.com/Pallinder/go-randomdata"
"legu.airobot/lib" "legu.airobot/lib"
"legu.airobot/storage" "legu.airobot/storage"
) )
@ -132,7 +132,24 @@ func divide(a int, b int) int {
} }
func TestDiv(t *testing.T) { func TestStop(t *testing.T) {
timeout := func() {
fmt.Println("超时了")
}
read := func() {
time.Sleep(time.Second * 2)
fmt.Println(randomdata.Number(0, 5)) }
for {
timer := time.AfterFunc(time.Second*1, timeout)
read()
fmt.Println("read")
timer.Stop()
read()
fmt.Println("process")
break
}
} }

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"sync/atomic"
"time" "time"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
@ -29,18 +30,24 @@ var _ IWindow = (*MainWindow)(nil)
type MainWindow struct { type MainWindow struct {
UIImpl UIImpl
ai *lib.MyAI
w fyne.Window w fyne.Window
statusbar *statusBar //状态栏 statusbar *statusBar //状态栏
mainMenu *mainMenu //菜单 mainMenu *mainMenu //菜单
progress *widget.ProgressBar //进度条 progress *widget.ProgressBar //进度条
progressCounter int //进度条计数 progressCounter int32 //进度条计数
tipLabel *widget.Label //提示Label endProgress chan interface{}
startBtn *widget.Button //开始按钮 progressChan chan struct{}
tipLabel *widget.Label //提示Label
startBtn *widget.Button //开始按钮
robotCount int32
} }
func NewMainWdindow(ui *UIImpl) IWindow { func NewMainWdindow(ui *UIImpl) IWindow {
mw := &MainWindow{ mw := &MainWindow{
UIImpl: *ui, UIImpl: *ui,
progressChan: make(chan struct{}),
endProgress: make(chan interface{}, 1),
} }
globalWindow = mw globalWindow = mw
return mw return mw
@ -81,6 +88,7 @@ func (mw *MainWindow) changeContent(content fyne.CanvasObject) {
func (mw *MainWindow) startContainer() { func (mw *MainWindow) startContainer() {
mw.startBtn = widget.NewButton("启动", nil) mw.startBtn = widget.NewButton("启动", nil)
mw.startBtn.Disable() mw.startBtn.Disable()
config := mw.config config := mw.config
// 检查全局配置 // 检查全局配置
if config.Global == nil { if config.Global == nil {
@ -105,10 +113,24 @@ func (mw *MainWindow) startContainer() {
mw.progress = widget.NewProgressBar() mw.progress = widget.NewProgressBar()
mw.tipLabel = widget.NewLabel("") mw.tipLabel = widget.NewLabel("")
mw.progress.SetValue(0)
mw.UIImpl.obs.AddListener(lib.EVENT_PROGRESS, lib.Listener{
OnNotify: mw.showProgress,
})
mw.UIImpl.obs.AddListener(lib.EVENT_ROBOT, lib.Listener{
OnNotify: mw.listenRobot,
})
mw.startBtn.Enable() mw.startBtn.Enable()
mw.startBtn.OnTapped = func() { mw.startBtn.OnTapped = func() {
atomic.StoreInt32(&mw.robotCount, 0)
atomic.StoreInt32(&mw.progressCounter, 0)
mw.progress.SetValue(0)
mw.tipLabel.SetText("进行中...")
mw.startBtn.Disable()
param := lib.AIParam{ param := lib.AIParam{
Scenes: mw.UIImpl.scenes, Scenes: mw.UIImpl.scenes,
Config: mw.UIImpl.config, Config: mw.UIImpl.config,
@ -118,12 +140,11 @@ func (mw *MainWindow) startContainer() {
dialog.ShowError(err, mw.w) dialog.ShowError(err, mw.w)
return return
} }
mw.tipLabel.SetText("进行中...") ai.Obs = mw.UIImpl.obs
mw.startBtn.Disable() mw.ai = ai
ai.Obs.AddListener(lib.EVENT_PROGRESS, lib.Listener{
OnNotify: mw.showProgress,
})
ai.Start() ai.Start()
mw.showReport()
} }
content := container.NewBorder(mw.progress, nil, nil, nil, content := container.NewBorder(mw.progress, nil, nil, nil,
@ -131,19 +152,25 @@ func (mw *MainWindow) startContainer() {
mw.changeContent(content) mw.changeContent(content)
} }
func (mw *MainWindow) showReport() {
start := time.Now()
<-mw.progressChan
logrus.Debug("开始生成测试报告")
elipse := time.Since(start)
mw.ai.MergeResult()
mw.ai.GenReport(elipse)
mw.startBtn.Enable()
atomic.StoreInt32(&mw.progressCounter, 0)
mw.tipLabel.SetText("已生成报表")
}
func (mw *MainWindow) showProgress(data interface{}, args ...interface{}) { func (mw *MainWindow) showProgress(data interface{}, args ...interface{}) {
count := data.(int32) count := data.(int32)
if count == 0 { //表示结束 rc := atomic.LoadInt32(&mw.progressCounter)
mw.startBtn.Enable() atomic.StoreInt32(&mw.progressCounter, count+rc)
mw.progressCounter = 0
mw.tipLabel.SetText("已生成报表")
}
max := int(mw.config.Global.UserCountTotal) * len(mw.config.Scenes)
//计算进度 //计算进度
mw.progressCounter += int(count) max := int(mw.config.Global.UserCountTotal) * len(mw.config.Scenes)
expr := fmt.Sprintf("%v/%v", mw.progressCounter, max) expr := fmt.Sprintf("%v/%v", atomic.LoadInt32(&mw.progressCounter), max)
logrus.Debug(expr) logrus.Debug(expr)
r, err := engine.ParseAndExec(expr) r, err := engine.ParseAndExec(expr)
if err != nil { if err != nil {
@ -153,9 +180,19 @@ func (mw *MainWindow) showProgress(data interface{}, args ...interface{}) {
mw.progress.SetValue(r) mw.progress.SetValue(r)
if r >= 1 { if r >= 1 {
mw.tipLabel.SetText("正在处理结果...") mw.tipLabel.SetText("正在处理结果...")
mw.progressChan <- struct{}{}
logrus.Debug("....")
} }
} }
func (mw *MainWindow) listenRobot(data interface{}, args ...interface{}) {
count := data.(int32)
rc := atomic.LoadInt32(&mw.robotCount)
atomic.StoreInt32(&mw.robotCount, count+rc)
mw.tipLabel.SetText(fmt.Sprintf("投放用户数:%d/%d", atomic.LoadInt32(&mw.robotCount), mw.config.Global.UserCountTotal))
}
// 全局配置 // 全局配置
func (mw *MainWindow) configContainer() { func (mw *MainWindow) configContainer() {
config := mw.config.Global config := mw.config.Global

View File

@ -16,6 +16,7 @@ type UIImpl struct {
storage storage.Storage storage storage.Storage
config *storage.Config config *storage.Config
scenes []lib.IScene scenes []lib.IScene
obs lib.Observer
} }
func NewUI(app fyne.App, scenes []lib.IScene) (*UIImpl, error) { func NewUI(app fyne.App, scenes []lib.IScene) (*UIImpl, error) {
@ -39,6 +40,7 @@ func NewUI(app fyne.App, scenes []lib.IScene) (*UIImpl, error) {
storage: storage, storage: storage,
config: config, config: config,
scenes: scenes, scenes: scenes,
obs: lib.NewObserver(),
}, nil }, nil
} }