优化发布群租客清退与历史消息

This commit is contained in:
yml
2026-06-18 09:39:23 +08:00
parent 233920025e
commit f4361c9804
10 changed files with 325 additions and 33 deletions
+88
View File
@@ -10,6 +10,7 @@ import (
"time"
"hfb_sys/backend/internal/model"
"hfb_sys/backend/internal/modules/chat"
"hfb_sys/backend/internal/modules/notification"
"github.com/redis/go-redis/v9"
@@ -21,6 +22,12 @@ import (
const orderTimeoutLockKey = "hfb:job:ordertimeout:lock"
const (
defaultRenterRetentionDaysAfterOrderEnd = 5
minRenterRetentionDaysAfterOrderEnd = 3
maxRenterRetentionDaysAfterOrderEnd = 7
)
type Job struct {
db *gorm.DB
redis *redis.Client
@@ -35,6 +42,7 @@ type thresholds struct {
RenterConfirmTimeoutMinutes int
ReturnOverdueGraceMinutes int
OwnerReturnConfirmTimeoutMinutes int
RenterRetentionDaysAfterOrderEnd int
}
func New(db *gorm.DB, redisClient *redis.Client, logger *zap.Logger) *Job {
@@ -130,6 +138,7 @@ func (j *Job) run(ctx context.Context) {
j.handleRenterConfirmTimeout,
j.handleReturnOverdue,
j.handleOwnerReturnConfirmTimeout,
j.handleEndedOrderRenterRetention,
}
total := 0
for _, handler := range handlers {
@@ -152,6 +161,7 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
RenterConfirmTimeoutMinutes: 30,
ReturnOverdueGraceMinutes: 10,
OwnerReturnConfirmTimeoutMinutes: 120,
RenterRetentionDaysAfterOrderEnd: defaultRenterRetentionDaysAfterOrderEnd,
}
var rows []model.SystemConfig
err := j.db.WithContext(ctx).
@@ -161,6 +171,7 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
"order.pending_payment_timeout_minutes",
"order.return_overdue_grace_minutes",
"handoff.owner_return_confirm_timeout_minutes",
"chat.renter_retention_days_after_order_end",
}).
Find(&rows).Error
if err != nil {
@@ -182,11 +193,23 @@ func (j *Job) loadThresholds(ctx context.Context) (thresholds, error) {
cfg.ReturnOverdueGraceMinutes = value
case "handoff.owner_return_confirm_timeout_minutes":
cfg.OwnerReturnConfirmTimeoutMinutes = value
case "chat.renter_retention_days_after_order_end":
cfg.RenterRetentionDaysAfterOrderEnd = clampRenterRetentionDays(value)
}
}
return cfg, nil
}
func clampRenterRetentionDays(value int) int {
if value < minRenterRetentionDaysAfterOrderEnd {
return minRenterRetentionDaysAfterOrderEnd
}
if value > maxRenterRetentionDaysAfterOrderEnd {
return maxRenterRetentionDaysAfterOrderEnd
}
return value
}
func (j *Job) handlePendingPaymentTimeout(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
if cfg.PendingPaymentTimeoutMinutes <= 0 {
return 0, nil
@@ -483,6 +506,71 @@ func (j *Job) handleOwnerReturnConfirmTimeout(ctx context.Context, now time.Time
return count, nil
}
func (j *Job) handleEndedOrderRenterRetention(ctx context.Context, now time.Time, cfg thresholds) (int, error) {
retentionDays := clampRenterRetentionDays(cfg.RenterRetentionDaysAfterOrderEnd)
deadline := now.AddDate(0, 0, -retentionDays)
var rows []model.RentalOrder
err := j.db.WithContext(ctx).
Table("rental_orders AS o").
Select("o.*").
Joins("JOIN chat_conversations AS c ON c.listing_id = o.listing_id AND c.type = ?", chat.ConversationTypeListingGroup).
Joins("JOIN chat_participants AS cp ON cp.conversation_id = c.id AND cp.participant_type = ? AND cp.participant_id = o.renter_id AND cp.role = ?", "user", "renter").
Where("o.status IN ?", []string{"completed", "cancelled", "closed"}).
Where("o.settled_at IS NOT NULL AND o.settled_at <= ?", deadline).
Order("o.id ASC").
Limit(100).
Find(&rows).Error
if err != nil {
return 0, err
}
count := 0
for _, row := range rows {
removed := false
if err := j.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&order, row.ID).Error; err != nil {
return err
}
if !isEndedOrderStatus(order.Status) {
return nil
}
if order.SettledAt == nil || order.SettledAt.After(deadline) {
return nil
}
var err error
removed, err = chat.RemoveRenterFromListingConversation(tx, order.ListingID, order.RenterID)
if err != nil {
return err
}
if !removed {
return nil
}
return appendAuditLog(tx, "chat.renter_retention.remove", order.ID, map[string]any{
"order_id": order.ID,
"order_no": order.OrderNo,
"listing_id": order.ListingID,
"renter_id": order.RenterID,
"retention_days": retentionDays,
})
}); err != nil {
return count, err
}
if removed {
count++
}
}
return count, nil
}
func isEndedOrderStatus(status string) bool {
switch status {
case "completed", "cancelled", "closed":
return true
default:
return false
}
}
func (j *Job) updateOrder(ctx context.Context, orderID uint64, action string, fn func(*gorm.DB, *model.RentalOrder) (string, error)) error {
return j.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var order model.RentalOrder