From a57b44bd61276274fc003a8f5ffb9c035d4481db Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jul 2026 09:44:32 +0800 Subject: [PATCH] fix(user): serialize level experience awards --- src/AKSRT/User/Level.php | 79 +++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/src/AKSRT/User/Level.php b/src/AKSRT/User/Level.php index ac95caa7..5308479b 100644 --- a/src/AKSRT/User/Level.php +++ b/src/AKSRT/User/Level.php @@ -232,6 +232,21 @@ class Level { * @return int|false Granted amount or false. */ public static function add(int $user_id, string $key, int $points = 0, bool $bypass_day_max = false) { + if ($user_id <= 0) { + return false; + } + $lock = self::acquire_lock('user:' . $user_id); + if ($lock === '') { + return false; + } + try { + return self::add_locked($user_id, $key, $points, $bypass_day_max); + } finally { + self::release_lock($lock); + } + } + + private static function add_locked(int $user_id, string $key, int $points, bool $bypass_day_max) { $cfg = self::settings(); $key = sanitize_key($key); if ($user_id <= 0 || !$cfg['enabled'] || $key === '' || $key === 'day_max') { @@ -282,10 +297,34 @@ class Level { return $points; } + private static function acquire_lock(string $scope): string { + global $wpdb; + $name = substr($wpdb->prefix . 'aksrt_level_' . md5($scope), 0, 64); + $acquired = (int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, %d)', $name, 5)); + return $acquired === 1 ? $name : ''; + } + + private static function release_lock(string $name): void { + global $wpdb; + $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $name)); + } + private static function award_once(int $user_id, string $key, string $meta_type, int $entity_id, string $marker, int $limit = 1, int $actor_id = 0): void { if ($user_id <= 0 || $entity_id <= 0) { return; } + $lock = self::acquire_lock($meta_type . ':' . $entity_id . ':' . $marker); + if ($lock === '') { + return; + } + try { + self::award_once_locked($user_id, $key, $meta_type, $entity_id, $marker, $limit, $actor_id); + } finally { + self::release_lock($lock); + } + } + + private static function award_once_locked(int $user_id, string $key, string $meta_type, int $entity_id, string $marker, int $limit, int $actor_id): void { $get = $meta_type === 'comment' ? 'get_comment_meta' : 'get_post_meta'; $update = $meta_type === 'comment' ? 'update_comment_meta' : 'update_post_meta'; $awarded = $get($entity_id, $marker, true); @@ -310,11 +349,22 @@ class Level { public function on_login($user_login, $user): void { $user_id = $user instanceof \WP_User ? (int) $user->ID : 0; $today = current_time('Ymd'); - if ($user_id <= 0 || get_user_meta($user_id, self::META_LOGIN_DAY, true) === $today) { + if ($user_id <= 0) { return; } - if (self::add($user_id, 'sign_in')) { - update_user_meta($user_id, self::META_LOGIN_DAY, $today); + $lock = self::acquire_lock('login:' . $user_id . ':' . $today); + if ($lock === '') { + return; + } + try { + if (get_user_meta($user_id, self::META_LOGIN_DAY, true) === $today) { + return; + } + if (self::add($user_id, 'sign_in')) { + update_user_meta($user_id, self::META_LOGIN_DAY, $today); + } + } finally { + self::release_lock($lock); } } @@ -367,14 +417,25 @@ class Level { public function on_followed($follower_id, $followed_id): void { $follower_id = (int) $follower_id; $followed_id = (int) $followed_id; - $awarded = get_user_meta($followed_id, self::META_FOLLOWED, true); - $awarded = is_array($awarded) ? array_map('intval', $awarded) : []; - if ($follower_id <= 0 || $followed_id <= 0 || in_array($follower_id, $awarded, true)) { + if ($follower_id <= 0 || $followed_id <= 0) { return; } - if (self::add($followed_id, 'followed')) { - $awarded[] = $follower_id; - update_user_meta($followed_id, self::META_FOLLOWED, array_values(array_unique($awarded))); + $lock = self::acquire_lock('follow:' . $followed_id); + if ($lock === '') { + return; + } + try { + $awarded = get_user_meta($followed_id, self::META_FOLLOWED, true); + $awarded = is_array($awarded) ? array_map('intval', $awarded) : []; + if (in_array($follower_id, $awarded, true)) { + return; + } + if (self::add($followed_id, 'followed')) { + $awarded[] = $follower_id; + update_user_meta($followed_id, self::META_FOLLOWED, array_values(array_unique($awarded))); + } + } finally { + self::release_lock($lock); } }