package mysql import ( "context" "database/sql" "errors" "time" "hyapp/pkg/appcode" "hyapp/pkg/xerr" ) func (r *Repository) creditHostPeriodDiamonds(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, metadata giftMetadata, nowMs int64) (int64, int64, error) { if metadata.HostPeriodDiamondAdded <= 0 { return 0, 0, nil } after, version, exists, err := r.lockHostPeriodDiamondAccount(ctx, tx, metadata.TargetUserID, metadata.HostPeriodCycleKey) if err != nil { return 0, 0, err } if !exists { after = metadata.HostPeriodDiamondAdded version = 1 _, err = tx.ExecContext(ctx, `INSERT INTO host_period_diamond_accounts ( app_code, user_id, cycle_key, region_id, agency_owner_user_id, total_diamonds, gift_diamond_total, version, first_gift_at_ms, last_gift_at_ms, created_at_ms, updated_at_ms ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, appcode.FromContext(ctx), metadata.TargetUserID, metadata.HostPeriodCycleKey, metadata.TargetHostRegionID, metadata.TargetAgencyOwnerUserID, after, metadata.HostPeriodDiamondAdded, version, nowMs, nowMs, nowMs, nowMs, ) if err != nil && isMySQLDuplicateError(err) { after, version, exists, err = r.lockHostPeriodDiamondAccount(ctx, tx, metadata.TargetUserID, metadata.HostPeriodCycleKey) } if err != nil { return 0, 0, err } if !exists { if err := r.insertHostPeriodDiamondEntry(ctx, tx, transactionID, commandID, metadata, after, nowMs); err != nil { return 0, 0, err } return after, version, nil } } after, err = checkedAdd(after, metadata.HostPeriodDiamondAdded) if err != nil { return 0, 0, err } version++ result, err := tx.ExecContext(ctx, `UPDATE host_period_diamond_accounts SET region_id = ?, agency_owner_user_id = ?, total_diamonds = ?, gift_diamond_total = gift_diamond_total + ?, version = ?, last_gift_at_ms = ?, updated_at_ms = ? WHERE app_code = ? AND user_id = ? AND cycle_key = ?`, metadata.TargetHostRegionID, metadata.TargetAgencyOwnerUserID, after, metadata.HostPeriodDiamondAdded, version, nowMs, nowMs, appcode.FromContext(ctx), metadata.TargetUserID, metadata.HostPeriodCycleKey, ) if err != nil { return 0, 0, err } rows, err := result.RowsAffected() if err != nil { return 0, 0, err } if rows != 1 { return 0, 0, xerr.New(xerr.LedgerConflict, "host period diamond account version conflict") } if err := r.insertHostPeriodDiamondEntry(ctx, tx, transactionID, commandID, metadata, after, nowMs); err != nil { return 0, 0, err } return after, version, nil } func (r *Repository) lockHostPeriodDiamondAccount(ctx context.Context, tx *sql.Tx, userID int64, cycleKey string) (int64, int64, bool, error) { row := tx.QueryRowContext(ctx, `SELECT total_diamonds, version FROM host_period_diamond_accounts WHERE app_code = ? AND user_id = ? AND cycle_key = ? FOR UPDATE`, appcode.FromContext(ctx), userID, cycleKey, ) var totalDiamonds int64 var version int64 if err := row.Scan(&totalDiamonds, &version); err != nil { if errors.Is(err, sql.ErrNoRows) { return 0, 0, false, nil } return 0, 0, false, err } return totalDiamonds, version, true, nil } func (r *Repository) insertHostPeriodDiamondEntry(ctx context.Context, tx *sql.Tx, transactionID string, commandID string, metadata giftMetadata, diamondAfter int64, nowMs int64) error { _, err := tx.ExecContext(ctx, `INSERT INTO host_period_diamond_entries ( app_code, transaction_id, command_id, user_id, cycle_key, region_id, agency_owner_user_id, diamond_delta, diamond_after, gift_charge_asset_type, gift_charge_amount, gift_id, gift_count, sender_user_id, room_id, created_at_ms ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, appcode.FromContext(ctx), transactionID, commandID, metadata.TargetUserID, metadata.HostPeriodCycleKey, metadata.TargetHostRegionID, metadata.TargetAgencyOwnerUserID, metadata.HostPeriodDiamondAdded, diamondAfter, metadata.ChargeAssetType, metadata.ChargeAmount, metadata.GiftID, metadata.GiftCount, metadata.SenderUserID, metadata.RoomID, nowMs, ) return err } func hostPeriodCycleKeyFromMS(ms int64) string { return time.UnixMilli(ms).UTC().Format("2006-01") }