Load(); require_once 'cache.php'; require_once 'response.php'; header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Cache-Control: public, max-age=5'); if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } $method = $_SERVER['REQUEST_METHOD']; if ($method === 'GET') { $params = $_GET; } elseif ($method === 'POST') { $input = file_get_contents('php://input'); $jsonData = json_decode($input, true); if (is_array($jsonData)) { $params = $jsonData; } else { $params = $_POST; } } else { $params = array(); } $format = ApiResponse::getFormat(); $forceRefresh = isset($params['_refresh']) && $params['_refresh'] === '1'; $clientIP = getClientIP(); header('X-Cache: MISS'); $counts = calculateCounts($params); $data = array( 'recipes' => getRandomRecipes($counts['recipe'], $clientIP), 'ingredients' => getRandomIngredients($counts['ingredient'], $clientIP), 'categories' => getRandomCategories($counts['category'], $clientIP), 'tags' => getRandomTags($counts['tag'], $clientIP), 'nutrition_types' => getNutritionTypes($counts['nutrition'], $clientIP), 'meal_times' => getMealTimes($counts['meal_time']) ); $recipeIds = array_column($data['recipes'], 'id'); $ingredientIds = array_column($data['ingredients'], 'id'); $categoryIds = array_column($data['categories'], 'id'); $tagIds = array_column($data['tags'], 'id'); $nutritionNames = array_column($data['nutrition_types'], 'name'); if (!empty($recipeIds)) { ApiCache::addDiscoverReturnedIds($clientIP, 'recipe', $recipeIds); } if (!empty($ingredientIds)) { ApiCache::addDiscoverReturnedIds($clientIP, 'ingredient', $ingredientIds); } if (!empty($categoryIds)) { ApiCache::addDiscoverReturnedIds($clientIP, 'category', $categoryIds); } if (!empty($tagIds)) { ApiCache::addDiscoverReturnedIds($clientIP, 'tag', $tagIds); } if (!empty($nutritionNames)) { ApiCache::addDiscoverReturnedIds($clientIP, 'nutrition', $nutritionNames); } $result = array( 'code' => 200, 'message' => 'success', 'data' => $data, 'meta' => array( 'cache_status' => 'fresh', 'counts' => $counts ) ); $result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms'; ApiResponse::output($result, $format); exit; /** * 获取客户端IP */ function getClientIP() { $ip = ''; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])[0]; } elseif (!empty($_SERVER['REMOTE_ADDR'])) { $ip = $_SERVER['REMOTE_ADDR']; } return trim($ip) ?: 'unknown'; } /** * 计算各类型数量 */ function calculateCounts($params) { $total = isset($params['total']) ? min((int) $params['total'], 100) : 50; $ratios = array( 'recipe' => 0.75, 'ingredient' => 0.03, 'category' => 0.10, 'tag' => 0.03, 'nutrition' => 0.02, 'meal_time' => 0.02 ); $counts = array(); $specifiedTotal = 0; $specifiedTypes = array(); foreach ($ratios as $type => $ratio) { $paramName = $type === 'nutrition' ? 'nutrition' : $type; if (isset($params[$paramName]) && (int) $params[$paramName] > 0) { $counts[$type] = min((int) $params[$paramName], 80); $specifiedTotal += $counts[$type]; $specifiedTypes[] = $type; } } if (count($specifiedTypes) === count($ratios)) { return $counts; } $remaining = $total - $specifiedTotal; $remainingTypes = array_diff(array_keys($ratios), $specifiedTypes); $remainingRatioSum = 0; foreach ($remainingTypes as $type) { $remainingRatioSum += $ratios[$type]; } foreach ($remainingTypes as $type) { $adjustedRatio = $remainingRatioSum > 0 ? $ratios[$type] / $remainingRatioSum : 1 / count($remainingTypes); $counts[$type] = max(1, round($remaining * $adjustedRatio)); } return $counts; } /** * 获取随机菜品 */ function getRandomRecipes($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tablePost = $zbp->db->dbpre . 'post'; $tableCategory = $zbp->db->dbpre . 'category'; $tablePostStat = $zbp->db->dbpre . 'post_stat'; $tableIdMap = $zbp->db->dbpre . 'recipe_id_map'; $excludeIds = array(); if (!empty($clientIP)) { $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'recipe'); } $whereClause = "p.log_Type = 0 AND p.log_Status = 0"; if (!empty($excludeIds)) { $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); $whereClause .= " AND p.log_ID NOT IN ($excludeIdsStr)"; } $sql = "SELECT p.log_ID, p.log_Title, p.log_CateID, p.log_ViewNums, c.cate_Name, s.rate_nums, s.rate_score, m.old_id FROM $tablePost p LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID LEFT JOIN $tablePostStat s ON p.log_ID = s.log_id LEFT JOIN $tableIdMap m ON p.log_ID = m.new_log_id WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); if (empty($results) && !empty($excludeIds)) { ApiCache::clearDiscoverReturnedIds($clientIP, 'recipe'); return getRandomRecipes($limit, $clientIP); } $list = array(); foreach ($results as $row) { $rateNums = (int) ($row['rate_nums'] ?? 0); $rateScore = (float) ($row['rate_score'] ?? 0); $rating = ApiResponse::getRatingSummary($rateScore, $rateNums); $cover = ''; if (!empty($row['old_id'])) { $cover = 'http://eat.wktyl.com/api/assets/pic/' . $row['old_id'] . 'a.jpg'; } $list[] = array( 'id' => (int) $row['log_ID'], 'title' => $row['log_Title'], 'cover' => $cover, 'category' => array( 'id' => (int) $row['log_CateID'], 'name' => $row['cate_Name'] ?? '' ), 'rating' => $rating, 'views' => (int) $row['log_ViewNums'] ); } return $list; } /** * 获取随机食材 */ function getRandomIngredients($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableIngredient = $zbp->db->dbpre . 'ingredient_detail'; $tableCategory = $zbp->db->dbpre . 'category'; $excludeIds = array(); if (!empty($clientIP)) { $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'ingredient'); } $whereClause = "1=1"; if (!empty($excludeIds)) { $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); $whereClause .= " AND i.ingredient_id NOT IN ($excludeIdsStr)"; } $sql = "SELECT i.ingredient_id, i.name, i.cate_ID, i.allergen, i.introduction, c.cate_Name FROM $tableIngredient i LEFT JOIN $tableCategory c ON i.cate_ID = c.cate_ID WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); if (empty($results) && !empty($excludeIds)) { ApiCache::clearDiscoverReturnedIds($clientIP, 'ingredient'); return getRandomIngredients($limit, $clientIP); } $list = array(); foreach ($results as $row) { $allergen = array(); if (!empty($row['allergen'])) { $decoded = json_decode($row['allergen'], true); $allergen = is_array($decoded) ? $decoded : array($row['allergen']); } $intro = $row['introduction'] ?? ''; if (mb_strlen($intro) > 100) { $intro = mb_substr($intro, 0, 100) . '...'; } $list[] = array( 'id' => (int) $row['ingredient_id'], 'name' => $row['name'], 'category' => array( 'id' => (int) $row['cate_ID'], 'name' => $row['cate_Name'] ?? '' ), 'allergen' => $allergen, 'intro' => $intro ); } return $list; } /** * 获取随机分类 */ function getRandomCategories($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableCategory = $zbp->db->dbpre . 'category'; $tableIngredient = $zbp->db->dbpre . 'ingredient_detail'; $excludeIds = array(); if (!empty($clientIP)) { $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'category'); } $excludeClause = ''; if (!empty($excludeIds)) { $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); $excludeClause = " AND cate_ID NOT IN ($excludeIdsStr)"; } $recipeLimit = max(1, ceil($limit * 0.6)); $ingredientLimit = $limit - $recipeLimit; $results = array(); $sqlRecipe = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID FROM $tableCategory WHERE cate_ID > 10 AND cate_ID < 1000 AND cate_ParentID != 11 $excludeClause ORDER BY RAND() LIMIT $recipeLimit"; $recipeResults = $zbp->db->Query($sqlRecipe); $results = array_merge($results, $recipeResults); $sqlIngredient = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID FROM $tableCategory WHERE cate_ID >= 1000 $excludeClause ORDER BY RAND() LIMIT $ingredientLimit"; $ingredientResults = $zbp->db->Query($sqlIngredient); $results = array_merge($results, $ingredientResults); if (empty($results) && !empty($excludeIds)) { ApiCache::clearDiscoverReturnedIds($clientIP, 'category'); return getRandomCategories($limit, $clientIP); } $ingredientCounts = array(); $cateIds = array_column($results, 'cate_ID'); if (!empty($cateIds)) { $cateIdsStr = implode(',', array_map('intval', $cateIds)); $sqlIngredientCount = "SELECT cate_ID, COUNT(*) as cnt FROM $tableIngredient WHERE cate_ID IN ($cateIdsStr) GROUP BY cate_ID"; $ingredientCountResults = $zbp->db->Query($sqlIngredientCount); foreach ($ingredientCountResults as $row) { $ingredientCounts[$row['cate_ID']] = (int) $row['cnt']; } } $parentIds = array_unique(array_filter(array_column($results, 'cate_ParentID'))); $parentNames = array(); if (!empty($parentIds)) { $parentIdsStr = implode(',', array_map('intval', $parentIds)); $sqlParent = "SELECT cate_ID, cate_Name FROM $tableCategory WHERE cate_ID IN ($parentIdsStr)"; $parentResults = $zbp->db->Query($sqlParent); foreach ($parentResults as $row) { $parentNames[$row['cate_ID']] = $row['cate_Name']; } } $list = array(); foreach ($results as $row) { $type = 'recipe'; if ($row['cate_ParentID'] >= 1000 || $row['cate_ID'] >= 1000) { $type = 'ingredient'; } elseif ($row['cate_ParentID'] == 11) { $type = 'recipe_main'; } $recipeCount = (int) ($row['cate_Count'] ?? 0); $ingredientCount = isset($ingredientCounts[$row['cate_ID']]) ? $ingredientCounts[$row['cate_ID']] : 0; $parentId = (int) $row['cate_ParentID']; $parentName = isset($parentNames[$parentId]) ? $parentNames[$parentId] : ''; $list[] = array( 'id' => (int) $row['cate_ID'], 'name' => $row['cate_Name'], 'type' => $type, 'recipe_count' => $recipeCount, 'ingredient_count' => $ingredientCount, 'count' => $type === 'ingredient' ? $ingredientCount : $recipeCount, 'parent_id' => $parentId, 'parent_name' => $parentName ); } shuffle($list); return $list; } /** * 获取随机标签 */ function getRandomTags($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableTag = $zbp->db->dbpre . 'tag'; $excludeIds = array(); if (!empty($clientIP)) { $excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'tag'); } $whereClause = "tag_Alias IN ('口味', '做法')"; if (!empty($excludeIds)) { $excludeIdsStr = implode(',', array_map('intval', $excludeIds)); $whereClause .= " AND tag_ID NOT IN ($excludeIdsStr)"; } $sql = "SELECT tag_ID, tag_Name, tag_Alias, tag_Count FROM $tableTag WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); if (empty($results) && !empty($excludeIds)) { ApiCache::clearDiscoverReturnedIds($clientIP, 'tag'); return getRandomTags($limit, $clientIP); } $list = array(); foreach ($results as $row) { $type = 'taste'; if ($row['tag_Alias'] === '做法') { $type = 'cooking'; } $list[] = array( 'id' => (int) $row['tag_ID'], 'name' => $row['tag_Name'], 'type' => $type, 'count' => (int) ($row['tag_Count'] ?? 0) ); } return $list; } /** * 获取营养成分类型 */ function getNutritionTypes($limit, $clientIP = '') { global $zbp; if ($limit <= 0) return array(); $tableNutrition = $zbp->db->dbpre . 'recipe_nutrition'; $excludeNames = array(); if (!empty($clientIP)) { $excludeNames = ApiCache::getDiscoverReturnedIds($clientIP, 'nutrition'); } $whereClause = "name IS NOT NULL AND name != ''"; if (!empty($excludeNames)) { $escapedNames = array_map(function($name) use ($zbp) { return $zbp->db->EscapeString($name); }, $excludeNames); $excludeNamesStr = "'" . implode("','", $escapedNames) . "'"; $whereClause .= " AND name NOT IN ($excludeNamesStr)"; } $sql = "SELECT DISTINCT name, unit FROM $tableNutrition WHERE $whereClause ORDER BY RAND() LIMIT $limit"; $results = $zbp->db->Query($sql); if (empty($results) && !empty($excludeNames)) { ApiCache::clearDiscoverReturnedIds($clientIP, 'nutrition'); return getNutritionTypes($limit, $clientIP); } $list = array(); foreach ($results as $row) { $list[] = array( 'name' => $row['name'], 'unit' => $row['unit'] ?? '' ); } return $list; } /** * 获取用餐时段 */ function getMealTimes($limit) { global $zbp; if ($limit <= 0) return array(); $tablePost = $zbp->db->dbpre . 'post'; $sql = "SELECT log_Intro FROM $tablePost WHERE log_Type = 0 AND log_Status = 0 AND log_Intro IS NOT NULL AND log_Intro != '' LIMIT 500"; $results = $zbp->db->Query($sql); $mealTimes = array(); foreach ($results as $row) { $times = preg_split('/[、,,]/u', $row['log_Intro']); foreach ($times as $time) { $time = trim($time); if (!empty($time) && mb_strlen($time) <= 10) { if (!isset($mealTimes[$time])) { $mealTimes[$time] = 0; } $mealTimes[$time]++; } } } arsort($mealTimes); $list = array(); $id = 1; foreach ($mealTimes as $name => $count) { if ($id > $limit) break; $list[] = array( 'id' => $id++, 'name' => $name, 'count' => $count ); } return $list; }