You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

247 lines
7.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package maincontrollerClient
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/towgo/towgo/errors/terror"
"github.com/towgo/towgo/towgo"
"go.uber.org/zap"
"sync"
g "tgk-touch/internal/global"
)
var controllerClient *MainControllerClient
type Device struct {
ID int64 `json:"id" xorm:"pk"`
SerialNumber string `json:"serial_number" xorm:"index unique"`
Name string `json:"name"`
Classify string `json:"classify"`
IsOnline bool `json:"is_online" xorm:"-"`
LastSubsistTime int64 `json:"last_subsist_time"`
UpdatedAt int64 `xorm:"updated"`
CreatedAt int64 `xorm:"created"`
}
func ControllerClient() *MainControllerClient {
if controllerClient == nil {
controllerClient = NewMainControllerClient(MainControllerClientConfig{
ServerHost: g.Config().MainControllerClient.ServerHost,
SecretKey: g.Config().MainControllerClient.SecretKey,
})
}
return controllerClient
}
type MainControllerClientConfig struct {
ServerHost string `json:"server_host"`
SecretKey string `json:"secret_key"`
}
type MainControllerClient struct {
MainControllerClientConfig
*towgo.WebScoketClient
}
func NewMainControllerClient(config MainControllerClientConfig) *MainControllerClient {
g.Log().Info("初始化主控客户端")
ws := towgo.NewWebsocketClient(config.ServerHost, config.ServerHost)
m := &MainControllerClient{}
m.MainControllerClientConfig = config
m.WebScoketClient = ws
m.CallTimeOut = 10
m.OnConnect = func(wsc *towgo.WebScoketClient) {
m.mainControllerClientReg(wsc)
}
m.OnClose = func(wsc *towgo.WebScoketClient) {}
m.Connect()
return m
}
// 服务注册
func (m *MainControllerClient) mainControllerClientReg(wsc *towgo.WebScoketClient) {
g.Log().Info("注册主控客户端到服务端")
request := towgo.NewJsonrpcrequest()
request.Method = "/iotserver/maincontroller/reg"
request.Params = m.MainControllerClientConfig
wsc.Call(request, func(jrc towgo.JsonRpcConnection) {
if jrc.GetRpcResponse().Error.Code != 200 {
g.Log().Errorf("mainControllerClientReg call err {%v}", jrc.GetRpcResponse().Error.Message)
}
})
request.Await()
}
func getParamsType(params any) string {
switch params.(type) {
case []byte:
return "[]byte"
}
return ""
}
// 定义日志格式化回调类型,由调用者自定义友好显示内容
type LogFormatter func(deviceID, method string, params, result interface{}) (deviceType, cmdDetail, cmdDisplay, resultDisplay string)
// 带日志记录的设备调用函数,新增formatter参数用于自定义日志格式
func DeviceCallByDeviceID(deviceID, method, token string, params, destResult any, formatter LogFormatter) error {
var err error
// 最多重试3次
for i := 0; i < 3; i++ {
err = deviceCallByDeviceID(deviceID, method, token, params, destResult)
if err == nil {
break
}
}
// 构建日志基础信息
logEntry := New(deviceID, params, destResult)
if err != nil {
logEntry.Error = err.Error()
}
// 应用自定义格式化(如果提供)
if formatter != nil {
devType, cmdDetail, cmdDisp, resultDisp := formatter(deviceID, method, params, destResult)
if cmdDisp == "" {
cmdDisp = formatDefault(params)
}
if resultDisp == "" {
resultDisp = formatDefault(destResult)
}
logEntry.CmdDisply = cmdDisp
logEntry.ResultDisply = resultDisp
logEntry.DeviceType = devType
logEntry.CmdDetail = cmdDetail
} else {
// 默认格式化(处理字节切片等特殊类型)
logEntry.CmdDisply = formatDefault(params)
logEntry.ResultDisply = formatDefault(destResult)
}
zap.S().Debugf("%+v", logEntry)
return err
}
// formatDefault 默认格式化:优先用%q处理字符串类,特殊类型单独适配
func formatDefault(data interface{}) string {
if data == nil {
return "%q" // 明确标记nil,比"null"更符合%q的语义
}
// 1. 处理字符串:直接用%q,自动转义特殊字符
if s, ok := data.(string); ok {
return fmt.Sprintf("%q", s)
}
// 2. 处理字节切片:先转十六进制,再用%q包裹(避免乱码,且显式标记类型)
if b, ok := data.([]byte); ok {
// 若字节切片是可打印ASCII,优先转ASCII+%q;否则转十六进制
if isPrintableASCII(b) {
return fmt.Sprintf("[]byte(ascii): %q", string(b))
}
return fmt.Sprintf("[]byte(hex): %q", hex.EncodeToString(b))
}
// 3. 处理符文(rune):用%q显示字符+Unicode码点
if r, ok := data.(rune); ok {
return fmt.Sprintf("rune: %q (U+%04X)", r, r)
}
// 4. 复杂类型(结构体/切片/映射):用JSON序列化,字符串字段自动带%q效果
// (JSON序列化会自动转义特殊字符,等价于%q的最小化转义)
jsonBytes, err := json.MarshalIndent(data, "", " ")
if err == nil {
if string(jsonBytes) != "null" && string(jsonBytes) != "" {
return fmt.Sprintf("json: %q", string(jsonBytes)) // 用%q包裹JSON字符串,显式转义双引号
}
}
// 5. 其他基础类型(int/bool/float等):用%+v(%q会多一层引号,冗余)
return fmt.Sprintf("%+v", data)
}
// 辅助函数:判断字节切片是否全是可打印ASCII(32-126)
func isPrintableASCII(b []byte) bool {
for _, c := range b {
if c < 32 || c > 126 {
return false
}
}
return true
}
var pushDeviceLock sync.Mutex
func deviceCallByDeviceID(deviceID, method, token string, params, destResult any) error {
if serialPortMode {
pushDeviceLock.Lock()
defer pushDeviceLock.Unlock()
b, err := ToBytes(params)
if err != nil {
return err
}
result, err := serialPort.BIO(b)
if err != nil {
g.Log().Error("serialPort.BIO err:", err.Error())
return terror.Wrap(err, "serialPort.BIO result err")
}
b, err = json.Marshal(result)
if err != nil {
g.Log().Error("serialPort.BIO json.Marshal(result) err:", err.Error())
}
return json.Unmarshal(b, destResult)
}
deviceRequest := towgo.NewJsonrpcrequest()
deviceRequest.Method = method
deviceRequest.Session = token
deviceRequest.Params = params
deviceRequest.DataType = getParamsType(params)
mainControllerRequest := towgo.NewJsonrpcrequest()
mainControllerRequest.Method = "/iotserver/maincontroller/device/call"
mainControllerRequest.Params = deviceRequest
mainControllerRequest.Route.DestAddr = deviceID
var err error
ControllerClient().
Call(mainControllerRequest, func(jrc towgo.JsonRpcConnection) {
//g.Log().Debug(jrc.GetRpcResponse())
if jrc.GetRpcResponse().Error.Code != 200 {
err = terror.Newf("deviceId [%s],msg %s", deviceID, jrc.GetRpcResponse().Error.Message)
return
}
jrc.ReadResult(destResult)
})
mainControllerRequest.Await()
return err
}
type DeviceControllerLog struct {
DeviceId string `json:"device_id" xorm:"comment('网关Id')" `
DeviceType string `json:"device_type,omitempty" xorm:"comment('设备类型')"`
CmdDetail string `json:"cmd_detail" xorm:"text comment('命令描述')"`
Cmd string `json:"cmd" xorm:"text notnull comment('指令%q字符串')" `
Result string `json:"result" xorm:"text notnull comment('结果%q字符串')" `
CmdDisply string `json:"cmd_disply" xorm:"text comment('命令友好显示')"`
ResultDisply string `json:"result_disply" xorm:"text comment('结果友好显示')"`
Error string `json:"error" xorm:" varchar(255) comment('指令执行异常')" `
}
func (DeviceControllerLog) TableName() string {
return "device_controller_log"
}
func New(deviceId string, cmd, result interface{}) *DeviceControllerLog {
return &DeviceControllerLog{
DeviceId: deviceId,
Cmd: formatDefault(cmd),
Result: formatDefault(result),
}
}