package models import ( "errors" "fmt" "strconv" "strings" "time" "model-engine/db" "model-engine/pkg/logger" "model-engine/service" ) type DisappearModel struct { AreaIds map[string]struct{} Building string // 楼栋 Floor string // 楼层 AlarmType db.AlarmType // 预警方式 KeyPersonType string // 人员类型 PersonLabel string DisappearTime int // 消失时间, 单位小时 AlarmInterval int // 报警时间间隔, 单位天 LastDirection string // 最后一次抓拍 MaxAge int // 年龄 MinAge int // 年龄 StartTime int64 // 起始时间 Task *db.ModelTask } func (m *DisappearModel) Init(task *db.ModelTask) error { m.AreaIds = make(map[string]struct{}) for _, a := range task.DomainUnitIds { m.AreaIds[a] = struct{}{} } m.Task = task m.Building = task.Building m.Floor = task.Floor m.AlarmType = task.AlarmType m.KeyPersonType = task.PersonType m.PersonLabel = task.PersonLabel m.StartTime = task.BeginTime.Unix() for _, v := range task.Rules { if v.Alias == "disappearTime" { if val, ok := v.Value.(float64); ok { m.DisappearTime = int(val) } } if v.Alias == "alarmInterval" { if val, ok := v.Value.(float64); ok { m.AlarmInterval = int(val) } } if v.Alias == "age" { if val, ok := v.Value.(string); ok { ages := strings.Split(val, ",") m.MinAge, _ = strconv.Atoi(ages[0]) m.MaxAge, _ = strconv.Atoi(ages[1]) } } if v.Alias == "lastDirection" { if val, ok := v.Value.(string); ok { m.LastDirection = val } } } logger.Debugf("DisappearModel init finish ...task id:%s, name:%s, rule:%+v", task.ID, task.Name, m) if m.DisappearTime == 0 { logger.Warnf("invalid parameters. task id:%s, name:%s", task.ID, task.Name) return errors.New("invalid parameters") } return nil } func (m *DisappearModel) Run() error { results := make([]*db.ModelTaskResults, 0) var ageFilter, labelFilter, keyFilter, lastFilter []PersonInfo if m.MinAge == 0 && m.PersonLabel == "" && m.KeyPersonType == "" { err := db.GetDB().Raw(` SELECT s.document_number, s.community_id, s.org_id, p.person_name, p.id_card, s.last_appearance_time, s.last_direction, s.last_location FROM snapshot_count_summary AS s JOIN person AS p ON p.id = s.document_number WHERE s.last_appearance_time > ? `, m.StartTime).Scan(&lastFilter).Error if err != nil { logger.Warnf(err.Error()) } if len(lastFilter) == 0 { return fmt.Errorf("no results found that match the condition %+v", m.Task.Rules) } logger.Debugf("task %s match default result %d", m.Task.Name, len(lastFilter)) } if m.MinAge > 0 { err := db.GetDB().Raw(` SELECT s.document_number, s.community_id, s.org_id, p.person_name, p.id_card, s.last_appearance_time, s.last_direction, s.last_location FROM snapshot_count_summary AS s JOIN person AS p ON p.id = s.document_number WHERE s.last_appearance_time > ? AND p.id_card != "" AND TIMESTAMPDIFF( YEAR, STR_TO_DATE( CASE WHEN LENGTH( id_card ) = 18 THEN SUBSTRING( id_card, 7, 8 ) ELSE NULL END, '%Y%m%d' ), CURDATE( ) ) >= ? AND TIMESTAMPDIFF( YEAR, STR_TO_DATE( CASE WHEN LENGTH( id_card ) = 18 THEN SUBSTRING( id_card, 7, 8 ) ELSE NULL END, '%Y%m%d' ), CURDATE( ) ) <= ? `, m.StartTime, m.MinAge, m.MaxAge).Scan(&ageFilter).Error if err != nil { logger.Warnf(err.Error()) } if len(ageFilter) == 0 { return fmt.Errorf("no results found that match the age condition %s - %s", m.MinAge, m.MaxAge) } logger.Debugf("task %s match age result %d", m.Task.Name, len(ageFilter)) } if m.PersonLabel != "" { labels := strings.Split(m.PersonLabel, ",") err := db.GetDB().Raw(` SELECT s.document_number, s.community_id, s.org_id, p.person_name, p.id_card, s.last_appearance_time, s.last_direction, s.last_location FROM snapshot_count_summary AS s JOIN person AS p ON p.id = s.document_number JOIN person_label AS l ON p.id = l.person_id WHERE s.last_appearance_time > ? AND l.label_id IN ? `, m.StartTime, labels).Scan(&labelFilter).Error if err != nil { logger.Warnf(err.Error()) } if len(labelFilter) == 0 { return fmt.Errorf("no results found that match the label condition %s", m.PersonLabel) } logger.Debugf("task %s match label result %d", m.Task.Name, len(labelFilter)) } // 合并一下条件 if m.MinAge > 0 && m.PersonLabel != "" { lastFilter = intersectPersonInfo(ageFilter, labelFilter) } else if m.MinAge > 0 { lastFilter = ageFilter } else if m.PersonLabel != "" { lastFilter = labelFilter } if m.KeyPersonType != "" { keyTypes := strings.Split(m.KeyPersonType, ",") err := db.GetDB().Raw(` SELECT s.document_number, s.community_id, s.org_id, p.person_name, p.id_card, s.last_appearance_time, s.last_direction, s.last_location FROM snapshot_count_summary AS s JOIN person AS p ON p.id = s.document_number JOIN key_person AS k ON k.id_card = p.id_card WHERE s.last_appearance_time > ? AND s.key_status = 1 AND p.id_card != "" AND k.person_type IN ? `, m.StartTime, keyTypes).Scan(&keyFilter).Error if err != nil { logger.Warnf(err.Error()) } logger.Debugf("task %s match key person result %d", m.Task.Name, len(keyFilter)) if len(lastFilter) > 0 { lastFilter = intersectPersonInfo(lastFilter, keyFilter) } else { lastFilter = keyFilter } } logger.Debugf("task %s last result %d", m.Task.Name, len(lastFilter)) var personIds = make(map[string]struct{}, 0) for _, p := range lastFilter { if len(m.AreaIds) > 0 { _, o1 := m.AreaIds[p.CommunityId] if !o1 { continue } } if m.LastDirection != "" { if p.LastDirection != m.LastDirection { continue } } if isOlderThanGivenHours(p.LastAppearanceTime, m.DisappearTime) { result := &db.ModelTaskResults{ Title: m.Task.Name, Event: m.eventFormat(p.LastAppearanceTime, p.LastDirection), ModelID: m.Task.ModelID, ModelTaskID: m.Task.ID, CommunityId: p.CommunityId, OrgID: p.OrgId, ObjectIds: p.DocumentNumber, Location: p.LastLocation, PicDate: time.Unix(p.LastAppearanceTime, 0).Format("2006-01-02 15:04:05"), FirstPersonID: p.DocumentNumber, } // 同一个人报警一次 if _, ok := personIds[p.DocumentNumber]; ok { continue } else { personIds[p.DocumentNumber] = struct{}{} } results = append(results, result) } } logger.Debugf("task %s last filter result %d", m.Task.Name, len(results)) return service.SaveTaskResults(results) } func (m *DisappearModel) KeepAlive() error { db.GetDB().Model(m.Task).Where("id = ?", m.Task.ID).Update("last_run_time", time.Now()) return nil } func (m *DisappearModel) Shutdown() error { // 清理资源 fmt.Println("Shutting down Disappear Model") return nil } func intersectPersonInfo(arr1, arr2 []PersonInfo) []PersonInfo { // 使用 map 来存储 arr1 中的 DocumentNumber resultMap := make(map[string]PersonInfo) var result []PersonInfo // 将第一个数组的 DocumentNumber 添加到 map 中 for _, person := range arr1 { resultMap[person.DocumentNumber] = person } // 遍历第二个数组,检查 DocumentNumber 是否在 map 中 for _, person := range arr2 { if _, exists := resultMap[person.DocumentNumber]; exists { // 如果存在交集,将该元素加入结果数组 result = append(result, person) // 可选:如果希望每个交集元素只出现一次,可以删除 map 中的元素 delete(resultMap, person.DocumentNumber) } } return result } func isOlderThanGivenHours(timestamp int64, hours int) bool { // 将时间戳转换为时间对象 timestampTime := time.Unix(timestamp, 0) // 获取当前时间 currentTime := time.Now() // 计算当前时间减去指定小时数的时间 timeThreshold := currentTime.Add(-time.Duration(hours) * time.Hour) // 判断时间戳是否早于该时间 return timestampTime.Before(timeThreshold) } func (m *DisappearModel) eventFormat(lastAppearTime int64, lastDirection string) string { lastTime := time.Unix(lastAppearTime, 0) currentTime := time.Now() duration := currentTime.Sub(lastTime) // 输出时间差的小时数 var durationStr = "小时" hours := duration.Hours() if hours > 24 { hours = hours / 24 durationStr = "天" } var direction string var reverse = "未出现" if lastDirection == "in" { direction = "进" reverse = "未出" } else if lastDirection == "out" { direction = "出" reverse = "未归" } return fmt.Sprintf("%s%s,持续%.1f%s%s", lastTime.Format("2006-01-02 15:04:05"), direction, hours, durationStr, reverse) }