qixiaoning
2025-07-22 550492cb85ea7e01831939f2866259193378f2f7
新增修改实时监控和摄像机配置相关接口
9个文件已修改
3个文件已添加
1386 ■■■■ 已修改文件
api-gateway/models/base.go 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-common/models/Record.go 510 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-common/models/camera.go 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-common/models/db.go 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-common/models/task.go 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-service/controllers/camera.go 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-service/main.go 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-service/models/db.go 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
camera-service/models/milvus.go 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sync-service/models/camera.go 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sync-service/service/cameraService.go 151 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sync-service/vo/camera.go 116 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
api-gateway/models/base.go
New file
@@ -0,0 +1,39 @@
package models
import (
    "gopkg.in/ini.v1"
)
type PathConfig struct {
    BasePath      string `json:"basePath"`
    DecodeUrl     string `json:"decodeUrl"`
    SmartUrl      string `json:"smartUrl"`
    MilvusUrl     string `json:"milvusUrl"`
    WvpToken      string `json:"wvpToken"`
    WvpUrl        string `json:"wvpUrl"`
    ZlmediaKitUrl string `json:"zlmediaKitUrl"`
    SqlitePath    string `json:"sqlitePath"`
}
func GetBasePath() string {
    cfg, _ := ini.Load("config.ini")
    // 读取默认分区
    basePath := cfg.Section("setting").Key("basepath").String()
    return basePath
}
func GetConfig() PathConfig {
    config := PathConfig{}
    cfg, _ := ini.Load("config.ini")
    // 读取默认分区
    config.DecodeUrl = cfg.Section("setting").Key("decodeBaseUrl").String()
    config.BasePath = cfg.Section("setting").Key("basepath").String()
    config.SmartUrl = cfg.Section("setting").Key("smartUrl").String()
    config.MilvusUrl = cfg.Section("setting").Key("milvusUrl").String()
    config.WvpToken = cfg.Section("setting").Key("wvpToken").String()
    config.WvpUrl = cfg.Section("setting").Key("wvpUrl").String()
    config.ZlmediaKitUrl = cfg.Section("setting").Key("zlmediaKitUrl").String()
    config.SqlitePath = cfg.Section("setting").Key("sqlitePath").String()
    return config
}
camera-common/models/Record.go
New file
@@ -0,0 +1,510 @@
package models
import (
    "context"
    "fmt"
    "math"
    "sort"
    "strings"
    "time"
    "github.com/milvus-io/milvus-sdk-go/v2/entity"
)
// DataOperator 数据操作相关方法
type DataOperator struct {
    client *MilvusClient
}
// NewDataOperator 创建数据操作器
func NewDataOperator(client *MilvusClient) *DataOperator {
    return &DataOperator{client: client}
}
type PicWidHei struct {
    PicW int `json:"picW"`
    PicH int `json:"picH"`
}
// 统计查询
type StaticRecord struct {
    Id                 int64                     `json:"id"`
    TaskId             []int64                   `json:"task_id"`
    RuleId             []int64                   `json:"rule_id"`
    EventLevelId       []int64                   `json:"event_level_id"`
    VideoPointId       int64                     `json:"video_point_id"`
    DetectId           []int64                   `json:"detect_id"`
    VideoName          string                    `json:"video_name"`
    TaskNames          []TaskOption              `json:"task_names"`
    CheckNames         []CheckOption             `json:"check_names"`
    RuleNames          []RuleOption              `json:"rule_names"`
    EventLevels        []DictOption              `json:"event_levels"`
    KnowledgeDocuments []KnowledgeDocumentOption `json:"knowledge_documents"`
    ImagePath          string                    `json:"image_path"`
    VideoPath          string                    `json:"video_path"`
    DetectTime         string                    `json:"detect_time"`
    IsWarning          int64                     `json:"is_warning"`
    ZhDescClass        string                    `json:"zh_desc_class"`
    TaskName           string                    `json:"task_name"`
    EventLevelName     string                    `json:"event_level_name"`
    DetectNum          int64                     `json:"detect_num"`
    Suggestion         string                    `json:"suggestion"`
    RiskDescription    string                    `json:"risk_description"`
    KnowledgeId        []int64                   `json:"knowledge_id"`
    IsDesc             int64                     `json:"is_desc"`
    CameraId           string                    `json:"cameraId"`
    CameraName         string                    `json:"cameraName"`
    CameraAddr         string                    `json:"cameraAddr"`
    PicDate            string                    `json:"picDate"`
    PicId              int64                     `json:"picId"`
    PicMaxUrl          []string                  `json:"picMaxUrl"`
    PicSrcUrl          []string                  `json:"picSrcUrl"`
    PicWH              PicWidHei                 `json:"picWH"`
    SdkName            string                    `json:"sdkName"`
    Content            string                    `json:"content"`
    AlarmRules         []AlarmRule               `json:"alarmRules"`
    LikeDate           string                    `json:"likeDate"`
    ShowLabels         string                    `json:"showLabels"`
    OtherLabels        string                    `json:"otherLabels"`
    VideoUrl           string                    `json:"videoUrl"`
    AnalyServerId      string                    `json:"analyServerId"`
    AnalyServerName    string                    `json:"analyServerName"`
    AnalyServerIp      string                    `json:"analyServerIp"`
    ClusterId          string                    `json:"clusterId"`
    IsAlarm            bool                      `json:"isAlarm"`
    IsAckAlarm         bool                      `json:"isAckAlarm"`
    IsCollect          bool                      `json:"isCollect"`
    IsDelete           bool                      `json:"isDelete"`
    BaseInfo           []*BaseCompareInfo        `json:"baseInfo"`
    TargetInfo         []TargetInfo              `json:"targetInfo"`
    FileId             string                    `json:"fileId"`     // 数据栈文件id
    DataSource         string                    `json:"dataSource"` // 数据来源:摄像机, 数据栈
}
type Point struct {
    X float64 `json:"x"`
    Y float64 `json:"y"`
}
type Points struct {
    TopLeft     Point `json:"topLeft"`
    BottomRight Point `json:"bottomRight"`
}
type BaseCompareInfo struct {
    TableId      string  `json:"tableId"`
    TableName    string  `json:"tableName"`
    BwType       string  `json:"bwType"`
    CompareScore float64 `json:"compareScore"`
    TargetId     string  `json:"targetId"`
    TargetName   string  `json:"targetName"`
    TargetPicUrl string  `json:"targetPicUrl"`
    MonitorLevel string  `json:"monitorLevel"`
    Content      string  `json:"content"`
    DbLabel      string  `json:"labels"`
}
type TargetInfo struct {
    TargetId        string  `json:"targetId"`
    AreaId          string  `json:"areaId"`
    AreaName        string  `json:"areaName"`
    TargetScore     float64 `json:"targetScore"`
    TargetType      string  `json:"targetType"`
    Feature         string  `json:"feature"`
    PicSmUrl        string  `json:"picSmUrl"`
    TargetLocation  Points  `json:"targetLocation"`
    BelongsTargetID string  `json:"belongsTargetId"`
    Attribute       string  `json:"attribute"`
}
type AlarmRule struct {
    AlarmLevel   string `db:"alarmLevel" json:"alarmLevel"`
    DefenceState bool   `db:"defenceState" json:"defenceState"`
    GroupId      string `db:"groupId" json:"groupId"`
    LinkInfo     string `db:"linkInfo" json:"linkInfo"`
    RuleText     string `db:"ruleText" json:"ruleText"`
}
type TaskOption struct {
    TaskId   int64  `db:"task_id" json:"taskId"`
    TaskName string `db:"task_name" json:"taskName"`
}
type CheckOption struct {
    TaskId   int64  `db:"task_id" json:"taskId,omitempty"`
    CheckId  int64  `db:"check_id" json:"checkId"`
    FileName string `db:"file_name" json:"fileName"`
}
type RuleOption struct {
    TaskId   int64  `db:"task_id" json:"taskId,omitempty"`
    RuleId   int64  `db:"rule_id" json:"ruleId"`
    FileName string `db:"file_name" json:"fileName"`
}
// 字典
type DictOption struct {
    DictId    int64  `db:"dict_id" json:"dictId"`
    DictName  string `db:"dict_name" json:"dictName"`
    DictValue string `db:"dict_value" json:"dictValue"`
    DictType  string `db:"dict_type" json:"dictType"`
}
// 知识库
type KnowledgeDocumentOption struct {
    Id      string `db:"id" json:"id"`
    Title   string `db:"file_name" json:"title"`
    KnowId  int64  `db:"know_id" json:"know_id"`
    FileUrl string `db:"file_name" json:"file_url"`
}
// Pagination 分页信息
type Pagination struct {
    Page      int `json:"page"`      // 当前页码
    PageSize  int `json:"pageSize"`  // 每页大小
    Total     int `json:"total"`     // 总记录数
    TotalPage int `json:"totalPage"` // 总页数
}
// PaginatedResult 分页结果
type PaginatedResult struct {
    Items      []StaticRecord `json:"list"`
    Pagination Pagination     `json:"pagination"`
}
type PaginatedResult2 struct {
    Items      map[string]interface{} `json:"list"`
    Pagination Pagination             `json:"pagination"`
}
// 记录
type RecordReq struct {
    Ids      []string `json:"ids"`
    VideoIds []string `json:"treeNodes"`
    TaskIds  []int64  `json:"taskIds`
    Warning  int64    `json:"warning`
    Page     int64    `json:"page"`
    PageSize int64    `json:"pageSize"`
}
// 根据分页取数据
func GetWithPage(collectionName string, pageNum int64, pageSize int64, filter string) (*PaginatedResult, error) {
    if pageNum < 1 {
        pageNum = 1
    }
    if pageSize < 1 {
        pageSize = 10
    }
    if dbClient == nil {
        return nil, nil
    }
    dataOp := &DataOperator{client: dbClient}
    fmt.Println(filter)
    records, err := dataOp.queryWithPagination(collectionName, filter, pageNum, pageSize)
    if err != nil {
        return nil, err
    }
    return records, nil
}
// 转换数据
func convertResultToMap(result []entity.Column) []StaticRecord {
    if len(result) == 0 {
        return nil
    }
    count := result[0].Len()
    var records []StaticRecord
    for i := 0; i < count; i++ {
        record := StaticRecord{}
        for _, field := range result {
            switch field.Name() {
            case "id":
                IdColumn := field.(*entity.ColumnInt64).Data()
                if len(IdColumn) > 0 {
                    record.Id = IdColumn[i]
                }
            case "task_id":
                TaskIdColumn := field.(*entity.ColumnInt64Array).Data()
                if len(TaskIdColumn) > 0 {
                    record.TaskId = TaskIdColumn[i]
                }
            case "rule_id":
                RuleIdColumn := field.(*entity.ColumnInt64Array).Data()
                if len(RuleIdColumn) > 0 {
                    record.RuleId = RuleIdColumn[i]
                }
            case "event_level_id":
                EventLevelIdColumn := field.(*entity.ColumnInt64Array).Data()
                if len(EventLevelIdColumn) > 0 {
                    record.EventLevelId = EventLevelIdColumn[i]
                }
            case "video_point_id":
                VideoPointIdColumn := field.(*entity.ColumnInt64).Data()
                if len(VideoPointIdColumn) > 0 {
                    record.VideoPointId = VideoPointIdColumn[i]
                }
            case "detect_id":
                DetectIdColumn := field.(*entity.ColumnInt64Array).Data()
                if len(DetectIdColumn) > 0 {
                    record.DetectId = DetectIdColumn[i]
                }
            case "image_path":
                ImagePathColumn := field.(*entity.ColumnVarChar).Data()
                if len(ImagePathColumn) > 0 {
                    record.ImagePath = ImagePathColumn[i]
                }
            case "video_path":
                VideoPathColumn := field.(*entity.ColumnVarChar).Data()
                if len(VideoPathColumn) > 0 {
                    record.VideoPath = VideoPathColumn[i]
                }
            case "zh_desc_class":
                zhColumn := field.(*entity.ColumnVarChar).Data()
                if len(zhColumn) > 0 {
                    record.ZhDescClass = zhColumn[i]
                }
            case "task_name":
                TNColumn := field.(*entity.ColumnVarChar).Data()
                if len(TNColumn) > 0 {
                    record.TaskName = TNColumn[i]
                }
            case "event_level_name":
                EVColumn := field.(*entity.ColumnVarChar).Data()
                if len(EVColumn) > 0 {
                    record.EventLevelName = EVColumn[i]
                }
            case "is_desc":
                descColumn := field.(*entity.ColumnInt64).Data()
                if len(descColumn) > 0 {
                    record.IsDesc = descColumn[i]
                }
            case "detect_num":
                DEColumn := field.(*entity.ColumnInt64).Data()
                if len(DEColumn) > 0 {
                    record.DetectNum = DEColumn[i]
                }
            case "is_waning":
                warnColumn := field.(*entity.ColumnInt64).Data()
                if len(warnColumn) > 0 {
                    record.IsWarning = warnColumn[i]
                }
            case "detect_time":
                DetectTimeColumn := field.(*entity.ColumnVarChar).Data()
                if len(DetectTimeColumn) > 0 {
                    record.DetectTime = DetectTimeColumn[i]
                }
            case "knowledge_id":
                KnowledgeIdColumn := field.(*entity.ColumnInt64Array).Data()
                if len(KnowledgeIdColumn) > 0 {
                    record.KnowledgeId = KnowledgeIdColumn[i]
                }
            case "suggestion":
                SuggestionColumn := field.(*entity.ColumnVarChar).Data()
                if len(SuggestionColumn) > 0 {
                    record.Suggestion = SuggestionColumn[i]
                }
            case "risk_description":
                RiskDescriptionColumn := field.(*entity.ColumnVarChar).Data()
                if len(RiskDescriptionColumn) > 0 {
                    record.RiskDescription = RiskDescriptionColumn[i]
                }
            }
        }
        records = append(records, record)
    }
    return records
}
// 分页查询
func (do *DataOperator) queryWithPagination(collectionName string, filterExpr string, pageNum int64, pageSize int64) (*PaginatedResult, error) {
    ctx := context.Background()
    // 计算偏移量
    //offset := (pageNum - 1) * pageSize
    // 构建查询表达式(Milvus 2.1+ 支持 limit/offset)
    //queryExpr := fmt.Sprintf("%s limit %d offset %d", filterExpr, pageSize, offset)
    total, err := do.getTotalCount(collectionName, filterExpr)
    if err != nil {
        return nil, err
    }
    // 计算总页数
    totalPages := int(math.Ceil(float64(total) / float64(pageSize)))
    // 执行查询
    result, err := do.client.client.Query(
        ctx,
        collectionName,
        []string{}, // 所有分区
        filterExpr,
        []string{"rule_id", "task_id", "is_waning", "zh_desc_class", "task_name", "event_level_name", "detect_num",
            "event_level_id", "video_point_id", "detect_id", "image_path",
            "video_path", "detect_time", "knowledge_id", "risk_description", "suggestion", "id", "is_desc"}, // 返回所有字段
        // client.WithLimit(pageSize),
        // client.WithOffset(offset),
    )
    if err != nil {
        return nil, fmt.Errorf("分页查询失败: %v", err)
    }
    lists := convertResultToMap(result)
    layout := "2006-01-02 15:04:05.999999"
    var temp1 time.Time
    var temp2 time.Time
    //排序
    sort.Slice(lists, func(i, j int) bool {
        //return lists[i].DetectTime > lists[j].:DetectTime
        temp1, _ = time.Parse(layout, lists[i].DetectTime)
        temp2, _ = time.Parse(layout, lists[j].DetectTime)
        return temp1.After(temp2)
    })
    items := Paginate(lists, int(pageNum), int(pageSize))
    return &PaginatedResult{
        Items: items,
        Pagination: Pagination{
            Page:      int(pageNum),
            PageSize:  int(pageSize),
            Total:     int(total),
            TotalPage: totalPages,
        },
    }, nil
}
// 分页数据
func Paginate(data []StaticRecord, page, pageSize int) []StaticRecord {
    start := (page - 1) * pageSize
    if start >= len(data) {
        return nil
    }
    end := start + pageSize
    if end > len(data) {
        end = len(data)
    }
    return data[start:end]
}
// 统计数量
func (do *DataOperator) getTotalCount(collectionName string, filterExpr string) (int64, error) {
    result, err := do.client.client.Query(
        context.Background(),
        collectionName,
        []string{},
        filterExpr,
        []string{"count(*)"},
    )
    if err != nil {
        return 0, err
    }
    return result[0].(*entity.ColumnInt64).Data()[0], nil
}
// 根据id获取任务
func GetTaskByIds(ids []int64) (items []TaskOption, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `select task_id,task_name from mal_smart_task where task_id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
// 根据id获取视频
func GetVideoById(vid int64) (video Camera, err error) {
    // 如果返回的是指针,需要初始化
    sqlStr := "select id,name,alias,type,addr,rtsp,is_running,run_type,run_enable FROM cameras where video_id=?"
    if err := db.Raw(sqlStr, vid).Scan(&video).Error; err != nil {
        return Camera{}, err
    }
    return
}
// 根据id获取检测内容
func GetCheckByIds(ids []int64) (items []CheckOption, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `select check_id,file_name from mal_check_content where check_id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr, args...).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
// 根据id获取规则
func GetRuleByIds(ids []int64) (items []RuleOption, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `select rule_id,file_name from mal_warning_rule where rule_id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr, args...).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
// 获取事件
func GetEventByIds(ids []int64) (items []DictOption, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `select dict_id,dict_name,dict_value from mal_dict_type where dict_id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr, args...).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
// 根据知识库id查数据
func GetKnowledgeDocumentByIds(ids []int64) (items []KnowledgeDocumentOption, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `SELECT id, know_id, file_name FROM mal_knowledge_document where id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr, args...).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
// 根据知识库id查数据
func GetCameraIds(ids []string) (items []Camera, err error) {
    placeholders := make([]string, len(ids))
    args := make([]interface{}, len(ids))
    for i, id := range ids {
        placeholders[i] = "?"
        args[i] = id
    }
    sqlStr := `select id,name,alias,type,addr,rtsp,is_running,run_type,run_enable,video_id FROM cameras where id in (` + strings.Join(placeholders, ",") + `)`
    if err := db.Raw(sqlStr, args...).Scan(&items).Error; err != nil {
        return nil, err
    }
    return
}
camera-common/models/camera.go
@@ -1,14 +1,15 @@
package models
import (
    "basic.com/pubsub/protomsg.git"
    "basic.com/valib/logger.git"
    "encoding/json"
    "fmt"
    "strconv"
    "strings"
    "vamicro/camera-common/pub"
    "vamicro/config"
    "basic.com/pubsub/protomsg.git"
    "basic.com/valib/logger.git"
)
const (
@@ -55,6 +56,7 @@
    VoiceEnable    bool   `gorm:"column:voiceEnable;default:0" json:"voiceEnable"`
    VoiceId        string `gorm:"column:voiceId" json:"voiceId"`
    CoordTransform string `gorm:"column:coordTransform" json:"coordTransform"` //坐标转换,长春追踪reid用
    VideoId        int    `gorm:"column:video_id" json:"video_id"`             //摄像机id
}
func (Camera) TableName() string {
@@ -86,8 +88,8 @@
    return cameraEMap
}
//cameraType (0:查全部,1:分析摄像机,2:监控摄像机)
//cameraName
// cameraType (0:查全部,1:分析摄像机,2:监控摄像机)
// cameraName
func (camera *Camera) Find(cameraType int, cameraName string, camType int, isPlatform int, linkStr string) (camMenus []CameraTreeNode, err error) {
    logger.Debug("camType:", camType)
    cType := strconv.Itoa(camType)
camera-common/models/db.go
@@ -1,8 +1,17 @@
package models
import "github.com/jinzhu/gorm"
import (
    "github.com/jinzhu/gorm"
    "github.com/milvus-io/milvus-sdk-go/v2/client"
)
var db *gorm.DB
var dbClient *MilvusClient
// MilvusClient 封装Milvus操作的结构体
type MilvusClient struct {
    client client.Client
}
func SetDB(h *gorm.DB) {
    db = h
@@ -11,3 +20,11 @@
func GetDB() *gorm.DB {
    return db
}
func SetClientDB(h client.Client) {
    dbClient = &MilvusClient{client: h}
}
func GetClientDB() *MilvusClient {
    return dbClient
}
camera-common/models/task.go
@@ -1 +1,38 @@
package models
import (
    "fmt"
    "strings"
)
// 智查任务
type Task struct {
    TaskId   int64  `db:"task_id" json:"taskId"`
    TaskName string `db:"task_name" json:"taskName"`
}
// 视频
type Video struct {
    VideoIds []string `json:"videoIds"`
}
// 查询列表数据
func GetListTask(ids []string) (items []Task, err error) {
    //idList := strings.Join(ids, ",")
    idList := "'" + strings.Join(ids, "','") + "'"
    // 主查询SQL
    //sqlStr := `select c.task_id, c.task_name from mal_task_video_link a left join mal_smart_task c on a.task_id = c.task_id where a.video_id in(?) ORDER BY a.task_id desc`
    sqlStr := fmt.Sprintf(`
        SELECT c.task_id, c.task_name
        FROM mal_task_video_link a
        LEFT JOIN mal_smart_task c ON a.task_id = c.task_id
        WHERE a.video_id IN (%s)
        ORDER BY a.task_id DESC
    `, idList)
    if err := db.Raw(sqlStr).Scan(&items).Error; err != nil {
        fmt.Println(err.Error())
        return nil, err
    }
    return
}
camera-service/controllers/camera.go
@@ -2,8 +2,11 @@
import (
    "bytes"
    "database/sql"
    "encoding/json"
    "fmt"
    "image"
    "path/filepath"
    // "fmt"
    // "io/ioutil"
@@ -27,6 +30,7 @@
    "basic.com/valib/logger.git"
    //"vamicro/camera-common/service/uniquelCoordinate"
    commonModel "vamicro/api-gateway/models"
    "vamicro/config"
    "vamicro/extend/util"
    scs "vamicro/scene-service/controllers"
@@ -321,7 +325,7 @@
    if sv.ChangeRunType(ccrVo) {
        cIds := strings.Join(ccrVo.CameraIds, ",")
        cc.addDbChangeMsg(h.Bk, protomsg.TableChanged_T_Camera, cIds, protomsg.DbAction_Update, "")
         _, _ = service2.DoBusReq("/data/api-v/saas/syncCamera", config.Server.AnalyServerId, aiot.RequestMethod_Post, aiot.RequestContentType_ApplicationJson, map[string]interface{}{})
        _, _ = service2.DoBusReq("/data/api-v/saas/syncCamera", config.Server.AnalyServerId, aiot.RequestMethod_Post, aiot.RequestContentType_ApplicationJson, map[string]interface{}{})
        return &bhomeclient.Reply{Success: true, Msg: "更新成功"}
    } else {
        return &bhomeclient.Reply{Success: false, Msg: "更新失败"}
@@ -1002,9 +1006,190 @@
}
/**
 *  根据视频id获取任务
 *  获取抓拍记录
 */
func (cc CameraController) GetTaskById(h *bhomeclient.WrapperHandler, c *bhomeclient.Request) *bhomeclient.Reply {
    tasks := "hello,world"
func (cc CameraController) GetRecord(h *bhomeclient.WrapperHandler, c *bhomeclient.Request) *bhomeclient.Reply {
    //绑定json和结构体
    info := models.RecordReq{}
    if err := c.BindJSON(&info); err != nil {
        return &bhomeclient.Reply{Success: false, Msg: "参数有误"}
    }
    page := info.Page
    pageSize := info.PageSize
    idsStr := info.Ids
    TaskIds := info.TaskIds
    videoIdStr := info.VideoIds
    Warning := info.Warning
    filter := "zh_desc_class != '' "
    //根据id获取记录
    var ids []string
    if len(idsStr) > 0 {
        for _, tmp := range idsStr {
            id, _ := strconv.ParseInt(tmp, 10, 64)
            if id > 0 {
                ids = append(ids, tmp)
            }
        }
        filter += fmt.Sprintf(" and %s in [%s]", "id", strings.Join(ids, ","))
    }
    //根据视频id查询
    var videoIds []string
    if len(videoIdStr) > 0 {
        for _, tmp := range videoIdStr {
            if len(tmp) > 0 {
                videoIds = append(videoIds, tmp)
            }
        }
        //根据id查询视频id
        var vids []int
        cameras, _ := models.GetCameraIds(videoIds)
        for _, camera := range cameras {
            if len(camera.Id) > 0 {
                vids = append(vids, camera.VideoId)
            }
        }
        idStr := make([]string, len(vids))
        for i, id := range vids {
            idStr[i] = strconv.Itoa(id)
        }
        filter += fmt.Sprintf(" and %s in [%s]", "video_point_id", strings.Join(idStr, ","))
    }
    //根据任务查询
    if len(TaskIds) > 0 {
        filter += fmt.Sprintf(" and array_contains(task_id, %d)", TaskIds[0])
    }
    //是否预警
    if Warning > 0 {
        filter += fmt.Sprintf(" and is_waning == %d", Warning)
    }
    //filter += " ORDER BY detect_time DESC"
    if len(filter) > 0 {
        logger.Error("filter err:", filter)
    }
    result, err := models.GetWithPage("smartobject", int64(page), int64(pageSize), filter)
    // 通用响应
    if err != nil {
        if err == sql.ErrNoRows {
            return &bhomeclient.Reply{Success: false, Msg: "数据为空"}
        } else {
            return &bhomeclient.Reply{Success: false, Msg: "获取列表失败"}
        }
    }
    //组织数据
    //重新组织数据
    basePath := commonModel.GetBasePath()
    tmpAllDate := make(map[string]interface{})
    tmpDate := make(map[string][]interface{})
    for _, v := range result.Items {
        sources := make(map[string]interface{}, 0)
        temp := models.StaticRecord{
            Id:              v.Id,
            TaskId:          v.TaskId,
            RuleId:          v.RuleId,
            EventLevelId:    v.EventLevelId,
            VideoPointId:    v.VideoPointId,
            DetectId:        v.DetectId,
            ImagePath:       v.ImagePath,
            VideoPath:       v.VideoPath,
            DetectTime:      v.DetectTime,
            IsWarning:       v.IsWarning,
            DetectNum:       v.DetectNum,
            TaskName:        v.TaskName,
            ZhDescClass:     v.ZhDescClass,
            EventLevelName:  v.EventLevelName,
            KnowledgeId:     v.KnowledgeId,
            Suggestion:      v.Suggestion,
            RiskDescription: v.RiskDescription,
            IsDesc:          v.IsDesc,
            PicMaxUrl:       []string{v.ImagePath},
            PicSrcUrl:       []string{v.ImagePath},
            PicWH:           models.PicWidHei{PicH: 720, PicW: 1280},
            VideoUrl:        v.VideoPath,
            AnalyServerName: "智能服务节点",
            Content:         v.CameraName,
            PicDate:         v.PicDate,
            LikeDate:        v.LikeDate,
        }
        //任务名称
        items, _ := models.GetTaskByIds(v.TaskId)
        temp.TaskNames = items
        //视频内容
        items4, _ := models.GetVideoById(v.VideoPointId)
        temp.VideoName = items4.Name
        temp.CameraAddr = items4.Addr
        //检查内容
        items2, _ := models.GetCheckByIds(v.DetectId)
        temp.CheckNames = items2
        //规则内容
        items3, _ := models.GetRuleByIds(v.RuleId)
        temp.RuleNames = items3
        //事件
        items5, _ := models.GetEventByIds(v.EventLevelId)
        temp.EventLevels = items5
        //知识库
        items6, _ := models.GetKnowledgeDocumentByIds(v.KnowledgeId)
        // 知识库路径
        var KnowledgePath = "/txt/zs/"
        for i := range items6 {
            filePath := filepath.Join(basePath + KnowledgePath + items6[i].Title)
            newPath := filepath.ToSlash(filePath)
            items6[i].FileUrl = newPath
        }
        temp.KnowledgeDocuments = items6
        sources["list"] = []interface{}{
            temp,
        }
        sources["activeObject"] = temp
        tmpDate["datalist"] = append(tmpDate["datalist"], sources)
    }
    tmpAllDate["datalist"] = tmpDate["datalist"]
    var pageRes = &models.PaginatedResult2{
        Pagination: result.Pagination,
        Items:      tmpAllDate,
    }
    return &bhomeclient.Reply{Success: true, Msg: "获取成功", Data: pageRes}
}
/**
 *获取督查任务
 */
func (cc CameraController) GetTasks(h *bhomeclient.WrapperHandler, c *bhomeclient.Request) *bhomeclient.Reply {
    //绑定json和结构体
    info := models.Video{}
    if err := c.BindJSON(&info); err != nil {
        return &bhomeclient.Reply{Success: false, Msg: "参数有误"}
    }
    var tasks []models.Task
    if len(info.VideoIds) > 0 {
        tasks, _ = models.GetListTask(info.VideoIds)
    }
    return &bhomeclient.Reply{Success: true, Msg: "获取成功", Data: tasks}
}
camera-service/main.go
@@ -41,6 +41,8 @@
func main() {
    models.Init()
    models.InitVectorDb()
    defer models.CloseDBClient()
    defer models.CloseDB()
    ctx, cancel := context.WithCancel(context.Background())
@@ -126,8 +128,10 @@
    funcMap[urlPrefix+"/camera/getSyncSensorData"] = cc.GetSyncSensorData
    funcMap[urlPrefix+"/camera/saveNotExistOrUpdate"] = cc.SaveNotExistOrUpdate
    //摄像机关联督查任务
    funcMap[urlPrefix+"/camera/getTasks"] = cc.GetTaskById
    //获取抓拍预警信息
    funcMap[urlPrefix+"/camera/getRecord"] = cc.GetRecord
    //获取任务根据摄像机ids
    funcMap[urlPrefix+"/camera/getTasks"] = cc.GetTasks
    var pubTopics []string
    for key, _ := range funcMap {
camera-service/models/db.go
@@ -1,11 +1,12 @@
package models
import (
    commonModels "vamicro/camera-common/models"
    "vamicro/config"
    "basic.com/valib/logger.git"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
    "basic.com/valib/logger.git"
    "vamicro/config"
    commonModels "vamicro/camera-common/models"
)
var db *gorm.DB
@@ -20,16 +21,16 @@
    }
    db.LogMode(true)
    //db.SetLogger(&DbLogger{})
    db.AutoMigrate(&commonModels.Area{}, &commonModels.Camera{},&commonModels.CameraArea{},
    db.AutoMigrate(&commonModels.Area{}, &commonModels.Camera{}, &commonModels.CameraArea{},
        &commonModels.Sensor{}, &commonModels.CameraSensor{})
    commonModels.SetDB(db)
}
//GetDB ...
// GetDB ...
func GetDB() *gorm.DB {
    return db
}
func CloseDB() {
    db.Close()
}
}
camera-service/models/milvus.go
New file
@@ -0,0 +1,63 @@
package models
import (
    "context"
    "fmt"
    "log"
    "vamicro/api-gateway/models"
    commonModels "vamicro/camera-common/models"
    "github.com/milvus-io/milvus-sdk-go/v2/client"
)
var dbClient *MilvusClient
// MilvusClient 封装Milvus操作的结构体
type MilvusClient struct {
    client client.Client
}
// NewMilvusClient 创建Milvus客户端
func NewMilvusClient(ctx context.Context, addr string) (*MilvusClient, error) {
    milvusClient, err := client.NewGrpcClient(ctx, addr)
    if err != nil {
        return nil, fmt.Errorf("failed to connect to Milvus: %v", err)
    }
    return &MilvusClient{client: milvusClient}, nil
}
// Close 关闭连接
func (m *MilvusClient) Close() error {
    return m.client.Close()
}
// 初始化milvus
func InitVectorDb() {
    // 连接到Milvus服务器
    ctx := context.Background()
    //milvusClient, err := NewMilvusClient(ctx, "192.168.1.232:19530")
    config := models.GetConfig()
    url := config.MilvusUrl
    milvusClient, err := NewMilvusClient(ctx, url)
    if err != nil {
        log.Fatal("Failed to connect to Milvus:", err)
    }
    //defer milvusClient.client.Close()
    fmt.Println("Successfully connected to Milvus")
    //初始化
    dbClient = milvusClient
    commonModels.SetClientDB(dbClient.client)
}
// GetDB ...
func GetDBCient() *MilvusClient {
    return dbClient
}
func CloseDBClient() {
    dbClient.Close()
}
sync-service/models/camera.go
@@ -1,97 +1,104 @@
package models
import (
    "basic.com/valib/logger.git"
    "fmt"
    "strconv"
    "strings"
    "vamicro/config"
    "basic.com/valib/logger.git"
)
const (
    Default_Layer = -9999 //摄像机添加进来的默认楼层
    TYPE_LOCAL_CAMERA = 0 //本地摄像机
    TYPE_GB28181_CAMERA = 1 //国标摄像机
    Default_Layer       = -9999 //摄像机添加进来的默认楼层
    TYPE_LOCAL_CAMERA   = 0     //本地摄像机
    TYPE_GB28181_CAMERA = 1     //国标摄像机
    TYPE_RUNTYPE_VIDEO = -1 //单纯的监控,不做分析
    TYPE_RUNTYPE_POLL = 0 //轮询做任务
    TYPE_RUNTYPE_REALTIME = 1 //实时做任务
    TYPE_RUNTYPE_VIDEO    = -1 //单纯的监控,不做分析
    TYPE_RUNTYPE_POLL     = 0  //轮询做任务
    TYPE_RUNTYPE_REALTIME = 1  //实时做任务
    Camera_Status_NoRule = 0  //未配规则
    Camera_Status_Wait = 1  //等待处理
    Camera_Status_Doing = 2  //处理中
    Camera_Status_Other = -1 //其他情况
    Camera_Status_Wait   = 1  //等待处理
    Camera_Status_Doing  = 2  //处理中
    Camera_Status_Other  = -1 //其他情况
)
// 智查任务
type Task struct {
    TaskId   int64  `db:"task_id" json:"taskId"`
    TaskName string `db:"task_name" json:"taskName"`
}
type Camera struct {
    Id                    string           `gorm:"primary_key;column:id;type:varchar(100);unique;" json:"id"`
    Name                  string           `gorm:"column:name" json:"name"  bind:"required"`
    Alias                 string                `gorm:"column:alias" json:"alias"` //摄像机的别名
    SnapshotUrl         string             `gorm:"column:snapshot_url" json:"snapshot_url"`//快照地址
    Type                  int              `gorm:"column:type" json:"type" `
    Addr                  string           `gorm:"column:addr" json:"addr"`
    Longitude             float32          `gorm:"column:longitude" json:"longitude"`
    Latitude              float32          `gorm:"column:latitude" json:"latitude"`
    Floor                 int                   `gorm:"column:floor" json:"floor"`//楼层
    Rtsp                  string           `gorm:"column:rtsp" json:"rtsp"`
    Ip                    string           `gorm:"column:ip" json:"ip"`
    Port                  int              `gorm:"column:port" json:"port"`
    Username              string           `gorm:"column:username" json:"username"`
    Password              string           `gorm:"column:password" json:"password"`
    Brand                 string           `gorm:"column:brand" json:"brand"`
    Reserved              string           `gorm:"column:reserved" json:"reserved"`
    IsRunning             bool                `gorm:"column:is_running" json:"is_running"`//是否正在解码
    RunEnable             bool                `gorm:"column:run_enable" json:"run_enable"`//控制实时处理或轮询处理的开关
    RunType               int                `gorm:"column:run_type" json:"run_type"`//处理类型:0:轮询,1:实时,-1:无任务,不做分析或者分析任务被关了
    RunServerId         string                `gorm:"column:run_server_id" json:"run_server_id"`//当前正在处理的分析服务器id
    Id          string  `gorm:"primary_key;column:id;type:varchar(100);unique;" json:"id"`
    Name        string  `gorm:"column:name" json:"name"  bind:"required"`
    Alias       string  `gorm:"column:alias" json:"alias"`               //摄像机的别名
    SnapshotUrl string  `gorm:"column:snapshot_url" json:"snapshot_url"` //快照地址
    Type        int     `gorm:"column:type" json:"type" `
    Addr        string  `gorm:"column:addr" json:"addr"`
    Longitude   float32 `gorm:"column:longitude" json:"longitude"`
    Latitude    float32 `gorm:"column:latitude" json:"latitude"`
    Floor       int     `gorm:"column:floor" json:"floor"` //楼层
    Rtsp        string  `gorm:"column:rtsp" json:"rtsp"`
    Ip          string  `gorm:"column:ip" json:"ip"`
    Port        int     `gorm:"column:port" json:"port"`
    Username    string  `gorm:"column:username" json:"username"`
    Password    string  `gorm:"column:password" json:"password"`
    Brand       string  `gorm:"column:brand" json:"brand"`
    Reserved    string  `gorm:"column:reserved" json:"reserved"`
    IsRunning   bool    `gorm:"column:is_running" json:"is_running"`       //是否正在解码
    RunEnable   bool    `gorm:"column:run_enable" json:"run_enable"`       //控制实时处理或轮询处理的开关
    RunType     int     `gorm:"column:run_type" json:"run_type"`           //处理类型:0:轮询,1:实时,-1:无任务,不做分析或者分析任务被关了
    RunServerId string  `gorm:"column:run_server_id" json:"run_server_id"` //当前正在处理的分析服务器id
    ResolutionWidth      int               `gorm:"column:resolution_width;default:0" json:"resolution_width"`//分辨率宽
    ResolutionHeight     int               `gorm:"column:resolution_height;default:0" json:"resolution_height"`//分辨率高
    ResolutionWidth  int `gorm:"column:resolution_width;default:0" json:"resolution_width"`   //分辨率宽
    ResolutionHeight int `gorm:"column:resolution_height;default:0" json:"resolution_height"` //分辨率高
    VoiceEnable         bool                `gorm:"column:voiceEnable;default:0" json:"voiceEnable"`
    VoiceId             string                `gorm:"column:voiceId" json:"voiceId"`
    VoiceEnable bool   `gorm:"column:voiceEnable;default:0" json:"voiceEnable"`
    VoiceId     string `gorm:"column:voiceId" json:"voiceId"`
}
type CameraTreeNode struct {
    Id     string `json:"id"`
    Areaid string   `json:"areaid"`
    Name   string `json:"name"`
    CameraType int `json:"cameratype"`//是本地还是国标
    Rtsp   string `json:"rtsp"`
    Id         string `json:"id"`
    Areaid     string `json:"areaid"`
    Name       string `json:"name"`
    CameraType int    `json:"cameratype"` //是本地还是国标
    Rtsp       string `json:"rtsp"`
}
func (camera *Camera) GetIdNameMap() (map[string]string) {
    cameraEMap :=make(map[string]string,0)
func (camera *Camera) GetIdNameMap() map[string]string {
    cameraEMap := make(map[string]string, 0)
    allCams, err := camera.FindAll()
    if err ==nil && allCams !=nil && len(allCams) >0 {
        for _,model :=range allCams {
            cameraEMap[model.Id]=model.Name
    if err == nil && allCams != nil && len(allCams) > 0 {
        for _, model := range allCams {
            cameraEMap[model.Id] = model.Name
        }
    }
    return cameraEMap
}
//cameraType (0:查全部,1:分析摄像机,2:监控摄像机)
//cameraName
// cameraType (0:查全部,1:分析摄像机,2:监控摄像机)
// cameraName
func (camera *Camera) Find(cameraType int, cameraName string, camType int, isPlatform int) (camMenus []CameraTreeNode, err error) {
    logger.Debug("camType:",camType)
    logger.Debug("camType:", camType)
    cType := strconv.Itoa(camType)
    var sql = "select c.id,ca.areaid as areaid,case ifnull(c.alias,'') when '' then c.name else c.alias end as name,c.type as cameratype,c.rtsp from camera_area ca join cameras c on ca.cameraid=c.id where c.type="+cType+""
    var sql = "select c.id,ca.areaid as areaid,case ifnull(c.alias,'') when '' then c.name else c.alias end as name,c.type as cameratype,c.rtsp from camera_area ca join cameras c on ca.cameraid=c.id where c.type=" + cType + ""
    if cameraName != "" {
        sql += " and c.name like '%" + cameraName + "%'"
    }
    if cameraType == 1 { //查分析摄像机
        sql += " and (c.run_type="+strconv.Itoa(TYPE_RUNTYPE_POLL)+" or c.run_type="+strconv.Itoa(TYPE_RUNTYPE_REALTIME)+")"
        sql += " and (c.run_type=" + strconv.Itoa(TYPE_RUNTYPE_POLL) + " or c.run_type=" + strconv.Itoa(TYPE_RUNTYPE_REALTIME) + ")"
    } else if cameraType == 2 { //查监控摄像机
        sql += " and c.run_type="+strconv.Itoa(TYPE_RUNTYPE_VIDEO)+""
        sql += " and c.run_type=" + strconv.Itoa(TYPE_RUNTYPE_VIDEO) + ""
    } else if cameraType == 3 { //查联动摄像机
        sql += " and c.id in (select crga.camera_id from camera_rule_group_arg crga join camera_task_link ctl on crga.group_id=ctl.link_task_id)"
    }
    if camType == TYPE_LOCAL_CAMERA {//国标的始终显示整棵树,本地树本机查本机,平台查所有
        if isPlatform == 0 {//查本机
            sql += " and c.run_server_id='"+config.Server.AnalyServerId+"'"
    if camType == TYPE_LOCAL_CAMERA { //国标的始终显示整棵树,本地树本机查本机,平台查所有
        if isPlatform == 0 { //查本机
            sql += " and c.run_server_id='" + config.Server.AnalyServerId + "'"
        }
    }
@@ -103,31 +110,31 @@
    return camMenus, nil
}
func (cam *Camera) FindAllByServer(serverId string,cameraName string)(list []Camera,err error) {
func (cam *Camera) FindAllByServer(serverId string, cameraName string) (list []Camera, err error) {
    var sql = "select * from cameras where 1=1 "
    if serverId != "" {
        sql += " and (run_server_id='"+serverId+"' or (run_server_id ='' or run_server_id is NULL))"
        sql += " and (run_server_id='" + serverId + "' or (run_server_id ='' or run_server_id is NULL))"
    }
    if cameraName != "" {
        sql += " and name like '%"+cameraName+"%'"
        sql += " and name like '%" + cameraName + "%'"
    }
    if err := db.Raw(sql).Scan(&list).Error;err !=nil{
        return nil,err
    if err := db.Raw(sql).Scan(&list).Error; err != nil {
        return nil, err
    }
    return list,nil
    return list, nil
}
func (camera *Camera) GetCamerasByRunType(runType int,cameraName string) (list []Camera,err error) {
    analyServerId := config.Server.AnalyServerId//当前分析服务器的id(analyServerId,在配置文件中)
func (camera *Camera) GetCamerasByRunType(runType int, cameraName string) (list []Camera, err error) {
    analyServerId := config.Server.AnalyServerId //当前分析服务器的id(analyServerId,在配置文件中)
    sql := "select * from cameras where run_type=? and run_server_id=? "
    if cameraName !=""{
        sql = sql+" and name like ?"
    if cameraName != "" {
        sql = sql + " and name like ?"
    }
    if err := db.Raw(sql,runType,analyServerId,"%"+cameraName+"%").Scan(&list).Error;err !=nil{
        return nil,err
    if err := db.Raw(sql, runType, analyServerId, "%"+cameraName+"%").Scan(&list).Error; err != nil {
        return nil, err
    }
    return list,nil
    return list, nil
}
func (camera *Camera) Insert() (err error) {
@@ -164,10 +171,10 @@
}
func (camera *Camera) FindAllMap() map[string]Camera {
    m := make(map[string]Camera,0)
    m := make(map[string]Camera, 0)
    cams, err := camera.FindAll()
    if err ==nil && cams !=nil {
        for _,model :=range cams {
    if err == nil && cams != nil {
        for _, model := range cams {
            m[model.Id] = model
        }
    }
@@ -175,14 +182,14 @@
}
func (camera *Camera) Update() (err error) {
    logger.Debug("camera:",camera)
    longitude := fmt.Sprintf("%3.4f",camera.Longitude)
    latitude := fmt.Sprintf("%3.4f",camera.Latitude)
    logger.Debug("camera:", camera)
    longitude := fmt.Sprintf("%3.4f", camera.Longitude)
    latitude := fmt.Sprintf("%3.4f", camera.Latitude)
    voiceEnable := 0
    if camera.VoiceEnable {
        voiceEnable = 1
    }
    sql := "update cameras set name='"+camera.Name+"',alias='"+camera.Alias+"',type="+strconv.Itoa(camera.Type)+",addr='"+camera.Addr+"',longitude="+longitude+",latitude="+latitude+",rtsp='"+camera.Rtsp+"',ip='"+camera.Ip+"',port="+strconv.Itoa(camera.Port)+",username='"+camera.Username+"',password='"+camera.Password+"',brand='"+camera.Brand+"',reserved='"+camera.Reserved+"',run_server_id='"+camera.RunServerId+"',resolution_width="+strconv.Itoa(camera.ResolutionWidth)+",resolution_height="+strconv.Itoa(camera.ResolutionHeight)+",voiceEnable="+strconv.Itoa(voiceEnable)+",voiceId='"+camera.VoiceId+"' where id='"+camera.Id+"'"
    sql := "update cameras set name='" + camera.Name + "',alias='" + camera.Alias + "',type=" + strconv.Itoa(camera.Type) + ",addr='" + camera.Addr + "',longitude=" + longitude + ",latitude=" + latitude + ",rtsp='" + camera.Rtsp + "',ip='" + camera.Ip + "',port=" + strconv.Itoa(camera.Port) + ",username='" + camera.Username + "',password='" + camera.Password + "',brand='" + camera.Brand + "',reserved='" + camera.Reserved + "',run_server_id='" + camera.RunServerId + "',resolution_width=" + strconv.Itoa(camera.ResolutionWidth) + ",resolution_height=" + strconv.Itoa(camera.ResolutionHeight) + ",voiceEnable=" + strconv.Itoa(voiceEnable) + ",voiceId='" + camera.VoiceId + "' where id='" + camera.Id + "'"
    if err := db.Exec(sql).Error; err != nil {
        return err
@@ -191,87 +198,107 @@
    return nil
}
func (camera *Camera) UpdateRunEnable(cameraId string,runEnable bool) bool{
func (camera *Camera) UpdateRunEnable(cameraId string, runEnable bool) bool {
    result := db.Exec("update cameras set run_enable=? where id=?", runEnable, cameraId)
    if result.Error !=nil {
    if result.Error != nil {
        return false
    }
    return result.RowsAffected>0
    return result.RowsAffected > 0
}
func (camera *Camera) ChangeRunType(cameraId string,runType int) bool{
func (camera *Camera) ChangeRunType(cameraId string, runType int) bool {
    result := db.Exec("update cameras set run_type=? where id=?", runType, cameraId)
    if result.Error !=nil {
    if result.Error != nil {
        return false
    }
    return result.RowsAffected>0
    return result.RowsAffected > 0
}
func (camera *Camera) UpdateIsRunningState(cameraId string,isRunning bool) bool{
func (camera *Camera) UpdateIsRunningState(cameraId string, isRunning bool) bool {
    isRunningStr := "0"
    if isRunning {
        isRunningStr = "1"
    }
    result := db.Exec("update cameras set is_running="+isRunningStr+" where id='"+cameraId+"'")
    if result.Error !=nil {
    result := db.Exec("update cameras set is_running=" + isRunningStr + " where id='" + cameraId + "'")
    if result.Error != nil {
        return false
    }
    return result.RowsAffected>0
    return result.RowsAffected > 0
}
func (camera *Camera) UpdateIsRunningAll(camIds []string) bool {
    analyServerId := config.Server.AnalyServerId
    sql := "update cameras set is_running=0 where run_server_id='"+analyServerId+"'"
    sql := "update cameras set is_running=0 where run_server_id='" + analyServerId + "'"
    uIds := ""
    if camIds != nil {
        for _,id := range camIds {
            uIds = uIds + "'"+id+"',"
        for _, id := range camIds {
            uIds = uIds + "'" + id + "',"
        }
    }
    uIds = strings.Trim(uIds, ",")
    if uIds != "" {
        sql += " and id not in ("+uIds+");update cameras set is_running=1 where run_server_id='"+analyServerId+"' and id in ("+uIds+");"
        sql += " and id not in (" + uIds + ");update cameras set is_running=1 where run_server_id='" + analyServerId + "' and id in (" + uIds + ");"
    }
    logger.Debug("UpdateIsRunningAll sql:", sql)
    result := db.Exec(sql)
    if result.Error !=nil {
    if result.Error != nil {
        return false
    }
    return true
}
func (camera *Camera) UpdateSnapshot(cameraId string, snapshot string) bool{
func (camera *Camera) UpdateSnapshot(cameraId string, snapshot string) bool {
    result := db.Exec("update cameras set snapshot_url=? where id=?", snapshot, cameraId)
    if result.Error !=nil {
    if result.Error != nil {
        return false
    }
    return result.RowsAffected>0
    return result.RowsAffected > 0
}
func (camera *Camera) Delete(cid string) (int64, error) {
    var err error
    tx := GetDB().Begin()
    defer func() {
        if err !=nil && tx !=nil{
        if err != nil && tx != nil {
            tx.Rollback()
        }
    }()
    dbdel := tx.Exec("delete from cameras where id=?",cid)
    dbdel := tx.Exec("delete from cameras where id=?", cid)
    err = dbdel.Error
    if err != nil || dbdel.RowsAffected == 0 {
        return 0, err
    }
    if err = tx.Exec("delete from camera_polygon where camera_id=?",cid).Error;err !=nil{
        return 0,err
    if err = tx.Exec("delete from camera_polygon where camera_id=?", cid).Error; err != nil {
        return 0, err
    }
    if err = tx.Exec("delete from camera_area where cameraId=?",cid).Error;err !=nil {
        return 0,err
    if err = tx.Exec("delete from camera_area where cameraId=?", cid).Error; err != nil {
        return 0, err
    }
    if err = tx.Exec("delete from camera_sensor where camera_id=?",cid).Error;err !=nil {
        return 0,err
    if err = tx.Exec("delete from camera_sensor where camera_id=?", cid).Error; err != nil {
        return 0, err
    }
    tx.Commit()
    //发布数据更改消息
    return dbdel.RowsAffected, nil
}
}
// 查询列表数据
func GetListTask(ids []string) (items []Task, err error) {
    //idList := strings.Join(ids, ",")
    idList := "'" + strings.Join(ids, "','") + "'"
    // 主查询SQL
    //sqlStr := `select c.task_id, c.task_name from mal_task_video_link a left join mal_smart_task c on a.task_id = c.task_id where a.video_id in(?) ORDER BY a.task_id desc`
    sqlStr := fmt.Sprintf(`
        SELECT c.task_id, c.task_name
        FROM mal_task_video_link a
        LEFT JOIN mal_smart_task c ON a.task_id = c.task_id
        WHERE a.video_id IN (%s)
        ORDER BY a.task_id DESC
    `, idList)
    if err := db.Raw(sqlStr).Scan(&items).Error; err != nil {
        fmt.Println(err.Error())
        return nil, err
    }
    return
}
sync-service/service/cameraService.go
@@ -1,22 +1,22 @@
package service
import (
    "errors"
    "strconv"
    "vamicro/config"
    "vamicro/sync-service/models"
    "vamicro/sync-service/vo"
    "basic.com/pubsub/protomsg.git"
    "basic.com/valib/bhomedbapi.git"
    "basic.com/valib/logger.git"
    "errors"
    "github.com/satori/go.uuid"
    "strconv"
    "vamicro/sync-service/models"
    "vamicro/sync-service/vo"
    "vamicro/config"
    uuid "github.com/satori/go.uuid"
)
type CameraService struct {
}
func (sv CameraService) CameraAdd(camVo vo.CameraEditVo) (bool,models.Camera){
func (sv CameraService) CameraAdd(camVo vo.CameraEditVo) (bool, models.Camera) {
    var sysApi bhomedbapi.SysSetApi
    flag, sysconf := sysApi.GetServerInfo()
    var realMax = 16
@@ -26,16 +26,16 @@
    var err error
    tx := models.GetDB().Begin()
    defer func() {
        if err !=nil && tx !=nil {
        if err != nil && tx != nil {
            tx.Rollback()
        }
    }()
    cam := camVo.Copy2Camera()
    cam.Type = models.TYPE_LOCAL_CAMERA // 本地摄像机
    var areaE models.Area
    if camVo.AreaId !="0" {
    if camVo.AreaId != "0" {
        if _, err = areaE.SelectbyId(camVo.AreaId); err != nil {
            return false,cam
            return false, cam
        }
    }
    cam.Id = uuid.NewV4().String()
@@ -45,10 +45,10 @@
    } else {
        cam.RunType = camVo.RunType
    }
    if cam.RunType == models.TYPE_RUNTYPE_REALTIME {//实时需要判断实时数量
    if cam.RunType == models.TYPE_RUNTYPE_REALTIME { //实时需要判断实时数量
        realList, e := cam.GetCamerasByRunType(models.TYPE_RUNTYPE_REALTIME, "")
        if e !=nil || len(realList) >= realMax {
            return false,cam
        if e != nil || len(realList) >= realMax {
            return false, cam
        }
    }
@@ -56,17 +56,17 @@
    cam.Floor = models.Default_Layer
    //添加摄像机的时候不应该指定RunServerId,在哪台服务器上配了任务,RunServerId就是对应的那台服务器
    if err = tx.Table("cameras").Create(&cam).Error;err !=nil {
        return false,cam
    if err = tx.Table("cameras").Create(&cam).Error; err != nil {
        return false, cam
    }
    if err = tx.Table("camera_area").Create(&models.CameraArea{Cameraid:cam.Id,Areaid:camVo.AreaId}).Error;err !=nil{
        return false,cam
    if err = tx.Table("camera_area").Create(&models.CameraArea{Cameraid: cam.Id, Areaid: camVo.AreaId}).Error; err != nil {
        return false, cam
    }
    //处理传感器
    if len(camVo.Sensors) > 0 {
        for _,sor := range camVo.Sensors {
        for _, sor := range camVo.Sensors {
            var sorE models.Sensor
            exist := sorE.Exist(sor.Ip, sor.Port)
            sorId := ""
@@ -79,16 +79,16 @@
                }
                sorId = sor.Id
            }
            if err =tx.Exec("insert into camera_sensor(camera_id, sensor_id) select '"+cam.Id+"','"+sorId+"' where not exists (select 1 from camera_sensor where camera_id='"+cam.Id+"' and sensor_id='"+sorId+"')").Error;err !=nil {
            if err = tx.Exec("insert into camera_sensor(camera_id, sensor_id) select '" + cam.Id + "','" + sorId + "' where not exists (select 1 from camera_sensor where camera_id='" + cam.Id + "' and sensor_id='" + sorId + "')").Error; err != nil {
                return false, cam
            }
        }
    }
    tx.Commit()
    return true,cam
    return true, cam
}
func (sv CameraService) CameraUpdate(camVo vo.CameraEditVo) bool{
func (sv CameraService) CameraUpdate(camVo vo.CameraEditVo) bool {
    cam := camVo.Copy2Camera()
    var sysApi bhomedbapi.SysSetApi
    flag, sysconf := sysApi.GetServerInfo()
@@ -99,12 +99,12 @@
    var camTmp models.Camera
    rows, err := camTmp.SelectById(cam.Id)
    if err != nil || rows ==0 {
    if err != nil || rows == 0 {
        return false
    }
    cam.Floor = camTmp.Floor
    if camTmp.RunServerId == "" {//未设置过run_server_id,此种情况是国标摄像机第一次保存,会选择目标服务器的id
    if camTmp.RunServerId == "" { //未设置过run_server_id,此种情况是国标摄像机第一次保存,会选择目标服务器的id
        cam.RunServerId = config.Server.AnalyServerId //此摄像机归属到本台分析服务器
        //if cam.RunServerId == "" {
        //    return false
@@ -120,7 +120,7 @@
    if cam.RunType == models.TYPE_RUNTYPE_REALTIME {
        if camTmp.RunType != models.TYPE_RUNTYPE_REALTIME { //由非实时到实时的切换时,需要判断当前的实时数量
            realList, e := cam.GetCamerasByRunType(models.TYPE_RUNTYPE_REALTIME, "")
            if e !=nil || len(realList) >= realMax {
            if e != nil || len(realList) >= realMax {
                return false
            }
        }
@@ -133,13 +133,13 @@
    }
    if camVo.Sensors != nil && len(camVo.Sensors) > 0 {
        for _,sor :=range camVo.Sensors {
        for _, sor := range camVo.Sensors {
            var sorE models.Sensor
            exist := sorE.Exist(sor.Ip, sor.Port)
            sorId := ""
            if exist {
                sorId = sorE.Id
                if err = models.GetDB().Table(sor.TableName()).Where("id=?", sorId).Update(&sor).Error;err !=nil {
                if err = models.GetDB().Table(sor.TableName()).Where("id=?", sorId).Update(&sor).Error; err != nil {
                    return false
                }
            } else {
@@ -149,7 +149,7 @@
                }
                sorId = sor.Id
            }
            if err = models.GetDB().Exec("insert into camera_sensor(camera_id, sensor_id) select '"+cam.Id+"','"+sorId+"' where not exists (select 1 from camera_sensor where camera_id='"+camVo.Id+"' and sensor_id='"+sorId+"')").Error;err !=nil {
            if err = models.GetDB().Exec("insert into camera_sensor(camera_id, sensor_id) select '" + cam.Id + "','" + sorId + "' where not exists (select 1 from camera_sensor where camera_id='" + camVo.Id + "' and sensor_id='" + sorId + "')").Error; err != nil {
                return false
            }
        }
@@ -163,16 +163,16 @@
    return true
}
func (sv CameraService) GetCamerasByRunType(runType int,cameraName string) (cams []vo.CamSensorVo,err error){
func (sv CameraService) GetCamerasByRunType(runType int, cameraName string) (cams []vo.CamSensorVo, err error) {
    //指定当前机器为过滤条件
    var cameraE models.Camera
    list, err := cameraE.GetCamerasByRunType(runType, cameraName)
    if err !=nil {
        return nil,err
    if err != nil {
        return nil, err
    }
    var crApi bhomedbapi.CameraRuleApi
    var sensorE models.Sensor
    for _,cam :=range list {//判断每一个摄像机是否有可运行的任务和规则
    for _, cam := range list { //判断每一个摄像机是否有可运行的任务和规则
        if crApi.ExistRunningTask(cam.Id) {
            sensors, _ := sensorE.FindByCameraId(cam.Id)
            var camSor vo.CamSensorVo
@@ -186,12 +186,12 @@
            cams = append(cams, camSor)
        }
    }
    return cams,nil
    return cams, nil
}
func (sv CameraService) UpdateIsRunningState(id string,isRunning bool) bool{
func (sv CameraService) UpdateIsRunningState(id string, isRunning bool) bool {
    var cameraE models.Camera
    return cameraE.UpdateIsRunningState(id,isRunning)
    return cameraE.UpdateIsRunningState(id, isRunning)
}
@@ -200,13 +200,13 @@
    return cameraE.UpdateIsRunningAll(camIds)
}
func (sv CameraService) UpdateRunEnable(cameraId string,runEnable bool) bool{
func (sv CameraService) UpdateRunEnable(cameraId string, runEnable bool) bool {
    var cameraE models.Camera
    return cameraE.UpdateRunEnable(cameraId, runEnable)
}
//1.判断本地摄像机的实时数量
//2.判断是否安装有国标,以及国标实时摄像机的数量
// 1.判断本地摄像机的实时数量
// 2.判断是否安装有国标,以及国标实时摄像机的数量
func (sv CameraService) ChangeRunType(ccrVo vo.CameraChangeRunVo) bool {
    var sysApi bhomedbapi.SysSetApi
    flag, sysconf := sysApi.GetServerInfo()
@@ -218,20 +218,20 @@
    var err error
    tx := models.GetDB().Begin()
    defer func() {
        if err !=nil && tx !=nil{
        if err != nil && tx != nil {
            tx.Rollback()
        }
    }()
    for _,cameraId :=range ccrVo.CameraIds{
    for _, cameraId := range ccrVo.CameraIds {
        var camE models.Camera
        rows, e := camE.SelectById(cameraId)
        if e == nil && rows>0 {
        if e == nil && rows > 0 {
            if ccrVo.RunType == models.TYPE_RUNTYPE_REALTIME {
                //非实时向实时切换
                if camE.RunType != models.TYPE_RUNTYPE_REALTIME {
                    realList, e := camE.GetCamerasByRunType(models.TYPE_RUNTYPE_REALTIME, "")
                    if e !=nil || len(realList) >= realMax {
                        err = errors.New("realTime cameras limit "+strconv.Itoa(realMax))
                    if e != nil || len(realList) >= realMax {
                        err = errors.New("realTime cameras limit " + strconv.Itoa(realMax))
                        return false
                    }
                } else {
@@ -239,11 +239,11 @@
                }
            }
            if camE.RunServerId == "" {
                if err = tx.Exec("update cameras set run_type=?,run_server_id=?,is_running=0 where id=?", ccrVo.RunType,config.Server.AnalyServerId, cameraId).Error;err !=nil{
                if err = tx.Exec("update cameras set run_type=?,run_server_id=?,is_running=0 where id=?", ccrVo.RunType, config.Server.AnalyServerId, cameraId).Error; err != nil {
                    return false
                }
            } else {
                if err = tx.Exec("update cameras set run_type=?,is_running=0 where id=?", ccrVo.RunType, cameraId).Error;err !=nil{
                if err = tx.Exec("update cameras set run_type=?,is_running=0 where id=?", ccrVo.RunType, cameraId).Error; err != nil {
                    return false
                }
            }
@@ -259,53 +259,58 @@
    return cameraE.UpdateSnapshot(cameraId, snapshot)
}
func (sv CameraService) GetAllCamerasByServer(serverId string, cameraName string) (criList []vo.CameraRunInfo,err error) {
func (sv CameraService) GetAllCamerasByServer(serverId string, cameraName string) (criList []vo.CameraRunInfo, err error) {
    var cameraE models.Camera
    list, err := cameraE.FindAllByServer(serverId, cameraName)
    if err !=nil {
        return nil,err
    if err != nil {
        return nil, err
    }
    serverName := ""
    var syssetApi bhomedbapi.SysSetApi
    fb,localConf := syssetApi.GetServerInfo()
    fb, localConf := syssetApi.GetServerInfo()
    if fb {
        serverName = localConf.ServerName
    }
    nodesMap := make(map[string]protomsg.Node)
    var clusterApi bhomedbapi.ClusterApi
    flag, cInfo := clusterApi.FindCluster()
    if flag && cInfo.Nodes != nil{
        for _,node :=range cInfo.Nodes {
    if flag && cInfo.Nodes != nil {
        for _, node := range cInfo.Nodes {
            nodesMap[node.Id] = *node
        }
    }
    var crApi bhomedbapi.CameraRuleApi
    if list !=nil && len(list) >0 {
        for _,cE :=range list {
    if list != nil && len(list) > 0 {
        for _, cE := range list {
            var cri vo.CameraRunInfo
            cri.CopyFromCamera(cE)
            if cri.RunServerId != "" {
                if cri.RunServerId == config.Server.AnalyServerId {
                    cri.RunServerName = serverName
                } else {
                    if ne,ok :=nodesMap[cri.RunServerId];ok {
                    if ne, ok := nodesMap[cri.RunServerId]; ok {
                        cri.RunServerName = ne.NodeName
                    }
                }
            }
            fb, cgs := crApi.FindAllTaskById(cE.Id)
            if fb && cgs.Rules != nil {
                for _, g :=range cgs.Rules {
                    var crTask vo.CameraRunTask
            // fb, cgs := crApi.FindAllTaskById(cE.Id)
            // if fb && cgs.Rules != nil {
            //     for _, g :=range cgs.Rules {
            //         var crTask vo.CameraRunTask
                    crTask.HasRule = g.Enable
                    crTask.TaskName = g.SceneName
                    cri.Tasks = append(cri.Tasks, crTask)
                }
            } else {
                cri.Tasks = []vo.CameraRunTask{}
            }
            //         crTask.HasRule = g.Enable
            //         crTask.TaskName = g.SceneName
            //         cri.Tasks = append(cri.Tasks, crTask)
            //     }
            // } else {
            //     cri.Tasks = []vo.CameraRunTask{}
            // }
            //根据摄像机查询任务
            var Ids []string
            Ids = append(Ids, cE.Id)
            cri.AllTasks, _ = models.GetListTask(Ids)
            //处理摄像机的状态
            if cE.RunType == models.TYPE_RUNTYPE_POLL || cE.RunType == models.TYPE_RUNTYPE_REALTIME {
                if crApi.ExistRunningTask(cE.Id) {
@@ -324,27 +329,27 @@
            criList = append(criList, cri)
        }
    }
    return criList,nil
    return criList, nil
}
//通过摄像机id获取在哪台服务器上运行
// 通过摄像机id获取在哪台服务器上运行
func GetRunServerName(cameraId string) string {
    var runServerName = ""
    var camTmp models.Camera
    rows, _ := camTmp.SelectById(cameraId)
    if rows >0 {
    if rows > 0 {
        var syssetApi bhomedbapi.SysSetApi
        fb,localConf := syssetApi.GetServerInfo()
        fb, localConf := syssetApi.GetServerInfo()
        if fb {
            if camTmp.RunServerId == localConf.ServerId {//归属本台服务器
            if camTmp.RunServerId == localConf.ServerId { //归属本台服务器
                runServerName = localConf.ServerName
            } else {//属于其他服务器
            } else { //属于其他服务器
                var clusterApi bhomedbapi.ClusterApi
                flag, cInfo := clusterApi.FindCluster()
                if flag {
                    if cInfo.Nodes != nil {
                        for _,n := range cInfo.Nodes {
                        for _, n := range cInfo.Nodes {
                            if n.Id == camTmp.RunServerId {
                                runServerName = n.NodeName
                                break
@@ -357,4 +362,4 @@
    }
    return runServerName
}
}
sync-service/vo/camera.go
@@ -1,44 +1,46 @@
package vo
import (
    "basic.com/pubsub/protomsg.git"
    "vamicro/sync-service/models"
    "basic.com/pubsub/protomsg.git"
)
type CameraEditVo struct {
    models.Camera
    AreaId                string                   `json:"areaId"`
    Sensors               []models.Sensor         `json:"sensors"`
    AreaId  string          `json:"areaId"`
    Sensors []models.Sensor `json:"sensors"`
}
func (v *CameraEditVo) Copy2Camera() models.Camera{
func (v *CameraEditVo) Copy2Camera() models.Camera {
    return v.Camera
}
//摄像机运行状态结构体
// 摄像机运行状态结构体
type CameraRunVo struct {
    Id                     string                     `json:"id"`//摄像机id
    Name                 string                     `json:"name"`//摄像机名称
    IsRunning             bool                     `json:"is_running"`//是否正在运行(0:未运行任务,1:正在运行任务)
    RunType             int                     `json:"run_type"`//运行类型(0:轮询,1:实时)
    RunEnable             bool                     `json:"run_enable"`//是否启用
    Id        string `json:"id"`         //摄像机id
    Name      string `json:"name"`       //摄像机名称
    IsRunning bool   `json:"is_running"` //是否正在运行(0:未运行任务,1:正在运行任务)
    RunType   int    `json:"run_type"`   //运行类型(0:轮询,1:实时)
    RunEnable bool   `json:"run_enable"` //是否启用
}
type CameraChangeRunVo struct {
    CameraIds             []string                 `json:"camera_ids"`
    RunType             int                     `json:"run_type"`
    CameraIds []string `json:"camera_ids"`
    RunType   int      `json:"run_type"`
}
type CameraRunInfo struct {
    models.Camera
    RunServerName         string                     `json:"runServerName"`
    Tasks                 []CameraRunTask         `json:"tasks"`
    Status                 int                     `json:"status"`
    RunServerName string          `json:"runServerName"`
    Tasks         []CameraRunTask `json:"tasks"`
    AllTasks      []models.Task   `json:"allTasks"`
    Status        int             `json:"status"`
}
type CameraRunTask struct {
    TaskName             string                     `json:"taskname"` //场景名称
    HasRule               bool                       `json:"hasRule"`
    TaskName string `json:"taskname"` //场景名称
    HasRule  bool   `json:"hasRule"`
}
func (cri *CameraRunInfo) CopyFromCamera(v models.Camera) {
@@ -47,54 +49,54 @@
func (cri *CameraRunInfo) CopyFromProtoCamera(v protomsg.Camera) {
    cri.Camera = models.Camera{
        Id: v.Id,
        Name: v.Name,
        Alias: v.Alias,
        SnapshotUrl: v.SnapshotUrl,
        Type: int(v.Type),
        Addr: v.Addr,
        Longitude: v.Longitude,
        Latitude: v.Latitude,
        Floor: int(v.Floor),
        Rtsp: v.Rtsp,
        Ip: v.Ip,
        Port: int(v.Port),
        Username: v.Username,
        Password: v.Password,
        Brand: v.Brand,
        Reserved: v.Reserved,
        IsRunning: v.IsRunning,
        RunEnable: v.RunEnable,
        RunType: int(v.RunType),
        RunServerId: v.RunServerId,
        ResolutionWidth: int(v.ResolutionWidth),
        Id:               v.Id,
        Name:             v.Name,
        Alias:            v.Alias,
        SnapshotUrl:      v.SnapshotUrl,
        Type:             int(v.Type),
        Addr:             v.Addr,
        Longitude:        v.Longitude,
        Latitude:         v.Latitude,
        Floor:            int(v.Floor),
        Rtsp:             v.Rtsp,
        Ip:               v.Ip,
        Port:             int(v.Port),
        Username:         v.Username,
        Password:         v.Password,
        Brand:            v.Brand,
        Reserved:         v.Reserved,
        IsRunning:        v.IsRunning,
        RunEnable:        v.RunEnable,
        RunType:          int(v.RunType),
        RunServerId:      v.RunServerId,
        ResolutionWidth:  int(v.ResolutionWidth),
        ResolutionHeight: int(v.ResolutionHeight),
        VoiceEnable: v.VoiceEnable,
        VoiceId: v.VoiceId,
        VoiceEnable:      v.VoiceEnable,
        VoiceId:          v.VoiceId,
    }
}
type CameraRunStatistic struct {
    ChannelTotal         int                     `json:"channelTotal"`
    RealTotal             int                     `json:"realTotal"`
    RealValidCount         int                     `json:"realValidCount"`
    RealInvalidCount     int                     `json:"realInvalidCount"`
    RealRunningCount     int                     `json:"realRunningCount"`
    ChannelTotal     int `json:"channelTotal"`
    RealTotal        int `json:"realTotal"`
    RealValidCount   int `json:"realValidCount"`
    RealInvalidCount int `json:"realInvalidCount"`
    RealRunningCount int `json:"realRunningCount"`
    PollChannelCount    int                     `json:"pollChannelCount"`
    PollTotal             int                     `json:"pollTotal"`
    PollValidCount         int                     `json:"pollValidCount"`
    PollInvalidCount     int                     `json:"pollInvalidCount"`
    PollRunningCount     int                     `json:"pollRunningCount"`
    PollChannelCount int `json:"pollChannelCount"`
    PollTotal        int `json:"pollTotal"`
    PollValidCount   int `json:"pollValidCount"`
    PollInvalidCount int `json:"pollInvalidCount"`
    PollRunningCount int `json:"pollRunningCount"`
    StackChannelCount     int                     `json:"stackChannelCount"` //数据栈占用通道数量
    StackTotal             int                     `json:"stackTotal"`
    StackValidCount     int                     `json:"stackValidCount"`
    StackInvalidCount   int                     `json:"stackInvalidCount"`
    StackRunningCount     int                     `json:"stackRunningCount"`
    StackChannelCount int `json:"stackChannelCount"` //数据栈占用通道数量
    StackTotal        int `json:"stackTotal"`
    StackValidCount   int `json:"stackValidCount"`
    StackInvalidCount int `json:"stackInvalidCount"`
    StackRunningCount int `json:"stackRunningCount"`
}
type CamSensorVo struct {
    models.Camera
    Sensors             []models.Sensor         `json:"sensors"`
}
    Sensors []models.Sensor `json:"sensors"`
}