package models import ( "errors" "fmt" "strconv" "strings" "time" "model-engine/db" "model-engine/pkg/logger" "model-engine/service" ) type RegularityModel struct { OrgIds []interface{} `json:"-"` AreaIds []interface{} `json:"-"` IdentityType []string // 人员身份类型 陌生人, 访客, 住户 KeyPersonType string // 重点人员类型 PersonLabel string // 人员身份标签 Task *db.ModelTask AlarmType db.AlarmType // 预警方式 Appearances int // 出现次数 Duration int // 时间范围, 单位天 LastDirection string // 最后一次出行的方向 StartHour int // 开始计算的时间 配置为小时 23 - 02 表示第一天23点 - 第二天的2点 EndHour int // 结束时间 } func (m *RegularityModel) Init(task *db.ModelTask) error { if len(task.DomainUnitIds) == 0 { return errors.New("empty domain set") } orgIds, areaIds, err := service.GetOrgIdsAndAreaIdsByDomainUnitIds(task.DomainUnitIds) if err != nil { return err } m.Task = task m.OrgIds = orgIds m.AreaIds = areaIds m.AlarmType = task.AlarmType m.KeyPersonType = task.PersonType m.PersonLabel = task.PersonLabel m.LastDirection = "out" if task.IdentityType != "" { for _, t := range strings.Split(task.IdentityType, ",") { if t == "all" { m.IdentityType = []string{"stranger", "visitor", "resident"} break } else { m.IdentityType = append(m.IdentityType, t) } } } else { m.IdentityType = []string{"stranger", "visitor", "resident"} } for _, v := range task.Rules { if v.Alias == "timeRange" { if val, ok := v.Value.(string); ok { ages := strings.Split(val, ",") m.StartHour, _ = strconv.Atoi(ages[0]) m.EndHour, _ = strconv.Atoi(ages[1]) } } if v.Alias == "appearances" { if val, ok := v.Value.(float64); ok { m.Appearances = int(val) } } if v.Alias == "duration" { if val, ok := v.Value.(float64); ok { m.Duration = int(val) } } if v.Alias == "direction" { if val, ok := v.Value.(string); ok { m.LastDirection = val } } } // 默认计算30天的数据 if m.Duration == 0 { m.Duration = 30 } if m.StartHour == 0 || m.EndHour == 0 { return fmt.Errorf("task id:%s, %s timeRange Time range setting error. %+v", task.ID, task.Name, task.Rules) } logger.Debugf("AccessRegularity init finish ...task id:%s, name:%s, rule:%+v", task.ID, task.Name, m) return nil } func (m *RegularityModel) Run() error { results := make([]*db.ModelTaskResults, 0) baseFilter := make([]PersonInfo, 0) // 查找指定时间范围内出行过的档案编号 now := time.Now() startDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -m.Duration) endDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) err := db.GetDB().Raw(` SELECT document_number, frequent_address, community_id, org_id FROM snapshot_count_summary WHERE last_appearance_time > ? AND (community_id IN ? OR org_id IN ?) AND status IN ? `, startDate.Unix(), m.AreaIds, m.OrgIds, m.IdentityType).Scan(&baseFilter).Error if err != nil { logger.Warnf(err.Error()) } logger.Debugf("task %s base filter result %d", m.Task.Name, len(baseFilter)) esCli := db.GetEsClient() // 调用es分析此人的出行规律是否符合条件, 返回符合条件的次数和最后一次符合条件的时间 sTime := startDate.Format(time.DateTime) eTime := endDate.Format(time.DateTime) for _, p := range baseFilter { captures, err := service.QueryEsRecord(esCli, sTime, eTime, nil, []interface{}{p.CommunityId}, []string{p.DocumentNumber}) //logger.Debugf("task %s person %s captures %d", m.Task.Name, p.DocumentNumber, len(captures)) if len(captures) == 0 || err != nil { continue } //logger.Debugf("task %s person %s captures %+v", m.Task.Name, p.DocumentNumber, captures[0]) // 根据抓拍时间和出入方向,计算符合规则内的出入次数 hitCount, pd := countValidDays(captures, m.StartHour, m.EndHour, m.LastDirection) if hitCount > m.Appearances { // 写数据库 result := &db.ModelTaskResults{ Title: m.Task.Name, Event: fmt.Sprintf("%s - %s 时间段内, %s %d次", sTime, eTime, m.Task.Name, hitCount), ModelID: m.Task.ModelID, ModelTaskID: m.Task.ID, CommunityId: p.CommunityId, OrgID: p.OrgId, ObjectIds: p.DocumentNumber, Location: p.FrequentAddress, PicDate: pd, FirstPersonID: p.DocumentNumber, } results = append(results, result) } } logger.Debugf("task %s last filter result %d", m.Task.Name, len(results)) return service.SaveTaskResults(results) } func (m *RegularityModel) KeepAlive() error { db.GetDB().Model(m.Task).Where("id = ?", m.Task.ID).Update("last_run_time", time.Now()) return nil } func (m *RegularityModel) Shutdown() error { // 清理资源 fmt.Println("Shutting down accessRegularityS Model") return nil } func (m *RegularityModel) eventFormat() string { return "" } func countValidDays(records []*service.ESRecordInfo, startHour, endHour int, direction string) (int, string) { layout := "2006-01-02 15:04:05" // 时间格式 lastDirectionMap := make(map[string]string) // 记录最后一条 Direction lastTimeMap := make(map[string]time.Time) // 记录最后一条时间 lastPicDate := "" // 判断是否跨天 var isCrossDay bool if endHour < startHour { isCrossDay = true } for _, record := range records { // 解析时间 t, err := time.ParseInLocation(layout, record.PicDate, time.Local) if err != nil { fmt.Println("解析时间失败:", err) continue } // 获取小时 hour := t.Hour() var key string if !isCrossDay { // 判断时间范围,并归属到某一天 if hour >= startHour && hour <= endHour { key = t.Format("2006-01-02") } else { continue // 不在统计范围内 } } else { // 判断时间范围,并归属到某一天 if hour >= startHour { // 21:00-23:59 归属当天 key = t.Format("2006-01-02") } else if hour <= endHour { // 00:00-02:59 归属前一天 key = t.AddDate(0, 0, -1).Format("2006-01-02") } else { continue // 不在统计范围内 } } // 记录该时间段内的最后一条数据 if lastTime, exists := lastTimeMap[key]; !exists || t.After(lastTime) { lastTimeMap[key] = t lastDirectionMap[key] = record.CameraLocation.Direction lastPicDate = record.PicDate } } // 统计符合条件 count := 0 for _, dir := range lastDirectionMap { if dir == direction || direction == "" { count++ } } return count, lastPicDate }