418 lines
16 KiB
PHP
418 lines
16 KiB
PHP
<?php
|
|
|
|
namespace app\KitchenScale3\controller\app;
|
|
|
|
use think\Db;
|
|
use think\Controller;
|
|
|
|
class Guessyoulike extends Controller {
|
|
|
|
protected $kitchenscale_db_msg = [
|
|
'cookbook' => 'app_user_cookbook', //食谱表
|
|
'cookbook_label' => 'app_user_cookbook_label', //食谱标签表
|
|
'cookbook_food_relation' => 'app_user_cookbook_food_relation', //食谱跟食材关系表
|
|
'foodlist2' => 'app_z_national_standard_food_type_2_multilingual', //食材标签表2
|
|
'foodlist3' => 'app_z_national_standard_food_type_3_multilingual', //食材表
|
|
'kcal_log' => 'app_user_kcal_log_multilingual', //用户饮食记录表记录用户吃了什么食材
|
|
'search_history' => 'app_user_search_history_multilingual', //用户搜索记录表,记录用户搜索过什么内容
|
|
// 'tag_preference' => 'app_user_tag_preference', //用户标签偏好表
|
|
'recommend_cache' => 'app_recommend_cache_multilingual' //智能推荐缓存表
|
|
];
|
|
|
|
protected $config = [
|
|
'tag_limit' => 2,
|
|
'item_limit' => 12,
|
|
'cache_time' => 3600
|
|
];
|
|
|
|
/**
|
|
* 猜你喜欢主接口
|
|
*/
|
|
public function getGuessYouLike($user_id = 1, $type = 'food', $limit = null) {
|
|
try {
|
|
$cfc = Db::connect('cfc_db');
|
|
// dump(1);
|
|
// 设置限制数量
|
|
$tag_limit = $limit ? intval($limit) : $this->config['tag_limit'];
|
|
$item_limit = $this->config['item_limit'];
|
|
|
|
// 检查缓存
|
|
// $cache_key = $user_id . ':' . $type;
|
|
// $cache_result = $this->getCache($cfc, $cache_key);
|
|
// // die;
|
|
// if ($cache_result !== null) {
|
|
// return $cache_result;
|
|
// }
|
|
|
|
// 判断用户是否有历史数据
|
|
$has_history = $this->checkUserHistory($cfc, $user_id);
|
|
if (!$has_history) {
|
|
// 新用户,返回最火信息(仅一个标签)
|
|
$result = $this->getPopularRecommendations($cfc, $type, 1, $item_limit);
|
|
} else {
|
|
// 老用户,根据类型返回个性化推荐
|
|
if ($type === 'cookbook') {
|
|
$result = $this->getCookbookRecommendations($cfc, $user_id, $tag_limit, $item_limit);
|
|
|
|
} else {
|
|
$result = $this->getFoodRecommendations($cfc, $user_id, $tag_limit, $item_limit);
|
|
}
|
|
}
|
|
|
|
// 确保返回格式正确
|
|
if (!is_array($result)) {
|
|
$result = [];
|
|
}
|
|
|
|
// 更新缓存
|
|
// $this->updateCache($cfc, $cache_key, $user_id, $type, $result);
|
|
|
|
return $result;
|
|
|
|
} catch (\Exception $e) {
|
|
// 记录错误日志
|
|
\think\Log::error('猜你喜欢功能错误: ' . $e->getMessage());
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查用户是否有历史数据
|
|
*/
|
|
private function checkUserHistory($db, $user_id) {
|
|
try {
|
|
// 检查饮食记录
|
|
$kcal_result = $db->query("
|
|
SELECT COUNT(*) as count
|
|
FROM {$this->kitchenscale_db_msg['kcal_log']}
|
|
WHERE aud_id = ? AND is_del = 0
|
|
", [$user_id]);
|
|
$kcal_count = $kcal_result[0]['count'] ?? 0;
|
|
|
|
// 检查搜索记录
|
|
$search_result = $db->query("
|
|
SELECT COUNT(*) as count
|
|
FROM {$this->kitchenscale_db_msg['search_history']}
|
|
WHERE user_id = ? AND is_del = 0
|
|
", [$user_id]);
|
|
$search_count = $search_result[0]['count'] ?? 0;
|
|
|
|
return ($kcal_count > 0 || $search_count > 0);
|
|
} catch (\Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取缓存数据
|
|
*/
|
|
private function getCache($db, $cache_key) {
|
|
try {
|
|
$cache_result = $db->query("
|
|
SELECT id, cache_key, user_id, keyword, recommend_data, hit_count, last_hit, is_del, create_time
|
|
FROM {$this->kitchenscale_db_msg['recommend_cache']}
|
|
WHERE cache_key = ? AND is_del = 0
|
|
", [$cache_key]);
|
|
|
|
if (!empty($cache_result)) {
|
|
$cache = $cache_result[0];
|
|
$last_hit_timestamp = strtotime($cache['last_hit']);
|
|
|
|
if (time() - $last_hit_timestamp < $this->config['cache_time']) {
|
|
// 更新命中次数和时间
|
|
$db->execute("
|
|
UPDATE {$this->kitchenscale_db_msg['recommend_cache']}
|
|
SET hit_count = hit_count + 1, last_hit = GETDATE()
|
|
WHERE id = ?
|
|
", [$cache['id']]);
|
|
|
|
$data = json_decode($cache['recommend_data'], true);
|
|
return is_array($data) ? $data : null;
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// 忽略缓存错误,继续执行
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 更新缓存
|
|
*/
|
|
private function updateCache($db, $cache_key, $user_id, $keyword, $data) {
|
|
try {
|
|
$current_time = date('Y-m-d H:i:s');
|
|
$recommend_data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
|
|
|
// 检查是否存在缓存
|
|
$existing_result = $db->query("
|
|
SELECT id FROM {$this->kitchenscale_db_msg['recommend_cache']}
|
|
WHERE cache_key = ? AND is_del = 0
|
|
", [$cache_key]);
|
|
|
|
if (!empty($existing_result)) {
|
|
// 更新现有缓存
|
|
$db->execute("
|
|
UPDATE {$this->kitchenscale_db_msg['recommend_cache']}
|
|
SET user_id = ?, keyword = ?, recommend_data = ?, hit_count = 1,
|
|
last_hit = ?, create_time = ?
|
|
WHERE cache_key = ? AND is_del = 0
|
|
", [$user_id, $keyword, $recommend_data, $current_time, $current_time, $cache_key]);
|
|
} else {
|
|
// 插入新缓存
|
|
$db->execute("
|
|
INSERT INTO {$this->kitchenscale_db_msg['recommend_cache']}
|
|
(cache_key, user_id, keyword, recommend_data, hit_count, last_hit, create_time, is_del)
|
|
VALUES (?, ?, ?, ?, 1, ?, ?, 0)
|
|
", [$cache_key, $user_id, $keyword, $recommend_data, $current_time, $current_time]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
// 忽略缓存更新错误
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取热门推荐(新用户)
|
|
*/
|
|
private function getPopularRecommendations($db, $type, $tag_limit, $item_limit) {
|
|
// dump($type);
|
|
if ($type === 'cookbook') {
|
|
return $this->getPopularCookbooks($db, $tag_limit, $item_limit);
|
|
} else {
|
|
// dump(111);
|
|
return $this->getPopularFoods($db, $tag_limit, $item_limit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取热门食谱(新用户)
|
|
*/
|
|
private function getPopularCookbooks($db, $tag_limit, $item_limit) {
|
|
try {
|
|
// 简化查询,避免复杂关联导致的错误
|
|
$popular_cookbooks = $db->query("
|
|
SELECT TOP {$item_limit}
|
|
id,
|
|
title as name
|
|
FROM {$this->kitchenscale_db_msg['cookbook']}
|
|
WHERE is_del = 0
|
|
ORDER BY likes_num DESC, read_it DESC, create_time DESC
|
|
");
|
|
// dump('sp');
|
|
// dump($popular_cookbooks);
|
|
$result = [];
|
|
$label_data = [];
|
|
|
|
foreach ($popular_cookbooks as $cookbook) {
|
|
$label_data[] = [
|
|
'name' => $cookbook['name'] ?? '未知食谱',
|
|
'id' => $cookbook['id'] ?? 0,
|
|
'type' => 'cookbook'
|
|
];
|
|
}
|
|
|
|
if (!empty($label_data)) {
|
|
$result['最火食谱搜索'] = $label_data;
|
|
}
|
|
|
|
return $result;
|
|
} catch (\Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取热门食材(新用户)
|
|
*/
|
|
private function getPopularFoods($db, $tag_limit, $item_limit) {
|
|
try {
|
|
// dump(2222);
|
|
// // 简化查询,避免复杂关联导致的错误
|
|
$popular_foods = $db->query("
|
|
SELECT TOP {$item_limit}
|
|
id,
|
|
keyword as name,
|
|
COUNT(*) as num
|
|
FROM {$this->kitchenscale_db_msg['search_history']}
|
|
WHERE is_del = 0 AND type = 'food'
|
|
GROUP BY id, keyword
|
|
ORDER BY num DESC
|
|
");
|
|
// dump('sc');
|
|
// dump($popular_foods);
|
|
|
|
$popular_foods_2 = [];
|
|
if(count($popular_foods) < $item_limit){
|
|
$num = $item_limit - count($popular_foods);
|
|
$popular_foods_2 = $db->query("
|
|
SELECT TOP {$num}
|
|
id,
|
|
food_name as name
|
|
FROM {$this->kitchenscale_db_msg['foodlist3']}
|
|
WHERE is_del = 0
|
|
ORDER BY is_popular DESC, food_name ASC
|
|
");
|
|
}
|
|
|
|
foreach ($popular_foods_2 as $key => $value) {
|
|
$popular_foods[] = $value;
|
|
}
|
|
|
|
|
|
$result = [];
|
|
$label_data = [];
|
|
|
|
foreach ($popular_foods as $food) {
|
|
$label_data[] = [
|
|
'name' => $food['name'] ?? '未知食材',
|
|
'id' => $food['id'] ?? 0,
|
|
'type' => 'food'
|
|
];
|
|
}
|
|
|
|
if (!empty($label_data)) {
|
|
$result['最火食材搜索'] = $label_data;
|
|
}
|
|
|
|
return $result;
|
|
} catch (\Exception $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取个性化食谱推荐(老用户)
|
|
*/
|
|
private function getCookbookRecommendations($db, $user_id, $tag_limit, $item_limit) {
|
|
try {
|
|
// 获取用户最常吃的食材
|
|
$user_top_foods = $db->query("
|
|
SELECT TOP 10 food_id, COUNT(*) as eat_count
|
|
FROM {$this->kitchenscale_db_msg['kcal_log']}
|
|
WHERE aud_id = ? AND is_del = 0
|
|
GROUP BY food_id
|
|
ORDER BY eat_count DESC
|
|
", [$user_id]);
|
|
|
|
if (empty($user_top_foods)) {
|
|
return $this->getPopularCookbooks($db, $tag_limit, $item_limit);
|
|
}
|
|
|
|
$food_ids = array_column($user_top_foods, 'food_id');
|
|
if (empty($food_ids)) {
|
|
return $this->getPopularCookbooks($db, $tag_limit, $item_limit);
|
|
}
|
|
$food_ids_str = implode(',', $food_ids);
|
|
|
|
// 获取包含这些食材的食谱标签
|
|
$preferred_labels = $db->query("
|
|
SELECT TOP {$tag_limit} lbl.id, lbl.name, COUNT(DISTINCT cb.id) as match_count
|
|
FROM {$this->kitchenscale_db_msg['cookbook_label']} lbl
|
|
INNER JOIN {$this->kitchenscale_db_msg['cookbook']} cb ON lbl.id = cb.cook_label AND cb.is_del = 0
|
|
INNER JOIN {$this->kitchenscale_db_msg['cookbook_food_relation']} cfr ON cb.id = cfr.cookbook_id
|
|
WHERE lbl.is_del = 0 AND cfr.food_id IN ({$food_ids_str})
|
|
GROUP BY lbl.id, lbl.name
|
|
ORDER BY match_count DESC
|
|
");
|
|
|
|
$result = [];
|
|
foreach ($preferred_labels as $label) {
|
|
// 使用子查询避免GROUP BY复杂性问题
|
|
$cookbooks = $db->query("
|
|
SELECT TOP {$item_limit} cb.id, cb.title as name
|
|
FROM {$this->kitchenscale_db_msg['cookbook']} cb
|
|
WHERE cb.id IN (
|
|
SELECT DISTINCT cfr.cookbook_id
|
|
FROM {$this->kitchenscale_db_msg['cookbook_food_relation']} cfr
|
|
WHERE cfr.food_id IN ({$food_ids_str})
|
|
)
|
|
AND cb.cook_label = ?
|
|
AND cb.is_del = 0
|
|
ORDER BY cb.likes_num DESC, cb.read_it DESC
|
|
", [$label['id']]);
|
|
|
|
$label_data = [];
|
|
foreach ($cookbooks as $cookbook) {
|
|
$label_data[] = [
|
|
'name' => $cookbook['name'] ?? '未知食谱',
|
|
'id' => $cookbook['id'] ?? 0,
|
|
'type' => 'cookbook'
|
|
];
|
|
}
|
|
|
|
if (!empty($label_data)) {
|
|
$result[$label['name'] ?? '未知标签'] = $label_data;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
} catch (\Exception $e) {
|
|
return $this->getPopularCookbooks($db, $tag_limit, $item_limit);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取个性化食材推荐(老用户)
|
|
*/
|
|
private function getFoodRecommendations($db, $user_id, $tag_limit, $item_limit) {
|
|
try {
|
|
// 获取用户最常吃的食材
|
|
$user_top_foods = $db->query("
|
|
SELECT TOP 10 food_id, COUNT(*) as eat_count
|
|
FROM {$this->kitchenscale_db_msg['kcal_log']}
|
|
WHERE aud_id = ? AND is_del = 0
|
|
GROUP BY food_id
|
|
ORDER BY eat_count DESC
|
|
", [$user_id]);
|
|
|
|
if (empty($user_top_foods)) {
|
|
return $this->getPopularFoods($db, $tag_limit, $item_limit);
|
|
}
|
|
|
|
$food_ids = array_column($user_top_foods, 'food_id');
|
|
if (empty($food_ids)) {
|
|
return $this->getPopularFoods($db, $tag_limit, $item_limit);
|
|
}
|
|
$food_ids_str = implode(',', $food_ids);
|
|
|
|
// 获取用户偏好食材的分类
|
|
$preferred_categories = $db->query("
|
|
SELECT TOP {$tag_limit} f2.id, f2.name, COUNT(DISTINCT f3.id) as food_count
|
|
FROM {$this->kitchenscale_db_msg['foodlist2']} f2
|
|
INNER JOIN {$this->kitchenscale_db_msg['foodlist3']} f3 ON f2.id = f3.two_id
|
|
WHERE f3.id IN ({$food_ids_str}) AND f2.is_del = 0 AND f3.is_del = 0
|
|
GROUP BY f2.id, f2.name
|
|
ORDER BY food_count DESC
|
|
");
|
|
|
|
$result = [];
|
|
foreach ($preferred_categories as $category) {
|
|
// 获取该分类下的其他食材
|
|
$foods = $db->query("
|
|
SELECT TOP {$item_limit} id, food_name as name
|
|
FROM {$this->kitchenscale_db_msg['foodlist3']}
|
|
WHERE two_id = ? AND is_del = 0 AND id NOT IN ({$food_ids_str})
|
|
ORDER BY is_popular DESC, food_name ASC
|
|
", [$category['id']]);
|
|
|
|
$category_data = [];
|
|
foreach ($foods as $food) {
|
|
$category_data[] = [
|
|
'name' => $food['name'] ?? '未知食材',
|
|
'id' => $food['id'] ?? 0,
|
|
'type' => 'food'
|
|
];
|
|
}
|
|
|
|
if (!empty($category_data)) {
|
|
$result[$category['name'] ?? '未知分类'] = $category_data;
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
} catch (\Exception $e) {
|
|
return $this->getPopularFoods($db, $tag_limit, $item_limit);
|
|
}
|
|
}
|
|
} |