'GET', 'callback' => [$this, 'rest_today'], 'permission_callback' => function () { return is_user_logged_in(); }, ]); register_rest_route('aksrt/v1', '/signin', [ 'methods' => 'POST', 'callback' => [$this, 'rest_signin'], 'permission_callback' => function () { return is_user_logged_in(); }, ]); } /** GET /signin/today */ public function rest_today(WP_REST_Request $request) { $user_id = get_current_user_id(); $signin_cfg = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_signin_reward_settings() : [ 'enabled' => false, 'base' => 0, 'streak' => [], ]; $today_str = $this->today_date(); $rec = $this->get_record($user_id, $today_str); $current_streak = $this->compute_current_streak($user_id, $today_str); $next_streak = $current_streak + ($rec ? 0 : 1); return new WP_REST_Response([ 'enabled' => !empty($signin_cfg['enabled']), 'signed' => (bool) $rec, 'streak' => $current_streak, 'reward_today' => $rec ? (int) $rec['reward'] : 0, 'reward_if_signin' => $rec ? 0 : $this->calc_reward($next_streak, $signin_cfg), 'date' => $today_str, ], 200); } /** POST /signin */ public function rest_signin(WP_REST_Request $request) { global $wpdb; $user_id = get_current_user_id(); $signin_cfg = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_signin_reward_settings() : [ 'enabled' => false, 'base' => 0, 'streak' => [], ]; if (empty($signin_cfg['enabled'])) { return new WP_Error('aksrt_signin_disabled', __('签到功能未启用', 'aksrt'), ['status' => 403]); } $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(); $cap_payload = is_array($body) && isset($body['captcha']) && is_array($body['captcha']) ? $body['captcha'] : (is_array($body) ? $body : []); $verify = aksrt_require_captcha('signin', $cap_payload); if ($verify !== true) { $msg = is_string($verify) ? $verify : __('人机验证失败', 'aksrt'); return new WP_Error('aksrt_captcha_failed', $msg, ['status' => 400]); } } $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_DB_Migrator::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]); } if ($reward > 0) { if (class_exists('AKSRT_Points_Free')) { $granted = AKSRT_Points_Free::grant_amount($user_id, $reward, 'sign_in', ['type' => 'user', 'id' => $user_id]); if (is_wp_error($granted)) { if (class_exists('AKSRT_Credits')) { AKSRT_Credits::add($user_id, $reward, 'signin', ['type' => 'user', 'id' => $user_id]); } } else { $reward = (int) $granted; } } elseif (class_exists('AKSRT_Credits')) { AKSRT_Credits::add($user_id, $reward, 'signin', ['type' => 'user', 'id' => $user_id]); } } return new WP_REST_Response([ 'success' => true, 'streak' => $new_streak, 'reward' => $reward, 'balance' => class_exists('AKSRT_Credits') ? AKSRT_Credits::balance($user_id) : 0, ], 200); } // ---------- 工具方法 ---------- /** 站点时区下的今日日期 'Y-m-d' */ private function today_date(): string { return (new DateTimeImmutable('today', wp_timezone()))->format('Y-m-d'); } /** 取签到记录 */ private function get_record(int $user_id, string $date): ?array { global $wpdb; $table = AKSRT_DB_Migrator::table('signin_log'); if (!$table) return null; $row = $wpdb->get_row($wpdb->prepare( "SELECT * FROM {$table} WHERE user_id = %d AND signin_date = %s", $user_id, $date ), ARRAY_A); return $row ?: null; } /** * 获取最近 N 天的签到记录 * * @param int $user_id * @param int $days * @return array */ public function get_recent_signins(int $user_id, int $days = 30): array { global $wpdb; $table = AKSRT_DB_Migrator::table('signin_log'); if (!$table) return []; $from_date = (new DateTimeImmutable("-{$days} days", wp_timezone()))->format('Y-m-d'); $rows = $wpdb->get_results($wpdb->prepare( "SELECT signin_date FROM {$table} WHERE user_id = %d AND signin_date >= %s ORDER BY signin_date ASC", $user_id, $from_date ), ARRAY_A); return $rows ?: []; } /** * 算"今天之前"的连续签到天数。 */ private function compute_current_streak(int $user_id, string $today_str): int { global $wpdb; $table = AKSRT_DB_Migrator::table('signin_log'); if (!$table) return 0; // 拿最近 60 天的签到记录降序 $rows = $wpdb->get_results($wpdb->prepare( "SELECT signin_date FROM {$table} WHERE user_id = %d AND signin_date <= %s ORDER BY signin_date DESC LIMIT 60", $user_id, $today_str ), ARRAY_A); if (empty($rows)) return 0; $dates = array_map(fn($r) => $r['signin_date'], $rows); $today = new DateTimeImmutable($today_str); $tz = wp_timezone(); // 起点:若今天已签则从今天回看,否则从昨天回看 $cursor = in_array($today_str, $dates, true) ? $today : $today->sub(new DateInterval('P1D')); $count = 0; foreach ($dates as $d) { if ($d === $cursor->format('Y-m-d')) { $count++; $cursor = $cursor->sub(new DateInterval('P1D')); } else { break; } } return $count; } /** * 根据连续天数计算奖励 = base + streak 段额外奖励。 */ private function calc_reward(int $streak, array $cfg): int { $base = (int) ($cfg['base'] ?? 1); $extra = 0; $streak_table = is_array($cfg['streak'] ?? null) ? $cfg['streak'] : []; // streak_table 是 { N: extra } —— 当 streak 恰好命中 N 时给 extra // 对 array_keys 不稳定的存储格式做容错 $normalized = []; foreach ($streak_table as $k => $v) { $normalized[(int) $k] = (int) $v; } if (isset($normalized[$streak])) { $extra = $normalized[$streak]; } return max(0, $base + $extra); } }