From a25ee90c656b39e652f24e3378ad2bf5698b162b Mon Sep 17 00:00:00 2001 From: zhangzengfei <zhangzengfei@smartai.com> Date: 星期五, 20 十二月 2024 01:59:06 +0800 Subject: [PATCH] 添加疑似托管模型 --- pkg/mysqlx/mysqlx.go | 16 + service/task_results.go | 2 db/summay.go | 29 +++ models/gather_model.go | 5 db/model.go | 15 + db/persons.go | 54 ++++++ cron/cron.go | 2 db/task_results.go | 36 ++- models/disappear.go | 270 ++++++++++++++++++++++++++++++ db/db.go | 63 ++++++ 10 files changed, 462 insertions(+), 30 deletions(-) diff --git a/cron/cron.go b/cron/cron.go index 551c6f2..f85d719 100644 --- a/cron/cron.go +++ b/cron/cron.go @@ -46,7 +46,7 @@ } func Run() (err error) { - _, err = s.Every(1).Minute().StartImmediately().Do(Dispatch) + _, err = s.Every(10).Minute().StartImmediately().Do(Dispatch) if err != nil { return err } diff --git a/db/db.go b/db/db.go index de59e6a..b23ab8c 100644 --- a/db/db.go +++ b/db/db.go @@ -6,13 +6,12 @@ "model-engine/config" "model-engine/db/es" - "model-engine/pkg/logger" "model-engine/pkg/mysqlx" ) // Init 鍒濆鍖杕ysql鍜宔s func Init() error { - if err := mysqlx.Init(config.MysqlConf, logger.GetLogger()); err != nil { + if err := mysqlx.Init(config.MysqlConf, nil); err != nil { return err } if err := es.InitClient([]string{"http://" + config.EsInfo.Ip + ":" + config.EsInfo.Port}); err != nil { @@ -65,6 +64,15 @@ Name: "鐤戜技鑱氶泦", Description: "閫氱敤鑱氶泦妯″瀷", Version: "v1.0.1", + Enabled: false, + }, + { + BaseModel: BaseModel{ + ID: ModelIdDisappear, + }, + Name: "鐤戜技鑴辩", + Description: "閫氱敤鑴辩妯″瀷", + Version: "v1.0.0", Enabled: false, }, } @@ -143,6 +151,57 @@ Value: "1", ValType: "int", Operator: "==", + Sort: 3, + }, + }, + { + Id: "7a1f0a3a-c207-4d94-bc28-cc9e017b3628", + ModelId: ModelIdDisappear, + Scope: "", + RuleArg: RuleArg{ + Alias: "disappearTime", + Name: "鎸佺画鏃堕棿", + Type: "input", + Must: true, + Unit: "灏忔椂", + Range: "1,2400", + Value: "24", + ValType: "int", + Operator: ">=", + Sort: 0, + }, + }, + { + Id: "f1b99f28-1be0-4f78-b7c1-b01b1656b7fa", + ModelId: ModelIdDisappear, + Scope: "", + RuleArg: RuleArg{ + Alias: "age", + Name: "骞撮緞娈�", + Type: "input", + Must: true, + Unit: "宀�", + Range: "1,100", + Value: "60, 90", + ValType: "int", + Operator: "==", + Sort: 1, + }, + }, + { + Id: "47366fa6-2f61-4fe0-957a-b1e0606bb1f0", + ModelId: ModelIdDisappear, + Scope: "", + RuleArg: RuleArg{ + Alias: "age", + Name: "杩涘嚭鏂瑰悜", + Type: "input", + Must: false, + Unit: "", + Range: "", + Value: "", + ValType: "string", + Operator: "==", Sort: 2, }, }, diff --git a/db/model.go b/db/model.go index 97dcef1..bc3ac05 100644 --- a/db/model.go +++ b/db/model.go @@ -2,16 +2,18 @@ import ( "fmt" + "gorm.io/gorm" + "model-engine/pkg/mysqlx" ) type Model struct { BaseModel - Name string `json:"name" gorm:"type:varchar(255)"` //妯″瀷鍚嶇О - Description string `json:"description,omitempty" gorm:"type:varchar(1000)"` //妯″瀷鎻忚堪 - Version string `json:"version" gorm:"type:varchar(255)"` //鐗堟湰鍙� - Enabled bool `json:"enabled"` //鏄惁寮�鍚� + Name string `json:"name" gorm:"type:varchar(255)"` // 妯″瀷鍚嶇О + Description string `json:"description,omitempty" gorm:"type:varchar(1000)"` // 妯″瀷鎻忚堪 + Version string `json:"version" gorm:"type:varchar(255)"` // 鐗堟湰鍙� + Enabled bool `json:"enabled"` // 鏄惁寮�鍚� } func (m *Model) TableName() string { @@ -181,6 +183,7 @@ } const ( - ModelIdDrug = "drug" //娑夋瘨 - ModelIdGather = "gather" //鑱氶泦 + ModelIdDrug = "drug" // 娑夋瘨 + ModelIdGather = "gather" // 鑱氶泦 + ModelIdDisappear = "disappear" // 澶辫釜 ) diff --git a/db/persons.go b/db/persons.go new file mode 100644 index 0000000..4a01e3d --- /dev/null +++ b/db/persons.go @@ -0,0 +1,54 @@ +package db + +import ( + "time" + + "gorm.io/gorm" +) + +type BaseEntity struct { + ID string `gorm:"primary_key;column:id;type:varchar(255);" json:"id"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +type DbPersons struct { + BaseEntity + TableId string `gorm:"column:table_id"` + FaceFeature string `gorm:"column:face_feature"` + PersonPicUrl string `gorm:"column:person_pic_url"` + PersonName string `gorm:"column:person_name"` + Age string `gorm:"column:age"` + Sex string `gorm:"column:sex"` + IdCard string `gorm:"column:id_card"` + PhoneNum string `gorm:"column:phone_num"` + MonitorLevel string `gorm:"column:monitor_level"` + PicDesc string `gorm:"column:pic_desc"` + Reserved string `gorm:"column:reserved"` + FromServerId string `gorm:"column:from_server_id"` + LastAppearanceTime int64 `gorm:"column:last_appearance_time"` + SnapshotCount int `gorm:"column:snapshot_count"` + DaysAppeared int `gorm:"column:days_appeared"` + Location string `gorm:"column:location"` + LastLocation string `gorm:"column:last_location"` + FaceAngleYaw int32 `gorm:"column:face_angle_yaw"` + FaceAngleRoll int32 `gorm:"column:face_angle_roll"` + FaceAnglePitch int32 `gorm:"column:face_angle_pitch"` + CommunityID string `gorm:"column:community_id"` // 甯镐綇灏忓尯 domain unit ID + CommunityName string `gorm:"column:community_name"` + OrgID string `gorm:"column:org_id"` // 甯镐綇娲惧嚭鎵� domain unit ID + OrgName string `gorm:"column:community"` + FrontFaceScore float64 `gorm:"column:front_face_score"` + ImageQuality float64 `gorm:"column:image_quality"` + SimilarPersonId string `gorm:"column:similar_person_id"` + PersonalStatus string `gorm:"column:personal_status"` + NonHuman bool `gorm:"column:non_human"` // 闈炴椿浣擄紙1闈炲浣� 0娲讳綋锛� + IdPicUrl string `gorm:"column:id_pic_url"` + IsDelete int `gorm:"column:is_delete"` // 0 鏈垹闄� 1宸插垹闄� + FaceUpdateTime int64 `gorm:"column:face_update_time"` +} + +func (dp *DbPersons) TableName() string { + return "person" +} diff --git a/db/summay.go b/db/summay.go new file mode 100644 index 0000000..49a3d7f --- /dev/null +++ b/db/summay.go @@ -0,0 +1,29 @@ +package db + +type SnapshotCountSummary struct { + OrgId string `gorm:"column:org_id"` // 娲惧嚭鎵�id + CommunityID string `gorm:"column:community_id"` // 灏忓尯id + DocumentNumber string `gorm:"column:document_number"` // 妗f缂栧彿 + DaysAppeared int `gorm:"column:days_appeared"` // 鍑虹幇澶╂暟 + Count int `gorm:"column:count"` // 鎶撴媿娆℃暟 + Status string `gorm:"column:status"` // 鏍囩 + LastAppearanceTime int64 `gorm:"column:last_appearance_time"` // 鏈�鍚庡嚭鐜版椂闂� + LastAppearanceStatusTime int64 `gorm:"column:last_appearance_status_time"` // 鏈�鍚庡嚭鐜版椂闂� + LastLocation string `gorm:"column:last_location"` // 鏈�鍚庡嚭鐜板湴鐐� + Building string `gorm:"column:building"` // 妤兼爧鍙� + Floor string `gorm:"column:floor"` // 妤煎眰 + FrequentAddress string `gorm:"column:frequent_address"` // 甯镐綇鍦板潃 + IDStatus int `gorm:"type:tinyint;not null;default:2"` // 1宸插疄鍚� 2鏈疄鍚� + KeyStatus int `gorm:"type:tinyint;not null;default:2"` // 1閲嶇偣浜哄憳 2 闈為噸鐐逛汉鍛� + StatusScore float64 `gorm:"column:status_score"` // 灞呬綇灞炴�ц瘎鍒� + StatusInitTime int64 `gorm:"column:status_init_time"` + LastDirection string `gorm:"column:last_direction;type:varchar(255);"` // 鏈�鍚庡嚭鐜版柟鍚� +} + +func (sc *SnapshotCountSummary) TableName() string { + return "snapshot_count_summary" +} + +func (sc *SnapshotCountSummary) FindByPersonId(commId, docId string) error { + return GetDB().Table(sc.TableName()).Where("community_id = ? and document_number = ?", commId, docId).First(sc).Error +} diff --git a/db/task_results.go b/db/task_results.go index ae4367d..8409cf2 100644 --- a/db/task_results.go +++ b/db/task_results.go @@ -2,24 +2,25 @@ import ( "fmt" - "gorm.io/gorm" "time" + + "gorm.io/gorm" ) type ModelTaskResults struct { BaseModel - Title string `json:"name" gorm:"type:varchar(255)"` //棰勮鍚嶇О,鏆傛椂鐢ㄤ换鍔″悕绉� - Event string `json:"event" gorm:"type:varchar(255)"` //棰勮浜嬩欢 - ModelID string `json:"modelID" gorm:"type:varchar(255)"` //妯″瀷ID - ModelTaskID string `json:"modelTaskID" gorm:"type:varchar(255)"` //妯″瀷浠诲姟ID - OrgID string `json:"orgID" gorm:"index;column:org_id;type:varchar(299);not null;"` //娲惧嚭鎵� domain unit ID - CommunityId string `json:"communityID" gorm:"index;column:community_id;type:varchar(299);not null;"` //灏忓尯ID - PicDate string `json:"picDate" gorm:"uniqueIndex:pic_date_first_person_id;type:varchar(255);"` //鎶撴媿鏃堕棿 - FirstPersonID string `json:"-" gorm:"uniqueIndex:pic_date_first_person_id;type:varchar(255);"` //绗竴涓汉鐨処D - ObjectIds string `json:"objectIds" gorm:"type:text"` //浜嬩欢瀵硅薄锛屽彲浠ユ槸浜猴紝澶氫釜鐢ㄩ�楀彿鍒嗛殧 - Location string `json:"location" gorm:"type:varchar(255)"` //鍙戠敓鍦扮偣 - Building string `json:"building" gorm:"type:varchar(255);"` //妤兼爧 - Floor string `json:"floor" gorm:"type:varchar(255);"` //妤煎眰 + Title string `json:"name" gorm:"type:varchar(255)"` // 棰勮鍚嶇О,鏆傛椂鐢ㄤ换鍔″悕绉� + Event string `json:"event" gorm:"type:varchar(255)"` // 棰勮浜嬩欢 + ModelID string `json:"modelID" gorm:"type:varchar(255)"` // 妯″瀷ID + ModelTaskID string `json:"modelTaskID" gorm:"type:varchar(255)"` // 妯″瀷浠诲姟ID + OrgID string `json:"orgID" gorm:"index;column:org_id;type:varchar(299);not null;"` // 娲惧嚭鎵� domain unit ID + CommunityId string `json:"communityID" gorm:"index;column:community_id;type:varchar(299);not null;"` // 灏忓尯ID + PicDate string `json:"picDate" gorm:"uniqueIndex:pic_date_first_person_id;type:varchar(255);"` // 鎶撴媿鏃堕棿 + FirstPersonID string `json:"-" gorm:"uniqueIndex:pic_date_first_person_id;type:varchar(255);"` // 绗竴涓汉鐨処D + ObjectIds string `json:"objectIds" gorm:"type:text"` // 浜嬩欢瀵硅薄锛屽彲浠ユ槸浜猴紝澶氫釜鐢ㄩ�楀彿鍒嗛殧 + Location string `json:"location" gorm:"type:varchar(255)"` // 鍙戠敓鍦扮偣 + Building string `json:"building" gorm:"type:varchar(255);"` // 妤兼爧 + Floor string `json:"floor" gorm:"type:varchar(255);"` // 妤煎眰 } @@ -69,6 +70,11 @@ return slf } +func (slf *ModelTaskResultsSearch) SetTaskId(id string) *ModelTaskResultsSearch { + slf.ModelTaskID = id + return slf +} + func (slf *ModelTaskResultsSearch) SetFirstPersonId(firstId string) *ModelTaskResultsSearch { slf.FirstPersonID = firstId return slf @@ -97,6 +103,10 @@ db = db.Where("first_person_id = ?", slf.FirstPersonID) } + if slf.ModelTaskID != "" { + db = db.Where("model_task_id = ?", slf.ModelTaskID) + } + if slf.Keyword != "" { kw := "%" + slf.Keyword + "%" db = db.Where("name like ?", kw) diff --git a/models/disappear.go b/models/disappear.go new file mode 100644 index 0000000..e65907f --- /dev/null +++ b/models/disappear.go @@ -0,0 +1,270 @@ +package models + +import ( + "errors" + "fmt" + "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 // 鏈�鍚庝竴娆℃姄鎷� + PersonAge 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.(float64); ok { + m.PersonAge = int(val) + } + } + + 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.PersonAge > 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( ) > ? + `, m.PersonAge).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 %d", m.PersonAge) + } + } + + 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) + } + } + + // 鍚堝苟涓�涓嬫潯浠� + if m.PersonAge > 0 && m.PersonLabel != "" { + lastFilter = intersectPersonInfo(ageFilter, labelFilter) + } else if m.PersonAge > 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()) + } + } + + if len(lastFilter) > 0 { + lastFilter = intersectPersonInfo(lastFilter, keyFilter) + } else { + lastFilter = keyFilter + } + + 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) +} diff --git a/models/gather_model.go b/models/gather_model.go index 3724c0d..64aa815 100644 --- a/models/gather_model.go +++ b/models/gather_model.go @@ -15,6 +15,7 @@ "model-engine/config" "model-engine/db" + "model-engine/pkg/logger" "model-engine/pkg/set" "model-engine/service" ) @@ -81,10 +82,10 @@ } } - fmt.Printf("GatherModel init finish ...task id:%s, name:%s, rule:%+v\n", task.ID, task.Name, m) + logger.Debugf("GatherModel init finish ...task id:%s, name:%s, rule:%+v\n", task.ID, task.Name, m) if m.GatherPersons == 0 || m.AppearInterval == 0 || m.DaysWindow == 0 || m.Threshold == 0 { - fmt.Printf("invalid parameters. task id:%s, name:%s\n", task.ID, task.Name) + logger.Warnf("invalid parameters. task id:%s, name:%s\n", task.ID, task.Name) return errors.New("invalid parameters") } diff --git a/pkg/mysqlx/mysqlx.go b/pkg/mysqlx/mysqlx.go index fc5c043..326b2c5 100644 --- a/pkg/mysqlx/mysqlx.go +++ b/pkg/mysqlx/mysqlx.go @@ -1,12 +1,13 @@ package mysqlx import ( + "time" + "go.uber.org/zap" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" - "time" ) type Conf struct { @@ -29,16 +30,21 @@ }, DisableForeignKeyConstraintWhenMigrating: true, } - dbLogger := New(log).LogMode(logger.Info) - if !conf.LogMode { - dbLogger = dbLogger.LogMode(logger.Silent) + + if log != nil { + dbLogger := New(log).LogMode(logger.Info) + if !conf.LogMode { + dbLogger = dbLogger.LogMode(logger.Silent) + } + + gConfig.Logger = dbLogger } - gConfig.Logger = dbLogger db, err := gorm.Open(mysql.Open(conf.Dsn), gConfig) if err != nil { return err } + sqlDb, err := db.DB() if err != nil { return err diff --git a/service/task_results.go b/service/task_results.go index 56e3ce8..2c72759 100644 --- a/service/task_results.go +++ b/service/task_results.go @@ -7,7 +7,7 @@ func SaveTaskResults(results []*db.ModelTaskResults) error { for _, v := range results { - err := db.NewModelTaskResultsSearch().SetPicDate(v.PicDate).SetFirstPersonId(v.FirstPersonID).FirstOrCreate(v) + err := db.NewModelTaskResultsSearch().SetTaskId(v.ModelTaskID).SetPicDate(v.PicDate).SetFirstPersonId(v.FirstPersonID).FirstOrCreate(v) if err != nil { logger.Errorf("save task result err:%v", err) return err -- Gitblit v1.8.0