getData('is_vip') === 1 && (int) $user->getData('vip_expire_time') > time(); } public static function resetDailyIfNeeded(User $user): int { $today = date('Y-m-d'); if ($user->getData('ai_free_date') !== $today) { $user->save([ 'ai_free_count' => self::DAILY_FREE_COUNT, 'ai_free_date' => $today, ]); return self::DAILY_FREE_COUNT; } return max(0, (int) $user->getData('ai_free_count')); } public static function getRemainCount(int $userId): int { $user = User::findOrEmpty($userId); if ($user->isEmpty()) { return 0; } if (self::isVip($user)) { return self::VIP_REMAIN_COUNT; } return self::resetDailyIfNeeded($user); } public static function makeKey(string $analysisType, $refId = 0, array $parts = []): string { $segments = array_merge([$analysisType, (string) $refId], array_map(static function ($part) { return trim((string) $part); }, $parts)); $segments = array_values(array_filter($segments, static fn($part) => $part !== '')); return self::normalizeKey(implode(':', $segments), $analysisType); } public static function hasUnlocked(int $userId, string $analysisKey): bool { if ($userId <= 0 || $analysisKey === '') { return false; } return AiQuotaUnlock::where('user_id', $userId) ->where('analysis_key', self::normalizeKey($analysisKey)) ->count() > 0; } public static function unlock(int $userId, string $analysisType, string $analysisKey, int $refId = 0): array { if ($userId <= 0) { return [ 'ok' => false, 'msg' => '请先登录', 'code' => 401, 'remain' => 0, 'unlocked' => false, 'charged' => false, ]; } $analysisType = trim($analysisType); $analysisKey = self::normalizeKey($analysisKey, $analysisType); if ($analysisType === '' || $analysisKey === '') { return ['ok' => false, 'msg' => 'AI分析类型缺失', 'remain' => self::getRemainCount($userId)]; } return Db::transaction(function () use ($userId, $analysisType, $analysisKey, $refId) { $user = User::where('id', $userId)->lock(true)->findOrEmpty(); if ($user->isEmpty()) { return ['ok' => false, 'msg' => '用户不存在', 'remain' => 0]; } $existing = AiQuotaUnlock::where('user_id', $userId) ->where('analysis_key', $analysisKey) ->findOrEmpty(); if (!$existing->isEmpty()) { return [ 'ok' => true, 'msg' => '已解锁', 'remain' => self::getRemainCountForUser($user), 'unlocked' => true, 'charged' => false, ]; } if (self::isVip($user)) { self::createUnlockRecord($userId, $analysisType, $analysisKey, $refId); return [ 'ok' => true, 'msg' => 'VIP解锁', 'remain' => self::VIP_REMAIN_COUNT, 'unlocked' => true, 'charged' => false, ]; } $freeCount = self::resetDailyIfNeeded($user); if ($freeCount <= 0) { return [ 'ok' => false, 'msg' => '今日免费次数已用完,升级VIP可无限使用', 'remain' => 0, 'unlocked' => false, 'charged' => false, ]; } $remain = $freeCount - 1; $user->save(['ai_free_count' => $remain]); self::createUnlockRecord($userId, $analysisType, $analysisKey, $refId); return [ 'ok' => true, 'msg' => '解锁成功', 'remain' => $remain, 'unlocked' => true, 'charged' => true, ]; }); } private static function createUnlockRecord(int $userId, string $analysisType, string $analysisKey, int $refId): void { AiQuotaUnlock::create([ 'user_id' => $userId, 'analysis_type' => $analysisType, 'analysis_key' => $analysisKey, 'ref_id' => $refId, ]); } private static function getRemainCountForUser(User $user): int { if (self::isVip($user)) { return self::VIP_REMAIN_COUNT; } return self::resetDailyIfNeeded($user); } private static function normalizeKey(string $analysisKey, string $analysisType = 'ai'): string { $analysisKey = trim(preg_replace('/\s+/', '', $analysisKey) ?? ''); if ($analysisKey === '') { return ''; } if (strlen($analysisKey) <= 190) { return $analysisKey; } $prefix = trim($analysisType) ?: 'ai'; return $prefix . ':' . sha1($analysisKey); } }