1859 lines
62 KiB
PHP
1859 lines
62 KiB
PHP
<?php
|
||
/**
|
||
* 🍳 菜谱API接口 - 静态接口(只读)
|
||
*
|
||
* 独立API文件,直接访问: /api/api.php?act=xxx
|
||
* 动态接口: /api/api_action.php?act=xxx
|
||
* 管理页面: /api/
|
||
*
|
||
* 性能优化:支持文件缓存,减少数据库查询
|
||
* 格式支持:JSON、Gzip、MessagePack、CBOR
|
||
*/
|
||
|
||
$startTime = microtime(true);
|
||
|
||
require '../zb_system/function/c_system_base.php';
|
||
$zbp->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=300');
|
||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 300) . ' GMT');
|
||
|
||
$act = strtolower(trim($_GET['act'] ?? 'index'));
|
||
$type = strtolower(trim($_GET['type'] ?? 'recipe'));
|
||
$format = ApiResponse::getFormat();
|
||
|
||
$allowedTypes = array('recipe', 'ingredient');
|
||
if (!in_array($type, $allowedTypes)) {
|
||
$type = 'recipe';
|
||
}
|
||
|
||
increment_api_request($act);
|
||
|
||
if (rand(1, 200) === 1) {
|
||
ApiCache::cleanExpired();
|
||
}
|
||
|
||
function increment_api_request($api) {
|
||
$cacheDir = dirname(__FILE__) . '/cache/stats/';
|
||
if (!is_dir($cacheDir)) {
|
||
@mkdir($cacheDir, 0755, true);
|
||
}
|
||
|
||
$file = $cacheDir . 'request_stats.json';
|
||
$data = array(
|
||
'total' => 0,
|
||
'today' => 0,
|
||
'today_date' => date('Y-m-d'),
|
||
'apis' => array(),
|
||
'hourly' => array(),
|
||
'start_date' => date('Y-m-d')
|
||
);
|
||
|
||
if (file_exists($file)) {
|
||
$content = file_get_contents($file);
|
||
$loaded = json_decode($content, true);
|
||
if (is_array($loaded)) {
|
||
$data = $loaded;
|
||
}
|
||
}
|
||
|
||
$today = date('Y-m-d');
|
||
$hour = date('H');
|
||
|
||
if ($data['today_date'] !== $today) {
|
||
$data['today'] = 0;
|
||
$data['today_date'] = $today;
|
||
$data['hourly'] = array();
|
||
}
|
||
|
||
$data['total']++;
|
||
$data['today']++;
|
||
$data['apis'][$api] = isset($data['apis'][$api]) ? $data['apis'][$api] + 1 : 1;
|
||
$data['hourly'][$hour] = isset($data['hourly'][$hour]) ? $data['hourly'][$hour] + 1 : 1;
|
||
|
||
file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE));
|
||
}
|
||
|
||
$result = array();
|
||
|
||
$cacheableActs = array('list', 'detail', 'full', 'ingredients', 'ingredient_detail', 'search', 'categories', 'tags', 'stats', 'query', 'filter', 'unified_list', 'unified_detail', 'unified_search', 'unified_hot');
|
||
|
||
$forceRefresh = isset($_GET['_refresh']) && $_GET['_refresh'] === '1';
|
||
$staleMode = isset($_GET['_stale']) && $_GET['_stale'] === '1';
|
||
|
||
if (in_array($act, $cacheableActs) && !$forceRefresh) {
|
||
$cacheParams = $_GET;
|
||
unset($cacheParams['act']);
|
||
unset($cacheParams['_refresh']);
|
||
unset($cacheParams['_stale']);
|
||
unset($cacheParams['_format']);
|
||
unset($cacheParams['_pretty']);
|
||
|
||
$cachedResult = ApiCache::get($act, $cacheParams);
|
||
if ($cachedResult !== null) {
|
||
header('X-Cache: HIT');
|
||
header('X-Cache-TTL: ' . ApiCache::getTTL($act));
|
||
$cachedResult['_cached'] = true;
|
||
$cachedResult['_cache_age'] = ApiCache::getCacheAge($act, $cacheParams);
|
||
$cachedResult['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
|
||
ApiResponse::output($cachedResult, $format);
|
||
exit;
|
||
}
|
||
|
||
if ($staleMode || isset($_SERVER['HTTP_X_STALE_CACHE'])) {
|
||
$staleResult = ApiCache::getStale($act, $cacheParams);
|
||
if ($staleResult !== null) {
|
||
header('X-Cache: STALE');
|
||
header('X-Cache-Expired: true');
|
||
$staleResult['_cached'] = true;
|
||
$staleResult['_stale'] = true;
|
||
$staleResult['_cache_age'] = ApiCache::getCacheAge($act, $cacheParams);
|
||
$staleResult['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
|
||
ApiResponse::output($staleResult, $format);
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
|
||
header('X-Cache: MISS');
|
||
|
||
switch ($act) {
|
||
case 'list':
|
||
$result = recipe_list();
|
||
break;
|
||
case 'detail':
|
||
$result = recipe_detail();
|
||
break;
|
||
case 'full':
|
||
$result = recipe_full();
|
||
break;
|
||
case 'ingredients':
|
||
$result = ingredient_list();
|
||
break;
|
||
case 'ingredient_detail':
|
||
$result = ingredient_detail();
|
||
break;
|
||
case 'search':
|
||
$result = recipe_search();
|
||
break;
|
||
case 'categories':
|
||
$result = category_list();
|
||
break;
|
||
case 'tags':
|
||
$result = tag_list();
|
||
break;
|
||
case 'stats':
|
||
$result = site_stats();
|
||
break;
|
||
case 'query':
|
||
$result = advanced_query();
|
||
break;
|
||
case 'filter':
|
||
$result = field_filter();
|
||
break;
|
||
case 'unified_list':
|
||
$result = get_unified_list($type);
|
||
break;
|
||
case 'unified_detail':
|
||
$result = get_unified_detail($type);
|
||
break;
|
||
case 'unified_search':
|
||
$result = get_unified_search($type);
|
||
break;
|
||
case 'unified_hot':
|
||
$result = get_unified_hot($type);
|
||
break;
|
||
case 'index':
|
||
default:
|
||
$result = array(
|
||
'code' => 200,
|
||
'message' => '🍳 菜谱API服务正常运行',
|
||
'data' => array(
|
||
'version' => '1.26.0',
|
||
'endpoints' => array(
|
||
'list' => '?act=list',
|
||
'detail' => '?act=detail&id=1',
|
||
'full' => '?act=full&id=1',
|
||
'ingredients' => '?act=ingredients',
|
||
'ingredient_detail' => '?act=ingredient_detail&id=1',
|
||
'search' => '?act=search&keyword=苹果',
|
||
'categories' => '?act=categories',
|
||
'tags' => '?act=tags',
|
||
'stats' => '?act=stats'
|
||
),
|
||
'related_apis' => array(
|
||
'what_to_eat' => 'api_what_to_eat.php',
|
||
'action' => 'api_action.php',
|
||
'feed' => 'api_feed.php',
|
||
'unified' => 'api_unified.php'
|
||
),
|
||
'doc_url' => $zbp->host . 'api/'
|
||
)
|
||
);
|
||
break;
|
||
}
|
||
|
||
if (in_array($act, $cacheableActs) && $result['code'] === 200) {
|
||
$cacheParams = $_GET;
|
||
unset($cacheParams['act']);
|
||
unset($cacheParams['_format']);
|
||
unset($cacheParams['_pretty']);
|
||
ApiCache::set($act, $cacheParams, $result);
|
||
}
|
||
|
||
$result['_cached'] = false;
|
||
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
|
||
|
||
ApiResponse::output($result, $format);
|
||
exit;
|
||
|
||
// ==================== API函数 ====================
|
||
|
||
/**
|
||
* 获取菜谱列表
|
||
*/
|
||
function recipe_list() {
|
||
global $zbp;
|
||
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
$cateId = (int) ($_GET['cate_id'] ?? 0);
|
||
$tagId = (int) ($_GET['tag_id'] ?? 0);
|
||
$search = trim($_GET['search'] ?? '');
|
||
$userId = trim($_GET['user_id'] ?? '');
|
||
$usePreference = isset($_GET['use_preference']) && $_GET['use_preference'] === 'true';
|
||
|
||
if ($limit > 100) $limit = 100;
|
||
if ($limit < 1) $limit = 20;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$preferredTags = array();
|
||
$preferredCategories = array();
|
||
$blockedAllergens = array();
|
||
|
||
if ($usePreference && !empty($userId)) {
|
||
$preference = load_user_preference_data($userId);
|
||
$preferredTags = $preference['preferred_tags'] ?? array();
|
||
$preferredCategories = $preference['preferred_categories'] ?? array();
|
||
$blockedAllergens = $preference['blocked_allergens'] ?? array();
|
||
}
|
||
|
||
if (isset($_GET['preferred_tags'])) {
|
||
$preferredTags = array_map('intval', explode(',', $_GET['preferred_tags']));
|
||
}
|
||
if (isset($_GET['preferred_categories'])) {
|
||
$preferredCategories = array_map('intval', explode(',', $_GET['preferred_categories']));
|
||
}
|
||
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
$offset = ($page - 1) * $limit;
|
||
|
||
$whereSql = "WHERE p.log_Type = 0 AND p.log_Status = 0";
|
||
|
||
if ($cateId > 0) {
|
||
$whereSql .= " AND p.log_CateID = $cateId";
|
||
} elseif (!empty($preferredCategories)) {
|
||
$cateList = implode(',', $preferredCategories);
|
||
$whereSql .= " AND p.log_CateID IN ($cateList)";
|
||
}
|
||
|
||
if ($tagId > 0) {
|
||
$whereSql .= " AND p.log_Tag LIKE '%{$tagId}%'";
|
||
} elseif (!empty($preferredTags)) {
|
||
$tagConditions = array();
|
||
foreach ($preferredTags as $pt) {
|
||
$tagConditions[] = "p.log_Tag LIKE '%{$pt}%'";
|
||
}
|
||
$whereSql .= " AND (" . implode(' OR ', $tagConditions) . ")";
|
||
}
|
||
|
||
if (!empty($search)) {
|
||
$search = htmlspecialchars($search);
|
||
$whereSql .= " AND (p.log_Content LIKE '%$search%' OR p.log_Intro LIKE '%$search%' OR p.log_Title LIKE '%$search%')";
|
||
}
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $tablePost p $whereSql";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$selectFields = "p.log_ID, p.log_Title, p.log_Intro, p.log_CateID, p.log_Tag, p.log_PostTime, p.log_ViewNums, p.log_CommNums, p.log_Meta, p.log_Content, c.cate_Name";
|
||
$sql = "SELECT $selectFields FROM $tablePost p LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID $whereSql ORDER BY p.log_PostTime DESC LIMIT $offset, $limit";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$meta = json_decode($row['log_Meta'] ?? '', true) ?: array();
|
||
|
||
$list[] = array(
|
||
'id' => (int) $row['log_ID'],
|
||
'title' => $row['log_Title'],
|
||
'intro' => $row['log_Intro'] ?? '',
|
||
'category_id' => (int) ($row['log_CateID'] ?? 0),
|
||
'category_name' => $row['cate_Name'] ?? '',
|
||
'tags' => parse_tags($row['log_Tag'] ?? ''),
|
||
'create_time' => strtotime($row['log_PostTime'] ?? 'now'),
|
||
'view_count' => (int) ($row['log_ViewNums'] ?? 0),
|
||
'comment_count' => (int) ($row['log_CommNums'] ?? 0),
|
||
'meta' => $meta,
|
||
'url' => '?id=' . $row['log_ID'],
|
||
'cover' => extract_cover_from_content($row['log_Content'] ?? '')
|
||
);
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'filter' => array(
|
||
'preferred_tags' => $preferredTags,
|
||
'preferred_categories' => $preferredCategories
|
||
)
|
||
)
|
||
);
|
||
}
|
||
|
||
function load_user_preference_data($userId) {
|
||
$cacheDir = dirname(__FILE__) . '/cache/preference/';
|
||
$file = $cacheDir . 'pref_' . md5($userId) . '.json';
|
||
|
||
if (file_exists($file)) {
|
||
$content = file_get_contents($file);
|
||
$data = json_decode($content, true);
|
||
return is_array($data) ? $data : array(
|
||
'preferred_tags' => array(),
|
||
'preferred_categories' => array(),
|
||
'blocked_allergens' => array()
|
||
);
|
||
}
|
||
return array(
|
||
'preferred_tags' => array(),
|
||
'preferred_categories' => array(),
|
||
'blocked_allergens' => array()
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取菜谱详情
|
||
*/
|
||
function recipe_detail() {
|
||
global $zbp;
|
||
|
||
$id = (int) ($_GET['id'] ?? 0);
|
||
if ($id <= 0) {
|
||
return array('code' => 400, 'message' => '❌ 缺少菜谱ID参数');
|
||
}
|
||
|
||
$post = $zbp->GetPostByID($id);
|
||
if (!$post || $post->ID == 0) {
|
||
return array('code' => 404, 'message' => '❌ 菜谱不存在');
|
||
}
|
||
|
||
// 增加浏览量
|
||
if (isset($_GET['viewnums']) && $_GET['viewnums'] === 'true') {
|
||
$post->ViewNums += 1;
|
||
$sql = $zbp->db->sql->Update($zbp->table['Post'], array('log_ViewNums' => $post->ViewNums), array(array('=', 'log_ID', $post->ID)));
|
||
$zbp->db->Update($sql);
|
||
}
|
||
|
||
$meta = json_decode($post->Meta, true) ?: array();
|
||
$ingredients = get_recipe_ingredients($id);
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'id' => $post->ID,
|
||
'title' => $post->Title,
|
||
'content' => $post->Content,
|
||
'intro' => $post->Intro,
|
||
'category_id' => $post->CategoryID,
|
||
'category_name' => $post->Category ? $post->Category->Name : '',
|
||
'tags' => parse_tags($post->Tag),
|
||
'create_time' => strtotime($post->PostTime),
|
||
'update_time' => strtotime($post->UpdateTime),
|
||
'view_count' => $post->ViewNums,
|
||
'comment_count' => $post->CommNums,
|
||
'meta' => $meta,
|
||
'ingredients' => $ingredients,
|
||
'url' => '?' . parse_url($post->Url, PHP_URL_QUERY),
|
||
'cover' => extract_cover($post),
|
||
'author' => $post->Author ? array(
|
||
'id' => $post->Author->ID,
|
||
'name' => $post->Author->Name,
|
||
'avatar' => $post->Author->Avatar
|
||
) : null
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取菜谱完整信息
|
||
* 包含:基本信息、食材详情、营养、统计、标签、分类层级等所有信息
|
||
* @return array 完整菜谱信息
|
||
*/
|
||
function recipe_full() {
|
||
global $zbp;
|
||
|
||
$id = (int) ($_GET['id'] ?? 0);
|
||
if ($id <= 0) {
|
||
return array('code' => 400, 'message' => '缺少菜谱ID参数');
|
||
}
|
||
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
$tableMember = $zbp->db->dbpre . 'member';
|
||
$tablePostStat = $zbp->db->dbpre . 'post_stat';
|
||
$tableRecipeIngredient = $zbp->db->dbpre . 'recipe_ingredient';
|
||
$tableIngredientDetail = $zbp->db->dbpre . 'ingredient_detail';
|
||
$tableRecipeNutrition = $zbp->db->dbpre . 'recipe_nutrition';
|
||
$tableTag = $zbp->db->dbpre . 'tag';
|
||
$tableRecipeIdMap = $zbp->db->dbpre . 'recipe_id_map';
|
||
|
||
$sql = "SELECT
|
||
p.log_ID, p.log_Title, p.log_Content, p.log_Intro, p.log_Tag,
|
||
p.log_CateID, p.log_AuthorID, p.log_PostTime, p.log_UpdateTime,
|
||
p.log_ViewNums, p.log_CommNums, p.log_Meta, p.log_Status,
|
||
c.cate_ID as cate_id, c.cate_Name as cate_name, c.cate_Alias as cate_alias,
|
||
c.cate_ParentID as cate_parent_id,
|
||
m.mem_ID as author_id, m.mem_Name as author_name, m.mem_Alias as author_alias,
|
||
m.mem_Email as author_email, m.mem_HomePage as author_homepage,
|
||
COALESCE(s.like_nums, 0) as like_nums,
|
||
COALESCE(s.recommend_nums, 0) as recommend_nums,
|
||
COALESCE(s.recommend_score, 0) as recommend_score
|
||
FROM $tablePost p
|
||
LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID
|
||
LEFT JOIN $tableMember m ON p.log_AuthorID = m.mem_ID
|
||
LEFT JOIN $tablePostStat s ON p.log_ID = s.log_id
|
||
WHERE p.log_ID = $id AND p.log_Type = 0";
|
||
|
||
$result = $zbp->db->Query($sql);
|
||
|
||
if (empty($result)) {
|
||
return array('code' => 404, 'message' => '菜谱不存在');
|
||
}
|
||
|
||
$row = $result[0];
|
||
|
||
$picId = null;
|
||
$idMapSql = "SELECT old_id FROM $tableRecipeIdMap WHERE new_log_id = $id LIMIT 1";
|
||
$idMapResult = $zbp->db->Query($idMapSql);
|
||
if (!empty($idMapResult)) {
|
||
$picId = (int) $idMapResult[0]['old_id'];
|
||
}
|
||
|
||
$meta = json_decode($row['log_Meta'] ?? '', true) ?: array();
|
||
|
||
$tagIds = parse_tags($row['log_Tag'] ?? '');
|
||
$tags = array();
|
||
if (!empty($tagIds)) {
|
||
$tagIdList = implode(',', array_filter($tagIds, 'is_numeric'));
|
||
if (!empty($tagIdList)) {
|
||
$tagSql = "SELECT tag_ID, tag_Name, tag_Alias, tag_Count FROM $tableTag WHERE tag_ID IN ($tagIdList)";
|
||
$tagResults = $zbp->db->Query($tagSql);
|
||
foreach ($tagResults as $tagRow) {
|
||
$tags[] = array(
|
||
'id' => (int) $tagRow['tag_ID'],
|
||
'name' => $tagRow['tag_Name'],
|
||
'alias' => $tagRow['tag_Alias'] ?? '',
|
||
'count' => (int) ($tagRow['tag_Count'] ?? 0)
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
$ingredientSql = "SELECT
|
||
ri.ingredient_id, ri.log_id, ri.type, ri.name, ri.amount, ri.sort, ri.detail_id,
|
||
id.alias, id.usage_tip, id.introduction, id.nutrition,
|
||
id.guidance, id.effect, id.other, id.allergen, id.allergen_type
|
||
FROM $tableRecipeIngredient ri
|
||
LEFT JOIN $tableIngredientDetail id ON ri.detail_id = id.ingredient_id
|
||
WHERE ri.log_id = $id
|
||
ORDER BY ri.sort ASC";
|
||
$ingredientResults = $zbp->db->Query($ingredientSql);
|
||
|
||
$ingredients = array(
|
||
'main' => array(),
|
||
'auxiliary' => array(),
|
||
'seasoning' => array()
|
||
);
|
||
$allergens = array();
|
||
|
||
foreach ($ingredientResults as $ingRow) {
|
||
$ingType = $ingRow['type'] ?? 'main';
|
||
$ingredientData = array(
|
||
'ingredient_id' => (int) ($ingRow['ingredient_id'] ?? 0),
|
||
'name' => $ingRow['name'],
|
||
'amount' => $ingRow['amount'] ?? '',
|
||
'sort' => (int) ($ingRow['sort'] ?? 0),
|
||
'detail_id' => (int) ($ingRow['detail_id'] ?? 0),
|
||
'detail' => null
|
||
);
|
||
|
||
if (!empty($ingRow['detail_id'])) {
|
||
$ingredientData['detail'] = array(
|
||
'alias' => json_decode($ingRow['alias'] ?? '[]', true),
|
||
'usage_tip' => json_decode($ingRow['usage_tip'] ?? '[]', true),
|
||
'introduction' => $ingRow['introduction'] ?? '',
|
||
'nutrition' => $ingRow['nutrition'] ?? '',
|
||
'guidance' => $ingRow['guidance'] ?? '',
|
||
'effect' => $ingRow['effect'] ?? '',
|
||
'other' => $ingRow['other'] ?? '',
|
||
'allergen' => json_decode($ingRow['allergen'] ?? '[]', true),
|
||
'allergen_type' => json_decode($ingRow['allergen_type'] ?? '[]', true)
|
||
);
|
||
|
||
if (!empty($ingRow['allergen'])) {
|
||
$allergenList = json_decode($ingRow['allergen'], true);
|
||
if (is_array($allergenList)) {
|
||
$allergens = array_merge($allergens, $allergenList);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (isset($ingredients[$ingType])) {
|
||
$ingredients[$ingType][] = $ingredientData;
|
||
} else {
|
||
$ingredients['main'][] = $ingredientData;
|
||
}
|
||
}
|
||
$allergens = array_values(array_unique($allergens));
|
||
|
||
$nutritionSql = "SELECT name, value, unit FROM $tableRecipeNutrition WHERE log_id = $id";
|
||
$nutritionResults = $zbp->db->Query($nutritionSql);
|
||
$nutrition = array();
|
||
foreach ($nutritionResults as $nutRow) {
|
||
$nutrition[] = array(
|
||
'name' => $nutRow['name'],
|
||
'value' => (float) $nutRow['value'],
|
||
'unit' => $nutRow['unit']
|
||
);
|
||
}
|
||
|
||
$categoryHierarchy = array();
|
||
if (!empty($row['cate_id'])) {
|
||
$categoryHierarchy[] = array(
|
||
'id' => (int) $row['cate_id'],
|
||
'name' => $row['cate_name'] ?? '',
|
||
'alias' => $row['cate_alias'] ?? '',
|
||
'level' => 1
|
||
);
|
||
|
||
if (!empty($row['cate_parent_id'])) {
|
||
$parentSql = "SELECT cate_ID, cate_Name, cate_Alias, cate_ParentID FROM $tableCategory WHERE cate_ID = " . (int) $row['cate_parent_id'];
|
||
$parentResult = $zbp->db->Query($parentSql);
|
||
if (!empty($parentResult)) {
|
||
$parent = $parentResult[0];
|
||
$categoryHierarchy[] = array(
|
||
'id' => (int) $parent['cate_ID'],
|
||
'name' => $parent['cate_Name'] ?? '',
|
||
'alias' => $parent['cate_Alias'] ?? '',
|
||
'level' => 2
|
||
);
|
||
|
||
if (!empty($parent['cate_ParentID'])) {
|
||
$grandparentSql = "SELECT cate_ID, cate_Name, cate_Alias FROM $tableCategory WHERE cate_ID = " . (int) $parent['cate_ParentID'];
|
||
$grandparentResult = $zbp->db->Query($grandparentSql);
|
||
if (!empty($grandparentResult)) {
|
||
$grandparent = $grandparentResult[0];
|
||
$categoryHierarchy[] = array(
|
||
'id' => (int) $grandparent['cate_ID'],
|
||
'name' => $grandparent['cate_Name'] ?? '',
|
||
'alias' => $grandparent['cate_Alias'] ?? '',
|
||
'level' => 3
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$cover = '';
|
||
if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $row['log_Content'] ?? '', $matches)) {
|
||
$cover = $matches[1];
|
||
}
|
||
|
||
if (isset($_GET['viewnums']) && $_GET['viewnums'] === 'true') {
|
||
$newViews = ((int) $row['log_ViewNums']) + 1;
|
||
$updateSql = "UPDATE $tablePost SET log_ViewNums = $newViews WHERE log_ID = $id";
|
||
$zbp->db->Query($updateSql);
|
||
$row['log_ViewNums'] = $newViews;
|
||
}
|
||
|
||
$recipeCode = 'CP' . str_pad($id, 6, '0', STR_PAD_LEFT);
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'id' => (int) $row['log_ID'],
|
||
'code' => $recipeCode,
|
||
'pic_id' => $picId,
|
||
'title' => $row['log_Title'],
|
||
'intro' => $row['log_Intro'] ?? '',
|
||
'content' => $row['log_Content'],
|
||
'cover' => $cover,
|
||
'status' => (int) ($row['log_Status'] ?? 0),
|
||
'create_time' => strtotime($row['log_PostTime']),
|
||
'update_time' => strtotime($row['log_UpdateTime']),
|
||
'category' => array(
|
||
'id' => (int) ($row['cate_id'] ?? 0),
|
||
'name' => $row['cate_name'] ?? '',
|
||
'alias' => $row['cate_alias'] ?? '',
|
||
'hierarchy' => array_reverse($categoryHierarchy)
|
||
),
|
||
'author' => array(
|
||
'id' => (int) ($row['author_id'] ?? 0),
|
||
'name' => $row['author_name'] ?? '',
|
||
'alias' => $row['author_alias'] ?? '',
|
||
'email' => $row['author_email'] ?? '',
|
||
'homepage' => $row['author_homepage'] ?? ''
|
||
),
|
||
'tags' => $tags,
|
||
'ingredients' => $ingredients,
|
||
'allergens' => $allergens,
|
||
'nutrition' => $nutrition,
|
||
'statistics' => array(
|
||
'view_count' => (int) ($row['log_ViewNums'] ?? 0),
|
||
'comment_count' => (int) ($row['log_CommNums'] ?? 0),
|
||
'like_count' => (int) ($row['like_nums'] ?? 0),
|
||
'recommend_count' => (int) ($row['recommend_nums'] ?? 0),
|
||
'recommend_score' => (float) ($row['recommend_score'] ?? 0)
|
||
),
|
||
'meta' => array(
|
||
'indices' => $meta['indices'] ?? array(),
|
||
'process' => $meta['process'] ?? '',
|
||
'taste' => $meta['taste'] ?? '',
|
||
'eating_time' => $meta['eating_time'] ?? array()
|
||
)
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取食材列表
|
||
*/
|
||
function ingredient_list() {
|
||
global $zbp;
|
||
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
$cateId = (int) ($_GET['cate_id'] ?? 0);
|
||
$search = trim($_GET['search'] ?? '');
|
||
$author = trim($_GET['author'] ?? '');
|
||
$userId = trim($_GET['user_id'] ?? '');
|
||
$usePreference = isset($_GET['use_preference']) && $_GET['use_preference'] === 'true';
|
||
|
||
if ($limit > 100) $limit = 100;
|
||
if ($limit < 1) $limit = 20;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$blockedAllergens = array();
|
||
if (isset($_GET['blocked_allergens'])) {
|
||
$blockedAllergens = array_filter(explode(',', $_GET['blocked_allergens']));
|
||
} elseif ($usePreference && !empty($userId)) {
|
||
$preference = load_user_preference_data($userId);
|
||
$blockedAllergens = $preference['blocked_allergens'] ?? array();
|
||
}
|
||
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
$offset = ($page - 1) * $limit;
|
||
|
||
$whereClauses = array();
|
||
if (!empty($search)) {
|
||
$search = htmlspecialchars($search);
|
||
$whereClauses[] = "name LIKE '%$search%'";
|
||
}
|
||
if ($cateId > 0) {
|
||
$whereClauses[] = "cate_ID = $cateId";
|
||
}
|
||
if (!empty($author)) {
|
||
$author = htmlspecialchars($author);
|
||
$whereClauses[] = "author = '$author'";
|
||
}
|
||
|
||
$whereSql = empty($whereClauses) ? '' : 'WHERE ' . implode(' AND ', $whereClauses);
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $table $whereSql";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$sql = "SELECT ingredient_id, name, view_count, allergen, allergen_type FROM $table $whereSql ORDER BY ingredient_id ASC LIMIT $offset, $limit";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = array();
|
||
$filtered = 0;
|
||
foreach ($results as $row) {
|
||
$allergenType = json_decode($row['allergen_type'] ?? '[]', true);
|
||
|
||
if (!empty($blockedAllergens) && !empty($allergenType)) {
|
||
$hasBlocked = false;
|
||
foreach ($allergenType as $type) {
|
||
if (in_array($type, $blockedAllergens)) {
|
||
$hasBlocked = true;
|
||
$filtered++;
|
||
break;
|
||
}
|
||
}
|
||
if ($hasBlocked) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
$item = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'name' => $row['name'],
|
||
'view_count' => (int) ($row['view_count'] ?? 0),
|
||
'create_time' => time()
|
||
);
|
||
|
||
$allergen = json_decode($row['allergen'] ?? '[]', true);
|
||
if (!empty($allergen)) {
|
||
$item['allergen'] = $allergen;
|
||
$item['allergen_type'] = $allergenType;
|
||
}
|
||
|
||
$list[] = $item;
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'filtered' => $filtered,
|
||
'blocked_allergens' => $blockedAllergens
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取食材详情
|
||
*/
|
||
function ingredient_detail() {
|
||
global $zbp;
|
||
|
||
$id = (int) ($_GET['id'] ?? 0);
|
||
if ($id <= 0) {
|
||
return array('code' => 400, 'message' => '❌ 缺少食材ID参数');
|
||
}
|
||
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
$sql = "SELECT * FROM $table WHERE ingredient_id = $id LIMIT 1";
|
||
$result = $zbp->db->Query($sql);
|
||
|
||
if (empty($result)) {
|
||
return array('code' => 404, 'message' => '❌ 食材不存在');
|
||
}
|
||
|
||
$row = $result[0];
|
||
|
||
$statTable = $zbp->db->dbpre . 'ingredient_stat';
|
||
$statSql = "SELECT * FROM $statTable WHERE ingredient_id = $id LIMIT 1";
|
||
$statResult = $zbp->db->Query($statSql);
|
||
$likeCount = 0;
|
||
$recommendCount = 0;
|
||
$recommendScore = 0;
|
||
if (!empty($statResult)) {
|
||
$likeCount = (int) ($statResult[0]['like_count'] ?? 0);
|
||
$recommendCount = (int) ($statResult[0]['recommend_count'] ?? 0);
|
||
$recommendScore = (float) ($statResult[0]['recommend_score'] ?? 0);
|
||
}
|
||
|
||
$relatedRecipes = array();
|
||
$riTable = $zbp->db->dbpre . 'recipe_ingredient';
|
||
$recipeSql = "SELECT DISTINCT log_id FROM $riTable WHERE detail_id = $id LIMIT 10";
|
||
$recipeResults = $zbp->db->Query($recipeSql);
|
||
foreach ($recipeResults as $recipeRow) {
|
||
$logId = (int) $recipeRow['log_id'];
|
||
$post = $zbp->GetPostByID($logId);
|
||
if ($post && $post->ID > 0) {
|
||
$relatedRecipes[] = array(
|
||
'id' => $post->ID,
|
||
'title' => $post->Title,
|
||
'url' => '?' . parse_url($post->Url, PHP_URL_QUERY)
|
||
);
|
||
}
|
||
}
|
||
|
||
$allergen = json_decode($row['allergen'] ?? '[]', true);
|
||
$allergenType = json_decode($row['allergen_type'] ?? '[]', true);
|
||
|
||
$data = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'name' => $row['name'],
|
||
'alias' => json_decode($row['alias'] ?? '[]', true),
|
||
'usage_tip' => json_decode($row['usage_tip'] ?? '[]', true),
|
||
'introduction' => $row['introduction'] ?? '',
|
||
'nutrition' => $row['nutrition'] ?? '',
|
||
'guidance' => $row['guidance'] ?? '',
|
||
'effect' => $row['effect'] ?? '',
|
||
'other' => $row['other'] ?? '',
|
||
'nutrients' => json_decode($row['nutrients'] ?? '[]', true),
|
||
'view_count' => (int) ($row['view_count'] ?? 0),
|
||
'like_count' => $likeCount,
|
||
'recommend_count' => $recommendCount,
|
||
'recommend_score' => $recommendScore,
|
||
'author' => $row['author'] ?? '系统',
|
||
'cate_id' => (int) ($row['cate_ID'] ?? 0),
|
||
'create_time' => (int) ($row['create_time'] ?? 0),
|
||
'update_time' => (int) ($row['update_time'] ?? 0),
|
||
'related_recipes' => $relatedRecipes
|
||
);
|
||
|
||
if (!empty($allergen)) {
|
||
$data['allergen'] = $allergen;
|
||
$data['allergen_type'] = $allergenType;
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => $data
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 搜索功能
|
||
*/
|
||
function recipe_search() {
|
||
global $zbp;
|
||
|
||
$keyword = trim($_GET['keyword'] ?? '');
|
||
$type = $_GET['type'] ?? 'all';
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
|
||
if (empty($keyword)) {
|
||
return array('code' => 400, 'message' => '❌ 请输入搜索关键词');
|
||
}
|
||
|
||
if ($limit > 50) $limit = 50;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$result = array('recipes' => array(), 'ingredients' => array());
|
||
$offset = ($page - 1) * $limit;
|
||
$keyword = htmlspecialchars($keyword);
|
||
|
||
if ($type == 'all' || $type == 'recipe') {
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$searchSql = "SELECT * FROM $tablePost WHERE log_Type = 0 AND log_Status = 0 AND (log_Content LIKE '%$keyword%' OR log_Intro LIKE '%$keyword%' OR log_Title LIKE '%$keyword%') ORDER BY log_ViewNums DESC LIMIT $offset, $limit";
|
||
$posts = $zbp->db->Query($searchSql);
|
||
|
||
foreach ($posts as $row) {
|
||
$meta = json_decode($row['log_Meta'] ?? '', true) ?: array();
|
||
$categoryId = (int) ($row['log_CateID'] ?? 0);
|
||
$categoryName = '';
|
||
$cate = $zbp->GetCategoryByID($categoryId);
|
||
if ($cate) {
|
||
$categoryName = $cate->Name;
|
||
}
|
||
$result['recipes'][] = array(
|
||
'id' => (int) $row['log_ID'],
|
||
'title' => $row['log_Title'],
|
||
'intro' => $row['log_Intro'] ?? '',
|
||
'category_name' => $categoryName,
|
||
'view_count' => (int) ($row['log_ViewNums'] ?? 0),
|
||
'url' => '?id=' . $row['log_ID'],
|
||
'cover' => extract_cover_from_content($row['log_Content'] ?? ''),
|
||
'meta' => $meta
|
||
);
|
||
}
|
||
}
|
||
|
||
if ($type == 'all' || $type == 'ingredient') {
|
||
$table = $zbp->db->dbpre . 'recipe_ingredient';
|
||
$ingredientSql = "SELECT DISTINCT name, ingredient_id FROM $table WHERE name LIKE '%$keyword%' ORDER BY ingredient_id ASC LIMIT $offset, $limit";
|
||
$ingredients = $zbp->db->Query($ingredientSql);
|
||
|
||
foreach ($ingredients as $row) {
|
||
$result['ingredients'][] = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'name' => $row['name']
|
||
);
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'keyword' => $keyword,
|
||
'type' => $type,
|
||
'page' => $page,
|
||
'result' => $result
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取分类列表
|
||
*/
|
||
function category_list() {
|
||
global $zbp;
|
||
|
||
$type = $_GET['type'] ?? 'recipe';
|
||
$list = array();
|
||
|
||
if ($type == 'recipe') {
|
||
foreach ($zbp->categories as $cate) {
|
||
if ($cate->ParentID == 0) {
|
||
$children = array();
|
||
foreach ($cate->ChildrenCategories as $child) {
|
||
$children[] = array(
|
||
'id' => $child->ID,
|
||
'name' => $child->Name,
|
||
'alias' => $child->Alias,
|
||
'count' => $child->Count
|
||
);
|
||
}
|
||
$list[] = array(
|
||
'id' => $cate->ID,
|
||
'name' => $cate->Name,
|
||
'alias' => $cate->Alias,
|
||
'count' => $cate->AllCount,
|
||
'children' => $children
|
||
);
|
||
}
|
||
}
|
||
} else {
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
$sql = "SELECT cate_ID, COUNT(*) as count FROM $table WHERE cate_ID > 0 GROUP BY cate_ID";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
foreach ($results as $row) {
|
||
$cate = $zbp->GetCategoryByID($row['cate_ID']);
|
||
if ($cate) {
|
||
$list[] = array(
|
||
'id' => $cate->ID,
|
||
'name' => $cate->Name,
|
||
'alias' => $cate->Alias,
|
||
'count' => (int) $row['count']
|
||
);
|
||
}
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => $list
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取标签列表
|
||
*/
|
||
function tag_list() {
|
||
global $zbp;
|
||
|
||
$limit = (int) ($_GET['limit'] ?? 50);
|
||
$list = array();
|
||
|
||
$tags = $zbp->GetTagList('*', array(array('>', 'tag_Count', 0)), array('tag_Count' => 'DESC'), $limit);
|
||
|
||
foreach ($tags as $tag) {
|
||
$list[] = array(
|
||
'id' => $tag->ID,
|
||
'name' => $tag->Name,
|
||
'count' => $tag->Count,
|
||
'url' => '?' . parse_url($tag->Url, PHP_URL_QUERY)
|
||
);
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => $list
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取统计数据
|
||
*/
|
||
function site_stats() {
|
||
global $zbp;
|
||
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableRecipeIngredient = $zbp->db->dbpre . 'recipe_ingredient';
|
||
$tableTag = $zbp->db->dbpre . 'tag';
|
||
$tableIngredientDetail = $zbp->db->dbpre . 'ingredient_detail';
|
||
|
||
$recipeCountResult = $zbp->db->Query("SELECT COUNT(*) as total FROM $tablePost WHERE log_Type = 0 AND log_Status = 0");
|
||
$recipeCount = (int) ($recipeCountResult[0]['total'] ?? 0);
|
||
|
||
$ingredientCountResult = $zbp->db->Query("SELECT COUNT(*) as total FROM $tableIngredientDetail");
|
||
$ingredientCount = (int) ($ingredientCountResult[0]['total'] ?? 0);
|
||
|
||
$relationCountResult = $zbp->db->Query("SELECT COUNT(*) as total FROM $tableRecipeIngredient");
|
||
$relationCount = (int) ($relationCountResult[0]['total'] ?? 0);
|
||
|
||
$categoryCount = count($zbp->categories);
|
||
|
||
$tagCountResult = $zbp->db->Query("SELECT COUNT(*) as total FROM $tableTag");
|
||
$tagCount = (int) ($tagCountResult[0]['total'] ?? 0);
|
||
|
||
$recipeViewsResult = $zbp->db->Query("SELECT SUM(log_ViewNums) as total FROM $tablePost WHERE log_Type = 0");
|
||
$recipeViews = (int) ($recipeViewsResult[0]['total'] ?? 0);
|
||
|
||
$ingredientViewsResult = $zbp->db->Query("SELECT SUM(view_count) as total FROM $tableIngredientDetail");
|
||
$ingredientViews = (int) ($ingredientViewsResult[0]['total'] ?? 0);
|
||
$totalViews = $recipeViews + $ingredientViews;
|
||
|
||
$hotRecipes = array();
|
||
$hotRecipesSql = "SELECT log_ID, log_Title, log_ViewNums FROM $tablePost WHERE log_Type = 0 AND log_Status = 0 ORDER BY log_ViewNums DESC LIMIT 5";
|
||
$hotRecipesResults = $zbp->db->Query($hotRecipesSql);
|
||
foreach ($hotRecipesResults as $row) {
|
||
$hotRecipes[] = array(
|
||
'id' => (int) $row['log_ID'],
|
||
'title' => $row['log_Title'],
|
||
'view_count' => (int) $row['log_ViewNums'],
|
||
'url' => '?id=' . $row['log_ID']
|
||
);
|
||
}
|
||
|
||
$hotIngredients = array();
|
||
$hotIngredientsSql = "SELECT ingredient_id, name, view_count FROM $tableIngredientDetail ORDER BY view_count DESC LIMIT 5";
|
||
$ingredients = $zbp->db->Query($hotIngredientsSql);
|
||
foreach ($ingredients as $row) {
|
||
$hotIngredients[] = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'name' => $row['name'],
|
||
'view_count' => (int) ($row['view_count'] ?? 0)
|
||
);
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'recipe_count' => $recipeCount,
|
||
'ingredient_count' => $ingredientCount,
|
||
'relation_count' => $relationCount,
|
||
'category_count' => $categoryCount,
|
||
'tag_count' => $tagCount,
|
||
'total_views' => $totalViews,
|
||
'recipe_views' => $recipeViews,
|
||
'ingredient_views' => $ingredientViews,
|
||
'hot_recipes' => $hotRecipes,
|
||
'hot_ingredients' => $hotIngredients
|
||
)
|
||
);
|
||
}
|
||
|
||
// ==================== 辅助函数 ====================
|
||
|
||
function parse_tags($tagString) {
|
||
global $zbp;
|
||
$tags = array();
|
||
if (preg_match_all('/\{(\d+)\}/', $tagString, $matches)) {
|
||
foreach ($matches[1] as $tagId) {
|
||
$tag = $zbp->GetTagByID($tagId);
|
||
if ($tag && $tag->ID > 0) {
|
||
$tags[] = array('id' => $tag->ID, 'name' => $tag->Name);
|
||
}
|
||
}
|
||
}
|
||
return $tags;
|
||
}
|
||
|
||
function get_recipe_ingredients($recipeId) {
|
||
global $zbp;
|
||
$table = $zbp->db->dbpre . 'recipe_ingredient';
|
||
$sql = "SELECT * FROM $table WHERE log_id = $recipeId";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$ingredients = array();
|
||
foreach ($results as $row) {
|
||
$ingredients[] = array(
|
||
'name' => $row['name'],
|
||
'amount' => $row['amount'] ?? '',
|
||
'unit' => $row['unit'] ?? '',
|
||
'type' => $row['type'] ?? '',
|
||
'detail_id' => (int) ($row['detail_id'] ?? 0)
|
||
);
|
||
}
|
||
return $ingredients;
|
||
}
|
||
|
||
function extract_cover($post) {
|
||
$content = '';
|
||
if (is_object($post)) {
|
||
$content = $post->Content ?? '';
|
||
}
|
||
if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $matches)) {
|
||
return $matches[1];
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function extract_cover_from_content($content) {
|
||
if (preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i', $content, $matches)) {
|
||
return $matches[1];
|
||
}
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* 高级查询 - 精确查询和模糊查询
|
||
*
|
||
* 参数:
|
||
* - module: recipe(菜谱) / ingredient(食材) / category(分类) / tag(标签)
|
||
* - field: 查询字段名
|
||
* - value: 查询值
|
||
* - operator: eq(等于) / like(模糊) / gt(大于) / lt(小于) / gte(大于等于) / lte(小于等于) / in(包含)
|
||
* - fields: 返回字段(逗号分隔),如 id,title,views
|
||
* - page: 页码
|
||
* - limit: 每页数量
|
||
* - order: 排序字段
|
||
* - sort: asc/desc
|
||
*
|
||
* 示例:
|
||
* - ?act=query&module=recipe&field=process&value=炒
|
||
* - ?act=query&module=recipe&field=title&value=鸡蛋&operator=like
|
||
* - ?act=query&module=ingredient&field=name&value=番茄&operator=like&fields=id,name
|
||
*/
|
||
function advanced_query() {
|
||
global $zbp;
|
||
|
||
$module = strtolower(trim($_GET['module'] ?? 'recipe'));
|
||
$field = trim($_GET['field'] ?? '');
|
||
$value = $_GET['value'] ?? '';
|
||
$operator = strtolower(trim($_GET['operator'] ?? 'eq'));
|
||
$fields = trim($_GET['fields'] ?? '');
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
$order = trim($_GET['order'] ?? '');
|
||
$sort = strtolower(trim($_GET['sort'] ?? 'desc'));
|
||
|
||
if ($limit > 100) $limit = 100;
|
||
if ($limit < 1) $limit = 20;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$allowedModules = ['recipe', 'ingredient', 'category', 'tag'];
|
||
if (!in_array($module, $allowedModules)) {
|
||
return array('code' => 400, 'message' => '❌ module参数必须是: ' . implode(', ', $allowedModules));
|
||
}
|
||
|
||
if (empty($field)) {
|
||
return array('code' => 400, 'message' => '❌ 缺少field参数');
|
||
}
|
||
|
||
$allowedOperators = ['eq', 'like', 'gt', 'lt', 'gte', 'lte', 'in', 'neq'];
|
||
if (!in_array($operator, $allowedOperators)) {
|
||
return array('code' => 400, 'message' => '❌ operator参数必须是: ' . implode(', ', $allowedOperators));
|
||
}
|
||
|
||
$tableConfig = get_table_config($module);
|
||
if (!$tableConfig) {
|
||
return array('code' => 400, 'message' => '❌ 不支持的模块');
|
||
}
|
||
|
||
$table = $tableConfig['table'];
|
||
$primaryKey = $tableConfig['primary_key'];
|
||
$allowedFields = $tableConfig['fields'];
|
||
|
||
if (!in_array($field, $allowedFields)) {
|
||
return array('code' => 400, 'message' => '❌ 不允许查询的字段: ' . $field . ',允许的字段: ' . implode(', ', $allowedFields));
|
||
}
|
||
|
||
$selectFields = '*';
|
||
if (!empty($fields)) {
|
||
$fieldArray = array_map('trim', explode(',', $fields));
|
||
$validFields = array_intersect($fieldArray, $allowedFields);
|
||
if (!empty($validFields)) {
|
||
if (!in_array($primaryKey, $validFields)) {
|
||
array_unshift($validFields, $primaryKey);
|
||
}
|
||
$selectFields = implode(', ', $validFields);
|
||
}
|
||
}
|
||
|
||
$whereClause = build_where_clause($field, $value, $operator);
|
||
|
||
$offset = ($page - 1) * $limit;
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $table WHERE $whereClause";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$orderClause = '';
|
||
if (!empty($order) && in_array($order, $allowedFields)) {
|
||
$sortDir = ($sort === 'asc') ? 'ASC' : 'DESC';
|
||
$orderClause = "ORDER BY $order $sortDir";
|
||
} else {
|
||
$orderClause = $tableConfig['default_order'];
|
||
}
|
||
|
||
$sql = "SELECT $selectFields FROM $table WHERE $whereClause $orderClause LIMIT $offset, $limit";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = format_results($results, $module, $fields);
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'module' => $module,
|
||
'query' => array(
|
||
'field' => $field,
|
||
'value' => $value,
|
||
'operator' => $operator
|
||
),
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'total_pages' => ceil($total / $limit)
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 字段筛选 - 获取指定字段的所有值
|
||
*
|
||
* 参数:
|
||
* - module: recipe(菜谱) / ingredient(食材)
|
||
* - field: 要筛选的字段
|
||
* - distinct: 是否去重(默认true)
|
||
*
|
||
* 示例:
|
||
* - ?act=filter&module=recipe&field=process
|
||
* - ?act=filter&module=recipe&field=taste
|
||
*/
|
||
function field_filter() {
|
||
global $zbp;
|
||
|
||
$module = strtolower(trim($_GET['module'] ?? 'recipe'));
|
||
$field = trim($_GET['field'] ?? '');
|
||
$distinct = ($_GET['distinct'] ?? 'true') === 'true';
|
||
|
||
$allowedModules = ['recipe', 'ingredient'];
|
||
if (!in_array($module, $allowedModules)) {
|
||
return array('code' => 400, 'message' => '❌ module参数必须是: ' . implode(', ', $allowedModules));
|
||
}
|
||
|
||
if (empty($field)) {
|
||
return array('code' => 400, 'message' => '❌ 缺少field参数');
|
||
}
|
||
|
||
$tableConfig = get_table_config($module);
|
||
if (!$tableConfig) {
|
||
return array('code' => 400, 'message' => '❌ 不支持的模块');
|
||
}
|
||
|
||
$table = $tableConfig['table'];
|
||
$allowedFields = $tableConfig['fields'];
|
||
|
||
if (!in_array($field, $allowedFields)) {
|
||
return array('code' => 400, 'message' => '❌ 不允许筛选的字段: ' . $field);
|
||
}
|
||
|
||
if ($distinct) {
|
||
$sql = "SELECT DISTINCT $field FROM $table WHERE $field IS NOT NULL AND $field != '' ORDER BY $field ASC";
|
||
} else {
|
||
$sql = "SELECT $field, COUNT(*) as count FROM $table WHERE $field IS NOT NULL AND $field != '' GROUP BY $field ORDER BY count DESC LIMIT 100";
|
||
}
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
if ($distinct) {
|
||
$list[] = $row[$field];
|
||
} else {
|
||
$list[] = array(
|
||
'value' => $row[$field],
|
||
'count' => (int) $row['count']
|
||
);
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'module' => $module,
|
||
'field' => $field,
|
||
'distinct' => $distinct,
|
||
'list' => $list,
|
||
'total' => count($list)
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 获取表配置
|
||
*/
|
||
function get_table_config($module) {
|
||
global $zbp;
|
||
|
||
$configs = array(
|
||
'recipe' => array(
|
||
'table' => $zbp->db->dbpre . 'post',
|
||
'primary_key' => 'log_ID',
|
||
'fields' => ['log_ID', 'log_Title', 'log_CateID', 'log_AuthorID', 'log_Tag', 'log_Status', 'log_Type', 'log_IsTop', 'log_Intro', 'log_Content', 'log_CreateTime', 'log_PostTime', 'log_UpdateTime', 'log_CommNums', 'log_ViewNums'],
|
||
'default_order' => 'ORDER BY log_PostTime DESC'
|
||
),
|
||
'ingredient' => array(
|
||
'table' => $zbp->db->dbpre . 'ingredient_detail',
|
||
'primary_key' => 'ingredient_id',
|
||
'fields' => ['ingredient_id', 'name', 'alias', 'usage_tip', 'introduction', 'nutrition', 'guidance', 'effect', 'other', 'nutrients', 'create_time', 'update_time', 'author', 'view_count', 'cate_ID', 'allergen', 'allergen_type'],
|
||
'default_order' => 'ORDER BY ingredient_id ASC'
|
||
),
|
||
'category' => array(
|
||
'table' => $zbp->db->dbpre . 'category',
|
||
'primary_key' => 'cate_ID',
|
||
'fields' => ['cate_ID', 'cate_Name', 'cate_Order', 'cate_Type', 'cate_Count', 'cate_Alias', 'cate_ParentID', 'cate_RootID'],
|
||
'default_order' => 'ORDER BY cate_Order ASC'
|
||
),
|
||
'tag' => array(
|
||
'table' => $zbp->db->dbpre . 'tag',
|
||
'primary_key' => 'tag_ID',
|
||
'fields' => ['tag_ID', 'tag_Name', 'tag_Order', 'tag_Type', 'tag_Count', 'tag_Alias'],
|
||
'default_order' => 'ORDER BY tag_Count DESC'
|
||
)
|
||
);
|
||
|
||
return $configs[$module] ?? null;
|
||
}
|
||
|
||
/**
|
||
* 构建WHERE子句
|
||
*/
|
||
function build_where_clause($field, $value, $operator) {
|
||
$value = addslashes($value);
|
||
|
||
switch ($operator) {
|
||
case 'eq':
|
||
return "$field = '$value'";
|
||
case 'neq':
|
||
return "$field != '$value'";
|
||
case 'like':
|
||
return "$field LIKE '%$value%'";
|
||
case 'gt':
|
||
return "$field > '$value'";
|
||
case 'lt':
|
||
return "$field < '$value'";
|
||
case 'gte':
|
||
return "$field >= '$value'";
|
||
case 'lte':
|
||
return "$field <= '$value'";
|
||
case 'in':
|
||
$values = array_map(function($v) { return "'" . addslashes(trim($v)) . "'"; }, explode(',', $value));
|
||
return "$field IN (" . implode(',', $values) . ")";
|
||
default:
|
||
return "$field = '$value'";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 格式化查询结果
|
||
*/
|
||
function format_results($results, $module, $fields) {
|
||
global $zbp;
|
||
$list = array();
|
||
|
||
foreach ($results as $row) {
|
||
$item = array();
|
||
|
||
if ($module === 'recipe') {
|
||
$item = array(
|
||
'id' => (int) $row['log_ID'],
|
||
'title' => $row['log_Title'] ?? '',
|
||
'intro' => $row['log_Intro'] ?? '',
|
||
'category_id' => (int) ($row['log_CateID'] ?? 0),
|
||
'author_id' => (int) ($row['log_AuthorID'] ?? 0),
|
||
'view_count' => (int) ($row['log_ViewNums'] ?? 0),
|
||
'comment_count' => (int) ($row['log_CommNums'] ?? 0),
|
||
'post_time' => (int) ($row['log_PostTime'] ?? 0),
|
||
'status' => (int) ($row['log_Status'] ?? 0),
|
||
'url' => '?act=detail&id=' . $row['log_ID']
|
||
);
|
||
|
||
$cate = $zbp->GetCategoryByID($item['category_id']);
|
||
$item['category_name'] = $cate ? $cate->Name : '';
|
||
|
||
} elseif ($module === 'ingredient') {
|
||
$item = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'name' => $row['name'] ?? '',
|
||
'view_count' => (int) ($row['view_count'] ?? 0),
|
||
'author' => $row['author'] ?? '',
|
||
'cate_id' => (int) ($row['cate_ID'] ?? 0),
|
||
'create_time' => (int) ($row['create_time'] ?? 0),
|
||
'url' => '?act=ingredient_detail&id=' . $row['ingredient_id']
|
||
);
|
||
|
||
} elseif ($module === 'category') {
|
||
$item = array(
|
||
'id' => (int) $row['cate_ID'],
|
||
'name' => $row['cate_Name'] ?? '',
|
||
'alias' => $row['cate_Alias'] ?? '',
|
||
'parent_id' => (int) ($row['cate_ParentID'] ?? 0),
|
||
'count' => (int) ($row['cate_Count'] ?? 0),
|
||
'order' => (int) ($row['cate_Order'] ?? 0)
|
||
);
|
||
|
||
} elseif ($module === 'tag') {
|
||
$item = array(
|
||
'id' => (int) $row['tag_ID'],
|
||
'name' => $row['tag_Name'] ?? '',
|
||
'alias' => $row['tag_Alias'] ?? '',
|
||
'count' => (int) ($row['tag_Count'] ?? 0),
|
||
'order' => (int) ($row['tag_Order'] ?? 0)
|
||
);
|
||
}
|
||
|
||
$list[] = $item;
|
||
}
|
||
|
||
return $list;
|
||
}
|
||
|
||
// ==================== 统一格式输出函数 ====================
|
||
|
||
/**
|
||
* 统一列表输出
|
||
* @param string $type recipe/ingredient
|
||
* @return array
|
||
*/
|
||
function get_unified_list($type) {
|
||
global $zbp;
|
||
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
$cateId = (int) ($_GET['cate_id'] ?? 0);
|
||
$search = trim($_GET['search'] ?? '');
|
||
|
||
if ($limit > 100) $limit = 100;
|
||
if ($limit < 1) $limit = 20;
|
||
if ($page < 1) $page = 1;
|
||
|
||
$offset = ($page - 1) * $limit;
|
||
|
||
if ($type === 'recipe') {
|
||
return get_recipe_list_unified($page, $limit, $offset, $cateId, $search);
|
||
} else {
|
||
return get_ingredient_list_unified($page, $limit, $offset, $cateId, $search);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 食谱列表(统一格式)
|
||
*/
|
||
function get_recipe_list_unified($page, $limit, $offset, $cateId, $search) {
|
||
global $zbp;
|
||
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
$tablePostStat = $zbp->db->dbpre . 'post_stat';
|
||
|
||
$whereSql = "WHERE p.log_Type = 0 AND p.log_Status = 0";
|
||
|
||
if ($cateId > 0) {
|
||
$whereSql .= " AND p.log_CateID = $cateId";
|
||
}
|
||
|
||
if (!empty($search)) {
|
||
$search = $zbp->db->EscapeString($search);
|
||
$whereSql .= " AND (p.log_Title LIKE '%$search%' OR p.log_Intro LIKE '%$search%')";
|
||
}
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $tablePost p $whereSql";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$sql = "SELECT p.log_ID, p.log_Title, p.log_Intro, p.log_CateID, p.log_PostTime,
|
||
p.log_ViewNums, p.log_Tag, c.cate_Name,
|
||
COALESCE(s.like_nums, 0) as like_nums,
|
||
COALESCE(s.recommend_nums, 0) as recommend_nums
|
||
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
|
||
$whereSql
|
||
ORDER BY p.log_PostTime DESC
|
||
LIMIT $offset, $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'recipe');
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'type' => 'recipe',
|
||
'type_name' => '食谱',
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'has_more' => ($page * $limit) < $total
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 食材列表(统一格式)
|
||
*/
|
||
function get_ingredient_list_unified($page, $limit, $offset, $cateId, $search) {
|
||
global $zbp;
|
||
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
|
||
$whereClauses = array();
|
||
|
||
if ($cateId > 0) {
|
||
$whereClauses[] = "cate_ID = $cateId";
|
||
}
|
||
|
||
if (!empty($search)) {
|
||
$search = $zbp->db->EscapeString($search);
|
||
$whereClauses[] = "name LIKE '%$search%'";
|
||
}
|
||
|
||
$whereSql = empty($whereClauses) ? '' : 'WHERE ' . implode(' AND ', $whereClauses);
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $table $whereSql";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$sql = "SELECT ingredient_id, name, view_count, allergen, allergen_type,
|
||
introduction, cate_ID, create_time
|
||
FROM $table
|
||
$whereSql
|
||
ORDER BY ingredient_id ASC
|
||
LIMIT $offset, $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'ingredient');
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'type' => 'ingredient',
|
||
'type_name' => '食材',
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'has_more' => ($page * $limit) < $total
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 统一详情输出
|
||
*/
|
||
function get_unified_detail($type) {
|
||
global $zbp;
|
||
|
||
$id = (int) ($_GET['id'] ?? 0);
|
||
|
||
if ($id <= 0) {
|
||
return array('code' => 400, 'message' => '缺少ID参数');
|
||
}
|
||
|
||
if ($type === 'recipe') {
|
||
return get_recipe_detail_unified($id);
|
||
} else {
|
||
return get_ingredient_detail_unified($id);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 食谱详情(统一格式)
|
||
*/
|
||
function get_recipe_detail_unified($id) {
|
||
global $zbp;
|
||
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
$tablePostStat = $zbp->db->dbpre . 'post_stat';
|
||
$tableRecipeIngredient = $zbp->db->dbpre . 'recipe_ingredient';
|
||
|
||
$sql = "SELECT p.log_ID, p.log_Title, p.log_Content, p.log_Intro, p.log_CateID,
|
||
p.log_PostTime, p.log_ViewNums, p.log_Tag, c.cate_Name,
|
||
COALESCE(s.like_nums, 0) as like_nums,
|
||
COALESCE(s.recommend_nums, 0) as recommend_nums
|
||
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
|
||
WHERE p.log_ID = $id AND p.log_Type = 0 AND p.log_Status = 0
|
||
LIMIT 1";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
if (empty($results)) {
|
||
return array('code' => 404, 'message' => '食谱不存在');
|
||
}
|
||
|
||
$row = $results[0];
|
||
$item = format_unified_item($row, 'recipe', true);
|
||
|
||
$ingredientSql = "SELECT name, amount, unit FROM $tableRecipeIngredient WHERE log_id = $id";
|
||
$ingredientResults = $zbp->db->Query($ingredientSql);
|
||
$item['ingredients'] = array();
|
||
foreach ($ingredientResults as $ing) {
|
||
$item['ingredients'][] = array(
|
||
'name' => $ing['name'],
|
||
'amount' => $ing['amount'],
|
||
'unit' => $ing['unit']
|
||
);
|
||
}
|
||
|
||
return array('code' => 200, 'message' => 'success', 'data' => $item);
|
||
}
|
||
|
||
/**
|
||
* 食材详情(统一格式)
|
||
*/
|
||
function get_ingredient_detail_unified($id) {
|
||
global $zbp;
|
||
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
$tableStat = $zbp->db->dbpre . 'ingredient_stat';
|
||
|
||
$sql = "SELECT * FROM $table WHERE ingredient_id = $id LIMIT 1";
|
||
$results = $zbp->db->Query($sql);
|
||
|
||
if (empty($results)) {
|
||
return array('code' => 404, 'message' => '食材不存在');
|
||
}
|
||
|
||
$row = $results[0];
|
||
$item = format_unified_item($row, 'ingredient', true);
|
||
|
||
$statSql = "SELECT * FROM $tableStat WHERE ingredient_id = $id LIMIT 1";
|
||
$statResults = $zbp->db->Query($statSql);
|
||
if (!empty($statResults)) {
|
||
$stat = $statResults[0];
|
||
$item['statistics']['like_count'] = (int) ($stat['like_nums'] ?? 0);
|
||
$item['statistics']['recommend_count'] = (int) ($stat['recommend_nums'] ?? 0);
|
||
}
|
||
|
||
return array('code' => 200, 'message' => 'success', 'data' => $item);
|
||
}
|
||
|
||
/**
|
||
* 统一搜索
|
||
*/
|
||
function get_unified_search($type) {
|
||
global $zbp;
|
||
|
||
$keyword = trim($_GET['keyword'] ?? '');
|
||
$page = (int) ($_GET['page'] ?? 1);
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
|
||
if (empty($keyword)) {
|
||
return array('code' => 400, 'message' => '缺少搜索关键词');
|
||
}
|
||
|
||
if ($limit > 100) $limit = 100;
|
||
$offset = ($page - 1) * $limit;
|
||
|
||
$keyword = $zbp->db->EscapeString($keyword);
|
||
|
||
if ($type === 'recipe') {
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $tablePost
|
||
WHERE log_Type = 0 AND log_Status = 0
|
||
AND (log_Title LIKE '%$keyword%' OR log_Intro LIKE '%$keyword%')";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$sql = "SELECT p.log_ID, p.log_Title, p.log_Intro, p.log_CateID, p.log_PostTime,
|
||
p.log_ViewNums, c.cate_Name
|
||
FROM $tablePost p
|
||
LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID
|
||
WHERE p.log_Type = 0 AND p.log_Status = 0
|
||
AND (p.log_Title LIKE '%$keyword%' OR p.log_Intro LIKE '%$keyword%')
|
||
ORDER BY p.log_PostTime DESC
|
||
LIMIT $offset, $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'recipe');
|
||
}
|
||
} else {
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
|
||
$countSql = "SELECT COUNT(*) as total FROM $table WHERE name LIKE '%$keyword%'";
|
||
$countResult = $zbp->db->Query($countSql);
|
||
$total = (int) ($countResult[0]['total'] ?? 0);
|
||
|
||
$sql = "SELECT ingredient_id, name, view_count, allergen, allergen_type
|
||
FROM $table
|
||
WHERE name LIKE '%$keyword%'
|
||
ORDER BY ingredient_id ASC
|
||
LIMIT $offset, $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'ingredient');
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'type' => $type,
|
||
'type_name' => $type === 'recipe' ? '食谱' : '食材',
|
||
'keyword' => $keyword,
|
||
'list' => $list,
|
||
'page' => $page,
|
||
'limit' => $limit,
|
||
'total' => $total,
|
||
'has_more' => ($page * $limit) < $total
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 统一热门
|
||
*/
|
||
function get_unified_hot($type) {
|
||
global $zbp;
|
||
|
||
$limit = (int) ($_GET['limit'] ?? 20);
|
||
if ($limit > 50) $limit = 50;
|
||
|
||
if ($type === 'recipe') {
|
||
$tablePost = $zbp->db->dbpre . 'post';
|
||
$tableCategory = $zbp->db->dbpre . 'category';
|
||
|
||
$sql = "SELECT p.log_ID, p.log_Title, p.log_Intro, p.log_CateID, p.log_PostTime,
|
||
p.log_ViewNums, c.cate_Name
|
||
FROM $tablePost p
|
||
LEFT JOIN $tableCategory c ON p.log_CateID = c.cate_ID
|
||
WHERE p.log_Type = 0 AND p.log_Status = 0
|
||
ORDER BY p.log_ViewNums DESC
|
||
LIMIT $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'recipe');
|
||
}
|
||
} else {
|
||
$table = $zbp->db->dbpre . 'ingredient_detail';
|
||
|
||
$sql = "SELECT ingredient_id, name, view_count, allergen, allergen_type
|
||
FROM $table
|
||
ORDER BY view_count DESC
|
||
LIMIT $limit";
|
||
|
||
$results = $zbp->db->Query($sql);
|
||
$list = array();
|
||
foreach ($results as $row) {
|
||
$list[] = format_unified_item($row, 'ingredient');
|
||
}
|
||
}
|
||
|
||
return array(
|
||
'code' => 200,
|
||
'message' => 'success',
|
||
'data' => array(
|
||
'type' => $type,
|
||
'type_name' => $type === 'recipe' ? '食谱' : '食材',
|
||
'list' => $list,
|
||
'limit' => $limit
|
||
)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 格式化统一输出项
|
||
*/
|
||
function format_unified_item($row, $type, $isDetail = false) {
|
||
if ($type === 'recipe') {
|
||
$item = array(
|
||
'id' => (int) $row['log_ID'],
|
||
'type' => 'recipe',
|
||
'type_name' => '食谱',
|
||
'title' => $row['log_Title'],
|
||
'intro' => mb_substr(strip_tags($row['log_Intro'] ?? ''), 0, 100),
|
||
'category' => array(
|
||
'id' => (int) ($row['log_CateID'] ?? 0),
|
||
'name' => $row['cate_Name'] ?? ''
|
||
),
|
||
'statistics' => array(
|
||
'view_count' => (int) ($row['log_ViewNums'] ?? 0),
|
||
'like_count' => (int) ($row['like_nums'] ?? 0),
|
||
'recommend_count' => (int) ($row['recommend_nums'] ?? 0)
|
||
),
|
||
'publish_time' => strtotime($row['log_PostTime']),
|
||
'url' => '?act=unified_detail&type=recipe&id=' . $row['log_ID']
|
||
);
|
||
|
||
if ($isDetail) {
|
||
$item['content'] = $row['log_Content'] ?? '';
|
||
$item['tags'] = parse_tags($row['log_Tag'] ?? '');
|
||
}
|
||
} else {
|
||
$allergenType = json_decode($row['allergen_type'] ?? '[]', true);
|
||
$allergen = json_decode($row['allergen'] ?? '[]', true);
|
||
|
||
$item = array(
|
||
'id' => (int) $row['ingredient_id'],
|
||
'type' => 'ingredient',
|
||
'type_name' => '食材',
|
||
'title' => $row['name'],
|
||
'intro' => mb_substr(strip_tags($row['introduction'] ?? ''), 0, 100),
|
||
'category' => array(
|
||
'id' => (int) ($row['cate_ID'] ?? 0),
|
||
'name' => ''
|
||
),
|
||
'statistics' => array(
|
||
'view_count' => (int) ($row['view_count'] ?? 0),
|
||
'like_count' => 0,
|
||
'recommend_count' => 0
|
||
),
|
||
'publish_time' => strtotime($row['create_time'] ?? 'now'),
|
||
'url' => '?act=unified_detail&type=ingredient&id=' . $row['ingredient_id']
|
||
);
|
||
|
||
if (!empty($allergenType)) {
|
||
$item['allergen_type'] = $allergenType;
|
||
}
|
||
if (!empty($allergen)) {
|
||
$item['allergen'] = $allergen;
|
||
}
|
||
|
||
if ($isDetail) {
|
||
$item['content'] = $row['introduction'] ?? '';
|
||
$item['usage_tip'] = $row['usage_tip'] ?? '';
|
||
$item['nutrition'] = json_decode($row['nutrition'] ?? '[]', true);
|
||
$item['effect'] = $row['effect'] ?? '';
|
||
}
|
||
}
|
||
|
||
return $item;
|
||
}
|