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.

238 lines
7.3 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/gogf/gf/v2/errors/gerror"
"github.com/towgo/towgo/towgo"
"go.uber.org/zap"
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
err = deviceCallByDeviceID(deviceID, method, token, params, destResult)
// 构建日志基础信息
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)
}
// 辅助函数判断字节切片是否全是可打印ASCII32-126
func isPrintableASCII(b []byte) bool {
for _, c := range b {
if c < 32 || c > 126 {
return false
}
}
return true
}
func deviceCallByDeviceID(deviceID, method, token string, params, destResult any) error {
if serialPortMode {
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 gerror.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 gerror.Wrap(err, "json.Marshal(result) err")
}
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 = gerror.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),
}
}