积分与余额账务系统硬切重构
This commit is contained in:
1 parent
ca12226b39
commit
5cae5b5c93
159 files changed
+7275
-4718
No files matched your search
+118
-64
@@ -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');
|
||||
|
||||
Reference in new issue
Block a user