airobot/lib/robot.go
2022-12-29 19:24:57 +08:00

403 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package lib
import (
"fmt"
"sort"
"sync"
"sync/atomic"
"time"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"legu.airobot/pb"
"legu.airobot/storage"
)
type IRobot interface {
// 发送消息
SendMsg(mainType, subType string, req proto.Message, rsp proto.Message) pb.ErrorCode
// 存储数据
Store(key string, data interface{})
// 获取数据
Get(key string) interface{}
// 停止运行
Stop() bool
}
type Robot struct {
IStore
ai *MyAI
scene IScene //场景
account string //账号
sid string //区服编号
conn *websocket.Conn //连接对象
data map[string]interface{} //机器人缓存数据
status uint32 //状态
cacheLock sync.Mutex //缓存锁
resLock sync.Mutex //结果处理锁
sceneQueue *Queue[IScene] //场景队列
config *storage.Config //配置
resultCh chan *CallResult //请求结果通道
ReportMap map[int]map[string]*Statistics //测试报告 key1:场景序号 key2:协议
elipseTotal time.Duration //场景总耗时
}
func NewRobot(ai *MyAI) *Robot {
robot := &Robot{
ai: ai,
data: make(map[string]interface{}),
sceneQueue: NewQueue[IScene](),
config: ai.config,
resultCh: make(chan *CallResult, 1000),
ReportMap: make(map[int]map[string]*Statistics),
}
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
}
//存数据
func (a *Robot) Store(key string, data interface{}) {
defer a.cacheLock.Unlock()
a.cacheLock.Lock()
a.data[key] = data
}
//取数据
func (a *Robot) Get(key string) interface{} {
defer a.cacheLock.Unlock()
a.cacheLock.Lock()
return a.data[key]
}
// 发送消息
func (r *Robot) SendMsg(mainType, subType string, req proto.Message, rsp proto.Message) (code pb.ErrorCode) {
start := time.Now()
defer func() {
t := time.Since(start)
// logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType, "rsp": rsp, "since": t.String()}).Debug("接收消息")
var name string
if r.scene != nil {
name = r.scene.Info().Name
}
// 发送请求结果
r.SendResult(&CallResult{
SceneName: name,
MainType: mainType,
SubType: subType,
Elapse: t,
Num: r.getSortNum(name),
Code: int32(code),
})
}()
head := &pb.UserMessage{MainType: mainType, SubType: subType}
if mainType == "user" && subType == "login" {
loginReq := req.(*pb.UserLoginReq)
head.Sec = BuildSecStr(loginReq.Sid, loginReq.Account)
} else {
head.Sec = BuildSecStr(r.sid, r.account)
}
if ProtoMarshal(req, head) {
data, _ := proto.Marshal(head)
if err := r.conn.WriteMessage(websocket.BinaryMessage, data); err != nil {
logrus.WithField("err", err).Error("写数据异常")
return pb.ErrorCode_SystemError
}
// logrus.WithFields(logrus.Fields{"MainType": mainType, "SubType": subType, "req": req}).Debug("发送消息")
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()
if err != nil {
logrus.WithField("err", err).Error("读数据异常")
r.ai.Obs.Notify(EVENT_REPORT, true)
r.conn.Close()
break
}
if !atomic.CompareAndSwapUint32(&callStatus, 0, 1) {
return
}
timer.Stop()
msg := &pb.UserMessage{}
if err := proto.Unmarshal(data, msg); err != nil {
logrus.Error("pb解析失败")
break
}
if msg.MainType == mainType && msg.SubType == subType {
if !ProtoUnmarshal(msg, rsp) {
logrus.Error("pb解析失败")
break
}
return
} else if msg.MainType == "notify" && msg.SubType == "errornotify" {
rsp := &pb.NotifyErrorNotifyPush{}
if !ProtoUnmarshal(msg, rsp) {
logrus.Error("pb解析失败")
break
}
code = rsp.Code
return
} else {
// logrus.Debugf("推送 %s.%s", msg.MainType, msg.SubType)
}
}
}
return pb.ErrorCode_Success
}
// 设置场景队列
func (a *Robot) SetScenes() {
scensConf := a.config.Scenes
sort.SliceStable(scensConf, func(i, j int) bool {
return scensConf[i].Num < scensConf[j].Num
})
for _, conf := range scensConf {
for _, v := range a.ai.iscenes {
info := v.Info()
if conf.Name == info.Name {
if conf.Loop == 0 || conf.Loop == 1 {
a.sceneQueue.Add(v)
continue
} else if conf.Loop > 1 {
for i := int32(0); i < conf.Loop; i++ {
a.sceneQueue.Add(v)
}
continue
}
}
}
}
}
// 根据场景名称获取顺序号
func (a *Robot) getSortNum(name string) int {
var num int
for _, v := range a.config.Scenes {
if v.Name == name {
return v.Num
}
}
return num
}
func (m *Robot) Start() bool {
if len(m.sceneQueue.List()) == 0 {
logrus.Warn("没有设置场景队列")
return false
}
// 创建链接
dialer := &websocket.Dialer{
HandshakeTimeout: 2 * time.Second,
}
conn, _, err := dialer.Dial(m.config.Global.WsAddr, nil)
if err != nil {
logrus.Error(err)
return false
}
m.conn = conn
if err := m.conn.SetReadDeadline(time.Now().Add(600 * time.Second)); err != nil {
logrus.Error("SetReadDeadline %v", err)
}
//检查具备启动的状态
if !atomic.CompareAndSwapUint32(
&m.status, STATUS_ORIGINAL, STATUS_STARTING) {
if !atomic.CompareAndSwapUint32(&m.status, STATUS_STOPPED, STATUS_STARTING) {
return false
}
}
//设置状态为已启动
atomic.StoreUint32(&m.status, STATUS_STARTED)
//显示场景结果
go m.processResult()
m.syncCall()
return true
}
func (m *Robot) Stop() bool {
for {
if _, err := m.sceneQueue.Pop(); err != nil {
return true
}
}
}
func (m *Robot) syncCall() {
for {
defer func() {
if p := recover(); p != nil {
err, ok := interface{}(p).(error)
var errMsg string
if ok {
errMsg = fmt.Sprintf("业务模块异常! (error: %s)", err)
} else {
errMsg = fmt.Sprintf("调用时Panic! (clue: %#v)", p)
}
logrus.Error(errMsg)
}
}()
scene, err := m.sceneQueue.Pop()
if err != nil {
logrus.WithField("err", err).Debug("所有场景执行结束")
m.prepareToStop()
return
}
m.scene = scene
info := m.scene.Info()
start := time.Now()
//这里执行会花很长时间
if err := scene.Run(m); err != nil {
logrus.WithField("err", err).Error("执行业务时发生错误")
continue
}
elapsedTime := time.Since(start)
m.elipseTotal += elapsedTime
logrus.WithField("t", elapsedTime.String()).Debug("场景【" + info.Name + "】执行完毕耗时统计")
m.ai.Obs.Notify(EVENT_PROGRESS, int32(1))
}
}
func (m *Robot) prepareToStop() {
atomic.CompareAndSwapUint32(&m.status, STATUS_STARTED, STATUS_STOPPING)
close(m.resultCh)
atomic.StoreUint32(&m.status, STATUS_STOPPED)
}
func (m *Robot) SendResult(result *CallResult) bool {
if atomic.LoadUint32(&m.status) != STATUS_STARTED {
m.printIgnoredResult(result, "停止发送")
return false
}
select {
case m.resultCh <- result:
// logrus.WithField("res", result).Debug("发送结果")
return true
default:
m.printIgnoredResult(result, "通道满了")
return false
}
}
func (m *Robot) processResult() {
for r := range m.resultCh {
head := fmt.Sprintf("%s.%s", r.MainType, r.SubType)
if routes, ok := m.ReportMap[r.Num]; ok {
if route, y := routes[head]; y {
max := route.MaxElapse
min := route.MinElapse
if r.Elapse > max {
max = r.Elapse
}
if r.Elapse < min {
min = r.Elapse
}
statis := &Statistics{
Route: head,
SceneName: r.SceneName,
}
if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
statis.TimeoutCount += route.TimeoutCount
} 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
}
routes[head] = statis
} else {
statis := &Statistics{
Route: head,
SceneName: r.SceneName,
CallCount: 1,
}
if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
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
}
routes[head] = statis
}
m.ReportMap[r.Num] = routes
} else {
route := make(map[string]*Statistics)
statis := &Statistics{
Route: head,
SceneName: r.SceneName,
CallCount: 1,
}
if r.Code == int32(pb.ErrorCode_TimestampTimeout) {
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
}
route[head] = statis
m.ReportMap[r.Num] = route
}
}
}
func (m *Robot) printIgnoredResult(result *CallResult, cause string) {
resultMsg := fmt.Sprintf(
"MainType=%s, SubType=%s, Elapse=%v",
result.MainType, result.SubType, result.Elapse)
logrus.Warnf("Ignored result: %s. (cause: %s)\n", resultMsg, cause)
}