get_col($wpdb->prepare( "SELECT DISTINCT um.user_id FROM {$wpdb->usermeta} um WHERE um.meta_key = 'aksrt_vip_level' AND CAST(um.meta_value AS UNSIGNED) > 0 AND EXISTS ( SELECT 1 FROM {$wpdb->usermeta} expiry WHERE expiry.user_id = um.user_id AND expiry.meta_key = 'aksrt_vip_expires' AND CAST(expiry.meta_value AS UNSIGNED) > 0 AND CAST(expiry.meta_value AS UNSIGNED) <= %d ) ORDER BY user_id ASC LIMIT %d", $now, self::BATCH_SIZE )); foreach ((array) $rows as $user_id) { self::process_user((int) $user_id, 'cron'); } } /** * Resolve an expired current layer exactly once. * * @return array{changed:bool,before:array,after:array}|\WP_Error */ public static function process_user(int $user_id, string $source = 'resolver', string $reason = ''): array|\WP_Error { if ($user_id <= 0 || !get_userdata($user_id)) { return new \WP_Error('aksrt_vip_user_missing', __('用户不存在。', 'aksrt')); } $before = self::snapshot($user_id); if ($before['level'] <= 0 || $before['expires'] <= 0 || $before['expires'] > time()) { return ['changed' => false, 'before' => $before, 'after' => $before]; } $repository = new VipMembershipEventRepository(); $event_uuid = $repository->begin([ 'user_id' => $user_id, 'actor_user_id' => 0, 'operation' => 'expire_recover', 'source' => $source, 'reason' => $reason !== '' ? $reason : __('会员当前层到期处理', 'aksrt'), 'request' => ['now' => time()], 'before' => $before, ]); if (is_wp_error($event_uuid)) { return $event_uuid; } $fallback_valid = $before['base_level'] > 0 && ($before['base_expires'] === 0 || $before['base_expires'] > time()); if ($fallback_valid) { update_user_meta($user_id, 'aksrt_vip_level', $before['base_level']); update_user_meta($user_id, 'aksrt_vip_expires', $before['base_expires']); } else { update_user_meta($user_id, 'aksrt_vip_level_expired', max($before['expired_level'], $before['level'])); update_user_meta($user_id, 'aksrt_vip_level', 0); update_user_meta($user_id, 'aksrt_vip_expires', 0); } delete_user_meta($user_id, 'aksrt_vip_base_level'); delete_user_meta($user_id, 'aksrt_vip_base_expires'); $after = self::snapshot($user_id); $expected_level = $fallback_valid ? $before['base_level'] : 0; $expected_expires = $fallback_valid ? $before['base_expires'] : 0; if ($after['level'] !== $expected_level || $after['expires'] !== $expected_expires || $after['base_level'] !== 0 || $after['base_expires'] !== 0) { self::restore($user_id, $before); $after = self::snapshot($user_id); $error = new \WP_Error('aksrt_vip_expiry_write_failed', __('会员到期状态保存失败。', 'aksrt')); $repository->complete($event_uuid, 'failed', $after, $error->get_error_code(), $error->get_error_message()); return $error; } $repository->complete($event_uuid, 'succeeded', $after); do_action('aksrt_vip_membership_expired', $user_id, $before, $after, $source, $event_uuid); return ['changed' => true, 'before' => $before, 'after' => $after]; } /** @return array{level:int,expires:int,base_level:int,base_expires:int,expired_level:int} */ public static function snapshot(int $user_id): array { return [ 'level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_level', true))), 'expires' => max(0, (int) get_user_meta($user_id, 'aksrt_vip_expires', true)), 'base_level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_base_level', true))), 'base_expires' => max(0, (int) get_user_meta($user_id, 'aksrt_vip_base_expires', true)), 'expired_level' => max(0, min(2, (int) get_user_meta($user_id, 'aksrt_vip_level_expired', true))), ]; } /** @param array $snapshot */ private static function restore(int $user_id, array $snapshot): void { foreach (['level' => 'aksrt_vip_level', 'expires' => 'aksrt_vip_expires', 'base_level' => 'aksrt_vip_base_level', 'base_expires' => 'aksrt_vip_base_expires', 'expired_level' => 'aksrt_vip_level_expired'] as $key => $meta_key) { $value = (int) ($snapshot[$key] ?? 0); if ($value > 0 || in_array($key, ['level', 'expires'], true)) { update_user_meta($user_id, $meta_key, $value); } else { delete_user_meta($user_id, $meta_key); } } } }