积分与余额账务系统硬切重构

This commit is contained in:
kate522 committed 2026-08-24 15:59:11 +08:00
1 parent ca12226b39
commit 5cae5b5c93
159 files changed
+7275 -4718

No files matched your search

+118 -64
View File
@@ -27,33 +27,36 @@ class Signin {
register_rest_route('aksrt/v1', '/signin/today', [
'methods' => 'GET',
'callback' => [$this, 'rest_today'],
'permission_callback' => function () {
return is_user_logged_in();
},
'permission_callback' => [$this, 'check_rest_permission'],
]);
register_rest_route('aksrt/v1', '/signin', [
'methods' => 'POST',
'callback' => [$this, 'rest_signin'],
'permission_callback' => function () {
return is_user_logged_in();
},
'permission_callback' => [$this, 'check_rest_permission'],
]);
register_rest_route('aksrt/v1', '/signin/history', [
'methods' => 'GET',
'callback' => [$this, 'rest_history'],
'permission_callback' => function () {
return is_user_logged_in();
},
'permission_callback' => [$this, 'check_rest_permission'],
]);
register_rest_route('aksrt/v1', '/signin/details', [
'methods' => 'GET',
'callback' => [$this, 'rest_details_html'],
'permission_callback' => function () {
return is_user_logged_in();
},
'permission_callback' => [$this, 'check_rest_permission'],
]);
}
public function check_rest_permission(\WP_REST_Request $request) {
if (!is_user_logged_in()) {
return new \WP_Error('aksrt_need_login', __('请先登录', 'aksrt'), ['status' => 401]);
}
$nonce = (string) $request->get_header('X-WP-Nonce');
if ($nonce === '' || !wp_verify_nonce($nonce, 'wp_rest')) {
return new \WP_Error('aksrt_rest_nonce', __('安全验证失败,请刷新页面后重试', 'aksrt'), ['status' => 403]);
}
return true;
}
/** GET /signin/today */
public function rest_today(\WP_REST_Request $request) {
$user_id = get_current_user_id();
@@ -99,10 +102,6 @@ class Signin {
}
$today_str = $this->today_date();
if ($this->get_record($user_id, $today_str)) {
return new \WP_Error('aksrt_signin_dup', __('今日已签到', 'aksrt'), ['status' => 409]);
}
// 人机验证(若启用)
if (function_exists('aksrt_require_captcha')) {
$body = $request->get_json_params();
@@ -116,59 +115,75 @@ class Signin {
}
}
$current_streak = $this->compute_current_streak($user_id, $today_str);
$new_streak = $current_streak + 1;
$reward = $this->calc_reward($new_streak, $signin_cfg);
$existing_record = $this->get_record($user_id, $today_str);
if (is_array($existing_record)) {
$new_streak = max(1, (int) ($existing_record['streak'] ?? 1));
$reward = max(0, (int) ($existing_record['reward'] ?? 0));
} else {
$current_streak = $this->compute_current_streak($user_id, $today_str);
$new_streak = $current_streak + 1;
$reward = $this->calc_reward($new_streak, $signin_cfg);
}
$table = \AKSRT\Core\DBMigrator::table('signin_log');
if (!$table) {
return new \WP_Error('aksrt_signin_db', __('数据表不存在', 'aksrt'), ['status' => 500]);
}
$inserted = $wpdb->insert(
$table,
[
'user_id' => $user_id,
'signin_date' => $today_str,
'streak' => $new_streak,
'reward' => $reward,
],
['%d', '%s', '%d', '%d']
);
if ($inserted === false) {
return new \WP_Error('aksrt_signin_dup', __('签到失败', 'aksrt'), ['status' => 409]);
}
$operation_key = \AKSRT\Ledger\OperationKey::deterministic('signin', [$user_id, $today_str]);
$transaction_id = null;
$replayed = false;
if ($reward > 0) {
$guard = static function () use ($wpdb, $table, $user_id, $today_str) {
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$table} WHERE user_id = %d AND signin_date = %s FOR UPDATE",
$user_id,
$today_str
));
return $existing === null
? true
: new \WP_Error('aksrt_signin_dup', __('今日已签到', 'aksrt'), ['status' => 409]);
};
$mutation = static function () use ($wpdb, $table, $user_id, $today_str, $new_streak, $reward) {
$inserted = $wpdb->insert($table, [
'user_id' => $user_id,
'signin_date' => $today_str,
'streak' => $new_streak,
'reward' => $reward,
], ['%d', '%s', '%d', '%d']);
return $inserted === 1
? true
: new \WP_Error('aksrt_signin_db', __('签到记录写入失败', 'aksrt'), ['status' => 500]);
};
$ledger_result = \AKSRT\Ledger\LedgerService::get_instance()->credit(
$user_id,
\AKSRT\Ledger\AssetType::POINTS,
$reward,
$operation_key,
'signin',
['type' => 'signin', 'key' => $today_str],
__('每日签到奖励', 'aksrt'),
$user_id,
['signin_date' => $today_str, 'streak' => $new_streak],
$mutation,
$guard
);
if (is_wp_error($ledger_result)) {
return $ledger_result;
}
$transaction_id = $ledger_result->transaction_id;
$replayed = $ledger_result->replayed;
} else {
$stored = $this->store_zero_reward_signin($user_id, $today_str, $new_streak);
if (is_wp_error($stored)) {
return $stored;
}
$replayed = $stored;
}
do_action('aksrt_user_signed_in', $user_id, $reward);
if ($reward > 0) {
if (class_exists('AKSRT\\Credits\\PointsFree')) {
$granted = PointsFree::grant_amount($user_id, $reward, 'sign_in', ['type' => 'user', 'id' => $user_id]);
if (is_wp_error($granted)) {
if (class_exists('AKSRT\\Credits\\Credits')) {
Credits::add($user_id, $reward, 'signin', ['type' => 'user', 'id' => $user_id]);
}
} else {
$reward = (int) $granted;
}
} elseif (class_exists('AKSRT\\Credits\\Credits')) {
Credits::add($user_id, $reward, 'signin', ['type' => 'user', 'id' => $user_id]);
}
}
// Keep lightweight meta for header/badge consumers (legacy checkin_all_day).
update_user_meta($user_id, 'aksrt_signin_' . $today_str, 1);
$all_day = (int) get_user_meta($user_id, 'aksrt_checkin_all_day', true);
if ($all_day <= 0) {
// Fallback to legacy key if migrated users only have that meta.
$all_day = (int) get_user_meta($user_id, 'checkin_all_day', true);
}
$all_day = $all_day + 1;
update_user_meta($user_id, 'aksrt_checkin_all_day', $all_day);
update_user_meta($user_id, 'checkin_all_day', $all_day); // legacy alias
update_user_meta($user_id, 'aksrt_checkin_continuous_day', $new_streak);
update_user_meta($user_id, 'checkin_continuous_day', $new_streak); // legacy alias
do_action('aksrt_checkin_done', $user_id);
if (!$replayed) {
$this->after_signin_committed($user_id, $today_str, $new_streak, $reward);
}
return new \WP_REST_Response([
'success' => true,
@@ -180,10 +195,49 @@ class Signin {
),
'streak' => $new_streak,
'reward' => $reward,
'balance' => class_exists('AKSRT\Credits\Credits') ? Credits::balance($user_id) : 0,
'balance' => \AKSRT\Ledger\LedgerService::get_instance()->balance($user_id, \AKSRT\Ledger\AssetType::POINTS),
'transaction_id' => $transaction_id,
'replayed' => $replayed,
], 200);
}
/** @return bool|\WP_Error True means the existing row was replayed. */
private function store_zero_reward_signin(int $user_id, string $date, int $streak) {
global $wpdb;
$table = \AKSRT\Core\DBMigrator::table('signin_log');
if ($wpdb->query('START TRANSACTION') === false) {
return new \WP_Error('aksrt_signin_db', __('无法开启签到事务', 'aksrt'), ['status' => 500]);
}
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$table} WHERE user_id = %d AND signin_date = %s FOR UPDATE",
$user_id,
$date
));
if ($existing !== null) {
$wpdb->query('COMMIT');
return true;
}
$inserted = $wpdb->insert($table, [
'user_id' => $user_id,
'signin_date' => $date,
'streak' => $streak,
'reward' => 0,
], ['%d', '%s', '%d', '%d']);
if ($inserted !== 1 || $wpdb->query('COMMIT') === false) {
$wpdb->query('ROLLBACK');
return new \WP_Error('aksrt_signin_db', __('签到记录写入失败', 'aksrt'), ['status' => 500]);
}
return false;
}
private function after_signin_committed(int $user_id, string $date, int $streak, int $reward): void {
update_user_meta($user_id, 'aksrt_signin_' . $date, 1);
update_user_meta($user_id, 'aksrt_checkin_all_day', $this->get_total_days($user_id));
update_user_meta($user_id, 'aksrt_checkin_continuous_day', $streak);
do_action('aksrt_user_signed_in', $user_id, $reward);
do_action('aksrt_checkin_done', $user_id);
}
/** GET /signin/history — last N days for calendar/modals. */
public function rest_history(\WP_REST_Request $request) {
$user_id = get_current_user_id();
@@ -260,7 +314,7 @@ class Signin {
$rec = $this->get_record($user_id, $today);
$streak = $this->compute_current_streak($user_id, $today);
$total = $this->get_total_days($user_id);
$balance = class_exists('\AKSRT\Credits\Credits') ? (int) Credits::balance($user_id) : 0;
$balance = \AKSRT\Ledger\LedgerService::get_instance()->balance($user_id, \AKSRT\Ledger\AssetType::POINTS);
$currency = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_currency_name()
: __('积分', 'aksrt');