This commit is contained in:
Developer
2026-04-13 10:00:55 +08:00
parent a59f54567f
commit 5ce8759a18
51 changed files with 4848 additions and 575 deletions

View File

@@ -5,9 +5,10 @@
* @file api_discover.php
* @author AI Assistant
* @date 2026-04-12
* @version 1.0.0
* @version 1.1.0
* @desc 提供随机数据用于首页/发现页瀑布流,支持响应式布局
* @lastUpdate 2026-04-12 初始版本
* 同一客户端多次请求返回不重复数据30分钟后重置
* @lastUpdate 2026-04-12 添加已返回ID排除机制避免重复数据
*/
$startTime = microtime(true);
@@ -47,47 +48,52 @@ $format = ApiResponse::getFormat();
$forceRefresh = isset($params['_refresh']) && $params['_refresh'] === '1';
$clientIP = getClientIP();
$requestInfo = ApiCache::getDiscoverRequestCount($clientIP);
$shouldUseCache = !$forceRefresh && $requestInfo['count'] >= 2 && $requestInfo['data'] !== null;
if ($shouldUseCache) {
header('X-Cache: HIT');
header('X-Request-Count: ' . $requestInfo['count']);
$result = $requestInfo['data'];
$result['meta']['cache_status'] = 'cached';
$result['meta']['request_count'] = $requestInfo['count'];
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
ApiResponse::output($result, $format);
exit;
}
header('X-Cache: MISS');
$counts = calculateCounts($params);
$data = array(
'recipes' => getRandomRecipes($counts['recipe']),
'ingredients' => getRandomIngredients($counts['ingredient']),
'categories' => getRandomCategories($counts['category']),
'tags' => getRandomTags($counts['tag']),
'nutrition_types' => getNutritionTypes($counts['nutrition']),
'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(
'request_count' => $requestInfo['count'] + 1,
'cache_status' => 'fresh',
'counts' => $counts
)
);
ApiCache::incrementDiscoverRequestCount($clientIP, $result);
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
ApiResponse::output($result, $format);
@@ -112,15 +118,15 @@ function getClientIP() {
* 计算各类型数量
*/
function calculateCounts($params) {
$total = isset($params['total']) ? min((int) $params['total'], 100) : 30;
$total = isset($params['total']) ? min((int) $params['total'], 100) : 50;
$ratios = array(
'recipe' => 0.25,
'ingredient' => 0.15,
'category' => 0.15,
'tag' => 0.20,
'nutrition' => 0.15,
'meal_time' => 0.10
'recipe' => 0.75,
'ingredient' => 0.03,
'category' => 0.10,
'tag' => 0.03,
'nutrition' => 0.02,
'meal_time' => 0.02
);
$counts = array();
@@ -130,7 +136,7 @@ function calculateCounts($params) {
foreach ($ratios as $type => $ratio) {
$paramName = $type === 'nutrition' ? 'nutrition' : $type;
if (isset($params[$paramName]) && (int) $params[$paramName] > 0) {
$counts[$type] = min((int) $params[$paramName], 20);
$counts[$type] = min((int) $params[$paramName], 80);
$specifiedTotal += $counts[$type];
$specifiedTypes[] = $type;
}
@@ -159,7 +165,7 @@ function calculateCounts($params) {
/**
* 获取随机菜品
*/
function getRandomRecipes($limit) {
function getRandomRecipes($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
@@ -169,6 +175,17 @@ function getRandomRecipes($limit) {
$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,
@@ -177,12 +194,17 @@ function getRandomRecipes($limit) {
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 p.log_Type = 0 AND p.log_Status = 0
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);
@@ -213,7 +235,7 @@ function getRandomRecipes($limit) {
/**
* 获取随机食材
*/
function getRandomIngredients($limit) {
function getRandomIngredients($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
@@ -221,15 +243,32 @@ function getRandomIngredients($limit) {
$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();
@@ -261,20 +300,72 @@ function getRandomIngredients($limit) {
/**
* 获取随机分类
*/
function getRandomCategories($limit) {
function getRandomCategories($limit, $clientIP = '') {
global $zbp;
if ($limit <= 0) return array();
$tableCategory = $zbp->db->dbpre . 'category';
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$sql = "SELECT cate_ID, cate_Name, cate_Count, cate_ParentID
FROM $tableCategory
WHERE cate_ID > 10
ORDER BY RAND()
LIMIT $limit";
$excludeIds = array();
if (!empty($clientIP)) {
$excludeIds = ApiCache::getDiscoverReturnedIds($clientIP, 'category');
}
$results = $zbp->db->Query($sql);
$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) {
@@ -285,36 +376,62 @@ function getRandomCategories($limit) {
$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,
'count' => (int) ($row['cate_Count'] ?? 0),
'parent_id' => (int) $row['cate_ParentID']
'recipe_count' => $recipeCount,
'ingredient_count' => $ingredientCount,
'count' => $type === 'ingredient' ? $ingredientCount : $recipeCount,
'parent_id' => $parentId,
'parent_name' => $parentName
);
}
shuffle($list);
return $list;
}
/**
* 获取随机标签
*/
function getRandomTags($limit) {
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 tag_Alias IN ('口味', '做法')
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';
@@ -336,21 +453,40 @@ function getRandomTags($limit) {
/**
* 获取营养成分类型
*/
function getNutritionTypes($limit) {
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 name IS NOT NULL AND name != ''
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(