package app import ( "context" "log" "time" ) // The address table holds a stranger's door number and phone. Keeping that // after the job is over buys nothing and costs a lot if the database ever // leaks, so a finished task schedules its address for erasure and this worker // carries it out. The city and street stay: they are what the history reads // from, and they were public on the listing anyway. func (a *App) scheduleAddressPurge(ctx context.Context) int64 { days := a.configInt(ctx, "pet.address_purge_days", 7) if days < 1 { days = 7 } // 只给"没有任何进行中任务"的地址排期;主人常用的地址不会因为一次任务结束就被清空。 result, err := a.db.ExecContext(ctx, `UPDATE pet_addresses addr JOIN (SELECT address_id,MAX(updated_at) AS finished FROM pet_feed_tasks WHERE status IN ('SETTLED','REFUNDED','CANCELLED','ARBITRATED') GROUP BY address_id) ended ON ended.address_id=addr.id SET addr.purge_at=DATE_ADD(ended.finished,INTERVAL ? DAY) WHERE addr.purge_at IS NULL AND NOT EXISTS (SELECT 1 FROM pet_feed_tasks live WHERE live.address_id=addr.id AND live.status IN ('CREATED','ESCROWED','ASSIGNED','SERVING','COMPLETED','DISPUTED'))`, days) if err != nil { log.Printf("pet address purge scheduling failed: %v", err) return 0 } affected, _ := result.RowsAffected() return affected } func (a *App) purgeDueAddresses(ctx context.Context) int64 { // 一个地址重新被用于新任务时会清掉 purge_at;这里再确认一次没有活任务, // 免得排期和复用之间抢到一起。 result, err := a.db.ExecContext(ctx, `UPDATE pet_addresses addr SET addr.detail_cipher='',addr.contact_cipher='',addr.emergency_cipher=NULL,addr.vet_hospital='', addr.latitude=NULL,addr.longitude=NULL,addr.purge_at=NULL WHERE addr.purge_at IS NOT NULL AND addr.purge_at<=NOW(3) AND NOT EXISTS (SELECT 1 FROM pet_feed_tasks live WHERE live.address_id=addr.id AND live.status IN ('CREATED','ESCROWED','ASSIGNED','SERVING','COMPLETED','DISPUTED'))`) if err != nil { log.Printf("pet address purge failed: %v", err) return 0 } affected, _ := result.RowsAffected() if affected > 0 { log.Printf("pet address purge: cleared plaintext for %d addresses", affected) } return affected } func (a *App) startAddressPurgeWorker(ctx context.Context) { go func() { ticker := time.NewTicker(time.Hour) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: a.scheduleAddressPurge(ctx) a.purgeDueAddresses(ctx) } } }() }