package reporter import ( "crypto/md5" "encoding/hex" "encoding/json" "fmt" "io" "net/url" "os" "hnyfkj.com.cn/rtu/bxs-sy/baseapp" ) type rpcRequest struct { JSONRPC string `json:"jsonrpc"` // 版本号, 固定: "2.0" Method string `json:"method"` // 调用方法, 执行函数名 Params json.RawMessage `json:"params,omitempty"` // 请求参数, 可选可为空 ID int `json:"id"` // 请求ID, 用于标识请求 } type rpcError struct { Code int `json:"code"` // 错误码 Message string `json:"message"` // 错误信息 } type rpcResponse struct { JSONRPC string `json:"jsonrpc"` // 版本号, 固定: "2.0" Result json.RawMessage `json:"result,omitempty"` // 响应结果, 可选可为空 Error *rpcError `json:"error,omitempty"` // 错误信息, 可选可为空 ID int `json:"id"` // 应答ID, 响应匹配请求 } func makeLoginReq(imei, iccid, rssi, appVersion, inheritDUI string) ([]byte, error) { type params struct { DeviceType string `json:"deviceType"` IMEI string `json:"imei"` ICCID string `json:"iccid"` RSSI string `json:"rssi"` AppVersion string `json:"appVersion"` InheritID string `json:"inheritDUI,omitempty"` // 继承的DUI, 可选可为空 } paramsVar := params{ DeviceType: "33", // 便携式色诱产品类型为: 33 IMEI: imei, ICCID: iccid, RSSI: rssi, AppVersion: appVersion, InheritID: inheritDUI, } paramsBytes, err := json.Marshal(paramsVar) if err != nil { return nil, err } req := rpcRequest{ JSONRPC: "2.0", Method: "login", Params: paramsBytes, ID: 1, } return json.Marshal(req) } func parseLoginResp(data []byte) (string, string, error) { var resp rpcResponse if err := json.Unmarshal(data, &resp); err != nil { return "", "", err } if resp.Error != nil { return "", "", fmt.Errorf("服务器返回错误: %d, %s", resp.Error.Code, resp.Error.Message) } var result struct { IMEI string `json:"imei"` DUI string `json:"DUI"` } if err := json.Unmarshal(resp.Result, &result); err != nil { return "", "", err } if len(result.DUI) != 14 || !isDecimal(result.DUI) { return "", "", fmt.Errorf("无效DUI: %q", result.DUI) } return result.IMEI, result.DUI, nil } func makeLogoutMsg(imei string) ([]byte, error) { type params struct { IMEI string `json:"imei"` } paramsVar := params{ IMEI: imei, } paramsBytes, err := json.Marshal(paramsVar) if err != nil { return nil, err } req := rpcRequest{ JSONRPC: "2.0", Method: "logout", Params: paramsBytes, ID: 1, } return json.Marshal(req) } func makeCustomError(err error) *rpcError { if err == nil { return nil } return &rpcError{-32000, fmt.Sprintf("Custom error: %v", err)} } type mcuParams struct { Version string `json:"version"` // 版本号 x.x.x.x, 定义: [major(8bit)].[minor(8bit)].[patch(8bit)].[build(8bit)] CtrlMode string `json:"ctrlMode"` // 定时控制模式(0=光控, 1=时控) LightDuration string `json:"lightDuration"` // 光控定时时长(小时, 1-10, 仅光控模式有效) StartHour string `json:"startHour"` // 时控开始时间(小时, 0-23, 仅时控模式有效) EndHour string `json:"endHour"` // 时控结束时间(小时, 0-23, 仅时控模式有效) PhotoIntervalMinutes string `json:"takePhotoIntervalMinutes"` // 拍照间隔(分钟) } func isDecimal(s string) bool { for _, ch := range s { if ch < '0' || ch > '9' { return false } } return s != "" // 不允许空字符串 } func parseMCUParams(data []byte) (mcuParams, error) { var req rpcRequest if err := json.Unmarshal(data, &req); err != nil { return mcuParams{}, err } if len(req.Params) == 0 { return mcuParams{}, fmt.Errorf("missing params") } var params mcuParams if err := json.Unmarshal(req.Params, ¶ms); err != nil { return mcuParams{}, err } var verCode baseapp.VersionCode err := verCode.FromString(params.Version) if err != nil || !isDecimal(params.CtrlMode) || !isDecimal(params.LightDuration) || !isDecimal(params.StartHour) || !isDecimal(params.EndHour) || !isDecimal(params.PhotoIntervalMinutes) { return mcuParams{}, fmt.Errorf("invalid params") } return params, nil } func parseUpgradeAppResp(data []byte) (serverAddr, loginUser, loginPass, remoteFile, md5value string, err error) { var req rpcRequest if err := json.Unmarshal(data, &req); err != nil { return "", "", "", "", "", err } if len(req.Params) == 0 { return "", "", "", "", "", fmt.Errorf("missing params") } var params struct { URL string `json:"url"` // 升级包的URL地址 MD5 string `json:"md5"` // 升级包的MD5校验值, 32个十六进制数字组成, 16字节 } if err := json.Unmarshal(req.Params, ¶ms); err != nil { return "", "", "", "", "", err } u, err := url.Parse(params.URL) if err != nil { return "", "", "", "", "", fmt.Errorf("invalid URL: %s", params.URL) } if u.Scheme != "ftp" { return "", "", "", "", "", fmt.Errorf("unsupported scheme: %s", u.Scheme) } host := u.Hostname() port := u.Port() if port == "" { port = "21" } serverAddr = fmt.Sprintf("%s:%s", host, port) loginUser = u.User.Username() loginPass, _ = u.User.Password() remoteFile = u.Path if remoteFile == "" { return "", "", "", "", "", fmt.Errorf("missing remote file path") } md5value = params.MD5 return serverAddr, loginUser, loginPass, remoteFile, md5value, nil } func fileMD5(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close() h := md5.New() if _, err := io.Copy(h, f); err != nil { return "", err } return hex.EncodeToString(h.Sum(nil)), nil }