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 // 年龄 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 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\n", task.ID, task.Name, m) if m.DisappearTime == 0 { logger.Warnf("invalid parameters. task id:%s, name:%s\n", task.ID, task.Name) return errors.New("invalid parameters") } return nil } type PersonInfo struct { DocumentNumber string `json:"document_number"` CommunityId string `json:"community_id"` OrgId string `json:"org_id"` PersonName string `json:"person_name"` IdCard string `json:"id_card"` LastAppearanceTime int64 `json:"last_appearance_time"` LastDirection string `json:"last_direction"` LastLocation string `json:"last_location"` } func (m *DisappearModel) Run() error { results := make([]*db.ModelTaskResults, 0) var ageFilter, labelFilter, keyFilter, lastFilter []PersonInfo 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.STATUS = 'resident' 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.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("match age result %d", 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 l.label_id IN ? `, 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("match label result %d", 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.key_status = 1 AND p.id_card != "" AND k.person_type IN ? `, keyTypes).Scan(&keyFilter).Error if err != nil { logger.Warnf(err.Error()) } logger.Debugf("match key person result %d", len(keyFilter)) } if len(lastFilter) > 0 { lastFilter = intersectPersonInfo(lastFilter, keyFilter) } else { lastFilter = keyFilter } logger.Debugf("last result %d", len(lastFilter)) for _, p := range lastFilter { if len(m.AreaIds) > 0 { _, o1 := m.AreaIds[p.CommunityId] _, o2 := m.AreaIds[p.OrgId] if !o1 && !o2 { 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.Task.Name, 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, } results = append(results, result) } } return service.SaveTaskResults(results) } 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) }