Files
kitchen/docs/api/api_action.php
Developer 13fdbdc431 瀑布流
2026-04-13 03:39:29 +08:00

654 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 动态接口(写操作)
* 包含:点赞、评分、浏览量等写操作
* IP限制每个IP每天可评分30次
*
* @file api_action.php
* @author AI Assistant
* @date 2026-04-12
* @version 2.0.0
* @desc 动态操作接口,支持点赞、评分、浏览量统计
* @lastUpdate 2026-04-12 修改推荐为评分功能不可取消记录IP日志到本地
*/
$startTime = microtime(true);
require '../zb_system/function/c_system_base.php';
$zbp->Load();
require_once 'cache.php';
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
$act = strtolower(trim($_GET['act'] ?? 'index'));
$result = array();
switch ($act) {
case 'like':
$result = toggle_like();
break;
case 'rate':
$result = submit_rating();
break;
case 'recommend':
$result = array(
'code' => 400,
'message' => 'recommend接口已废弃请使用rate接口',
'data' => array(
'new_endpoint' => '?act=rate&type=recipe&id=1&score=5',
'description' => '评分接口分数范围1-5每日限制30次不可取消'
)
);
break;
case 'view':
$result = increase_view();
break;
case 'ip_status':
$result = get_ip_status();
break;
case 'index':
default:
$result = array(
'code' => 200,
'message' => '动态接口',
'data' => array(
'version' => '2.0.0',
'description' => '包含点赞、评分、浏览量等写操作',
'ip_limit' => '每个IP每天可评分30次',
'endpoints' => array(
'like' => '?act=like&type=recipe&id=1&action=like',
'rate' => '?act=rate&type=recipe&id=1&score=5',
'view' => '?act=view&type=recipe&id=1&count=1',
'ip_status' => '?act=ip_status'
),
'changes' => array(
'recommend' => '已废弃改用rate接口',
'rate' => '评分功能1-5分不可取消每日30次限制'
)
)
);
break;
}
if ($result['code'] === 200) {
$type = strtolower(trim($_GET['type'] ?? ''));
if ($type === 'recipe') {
ApiCache::clearByAct('list');
ApiCache::clearByAct('stats');
$id = (int) ($_GET['id'] ?? 0);
if ($id > 0) {
ApiCache::clear('detail', array('id' => $id));
}
} elseif ($type === 'ingredient') {
ApiCache::clearByAct('ingredients');
ApiCache::clearByAct('stats');
$id = (int) ($_GET['id'] ?? 0);
if ($id > 0) {
ApiCache::clear('ingredient_detail', array('id' => $id));
}
}
}
$result['_query_time'] = round((microtime(true) - $startTime) * 1000, 2) . 'ms';
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
/**
* 获取客户端IP
*/
function get_client_ip() {
$ip = '';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip = trim(explode(',', $ip)[0]);
return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
}
/**
* 记录统计日志
* @param string $type 类型: recipe 或 ingredient
* @param int $id ID
* @param string $field 字段: view, like, rate
* @param int $count 变化数量(正数增加,负数减少)
*/
function log_stat($type, $id, $field, $count = 1) {
global $zbp;
$today = date('Y-m-d');
if ($type === 'recipe') {
$table = $zbp->db->dbpre . 'recipe_stat_log';
$idField = 'log_id';
} else {
$table = $zbp->db->dbpre . 'ingredient_stat_log';
$idField = 'ingredient_id';
}
$checkSql = "SELECT id, view_count, like_count, recommend_count FROM $table WHERE $idField = $id AND stat_date = '$today'";
$result = $zbp->db->Query($checkSql);
if (empty($result)) {
$insertSql = "INSERT INTO $table ($idField, stat_date, view_count, like_count, recommend_count) VALUES ($id, '$today', 0, 0, 0)";
$zbp->db->Query($insertSql);
$currentView = 0;
$currentLike = 0;
$currentRate = 0;
} else {
$currentView = (int) ($result[0]['view_count'] ?? 0);
$currentLike = (int) ($result[0]['like_count'] ?? 0);
$currentRate = (int) ($result[0]['recommend_count'] ?? 0);
}
switch ($field) {
case 'view':
$newView = max(0, $currentView + $count);
$updateSql = "UPDATE $table SET view_count = $newView WHERE $idField = $id AND stat_date = '$today'";
break;
case 'like':
$newLike = max(0, $currentLike + $count);
$updateSql = "UPDATE $table SET like_count = $newLike WHERE $idField = $id AND stat_date = '$today'";
break;
case 'rate':
$newRate = max(0, $currentRate + $count);
$updateSql = "UPDATE $table SET recommend_count = $newRate WHERE $idField = $id AND stat_date = '$today'";
break;
default:
return;
}
$zbp->db->Query($updateSql);
}
/**
* 获取IP缓存文件路径
*/
function get_ip_cache_file() {
$cacheDir = dirname(__FILE__) . '/cache/ip/';
if (!is_dir($cacheDir)) {
@mkdir($cacheDir, 0755, true);
}
return $cacheDir . 'rate_' . date('Y-m-d') . '.json';
}
/**
* 获取评分日志文件路径
*/
function get_rating_log_file() {
$logDir = dirname(__FILE__) . '/logs/rating/';
if (!is_dir($logDir)) {
@mkdir($logDir, 0755, true);
}
return $logDir . 'rating_' . date('Y-m-d') . '.json';
}
/**
* 加载IP评分缓存
*/
function load_ip_rate_cache() {
$file = get_ip_cache_file();
if (file_exists($file)) {
$content = file_get_contents($file);
$data = json_decode($content, true);
return is_array($data) ? $data : array();
}
return array();
}
/**
* 保存IP评分缓存
*/
function save_ip_rate_cache($data) {
$file = get_ip_cache_file();
file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE));
}
/**
* 记录评分日志到本地文件
* @param string $type 类型: recipe 或 ingredient
* @param int $id ID
* @param int $score 评分 1-5
* @param string $ip 客户端IP
*/
function log_rating_to_file($type, $id, $score, $ip) {
$file = get_rating_log_file();
$logEntry = array(
'timestamp' => date('Y-m-d H:i:s'),
'type' => $type,
'id' => $id,
'score' => $score,
'ip' => $ip
);
$logs = array();
if (file_exists($file)) {
$content = file_get_contents($file);
$logs = json_decode($content, true);
if (!is_array($logs)) {
$logs = array();
}
}
$logs[] = $logEntry;
file_put_contents($file, json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
/**
* 清理过期的IP缓存文件和日志文件
*/
function clean_expired_cache_files() {
$cacheDir = dirname(__FILE__) . '/cache/ip/';
$logDir = dirname(__FILE__) . '/logs/rating/';
$today = date('Y-m-d');
if (is_dir($cacheDir)) {
$files = glob($cacheDir . 'rate_*.json');
foreach ($files as $file) {
$basename = basename($file, '.json');
$date = str_replace('rate_', '', $basename);
if ($date < $today) {
@unlink($file);
}
}
}
if (is_dir($logDir)) {
$files = glob($logDir . 'rating_*.json');
$expireDate = date('Y-m-d', strtotime('-7 days'));
foreach ($files as $file) {
$basename = basename($file, '.json');
$date = str_replace('rating_', '', $basename);
if ($date < $expireDate) {
@unlink($file);
}
}
}
}
/**
* 检查IP评分限制
*/
function check_ip_rate_limit() {
$ip = get_client_ip();
$maxDaily = 30;
if (rand(1, 50) === 1) {
clean_expired_cache_files();
}
$cache = load_ip_rate_cache();
$current = isset($cache[$ip]) ? (int) $cache[$ip] : 0;
return array(
'allowed' => $current < $maxDaily,
'current' => $current,
'remaining' => max(0, $maxDaily - $current)
);
}
/**
* 增加IP评分次数
*/
function increment_ip_rate() {
$ip = get_client_ip();
$cache = load_ip_rate_cache();
$current = isset($cache[$ip]) ? (int) $cache[$ip] : 0;
$cache[$ip] = $current + 1;
save_ip_rate_cache($cache);
}
/**
* 获取IP状态
*/
function get_ip_status() {
$ipStatus = check_ip_rate_limit();
return array(
'code' => 200,
'message' => 'success',
'data' => array(
'ip' => get_client_ip(),
'today_rate_count' => $ipStatus['current'],
'remaining_rate' => $ipStatus['remaining'],
'daily_limit' => 30,
'date' => date('Y-m-d')
)
);
}
/**
* 点赞/取消点赞
*/
function toggle_like() {
global $zbp;
$type = strtolower(trim($_GET['type'] ?? 'recipe'));
$id = (int) ($_GET['id'] ?? 0);
$action = strtolower(trim($_GET['action'] ?? 'like'));
if (!in_array($type, ['recipe', 'ingredient'])) {
return array('code' => 400, 'message' => 'type参数必须是 recipe 或 ingredient');
}
if ($id <= 0) {
return array('code' => 400, 'message' => 'id参数无效');
}
if (!in_array($action, ['like', 'unlike'])) {
return array('code' => 400, 'message' => 'action参数必须是 like 或 unlike');
}
if ($type === 'recipe') {
$tablePost = $zbp->db->dbpre . 'post';
$tableStat = $zbp->db->dbpre . 'post_stat';
$checkSql = "SELECT log_ID FROM $tablePost WHERE log_ID = $id AND log_Type = 0 AND log_Status = 0";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '菜谱不存在');
}
$statSql = "SELECT * FROM $tableStat WHERE log_id = $id";
$statResult = $zbp->db->Query($statSql);
if (empty($statResult)) {
$insertSql = "INSERT INTO $tableStat (log_id, like_nums, rate_nums, rate_score) VALUES ($id, 0, 0, 0.00)";
$zbp->db->Query($insertSql);
$currentLikes = 0;
} else {
$currentLikes = (int) ($statResult[0]['like_nums'] ?? 0);
}
if ($action === 'like') {
$newLikes = $currentLikes + 1;
} else {
$newLikes = max(0, $currentLikes - 1);
}
$updateSql = "UPDATE $tableStat SET like_nums = $newLikes WHERE log_id = $id";
$zbp->db->Query($updateSql);
log_stat('recipe', $id, 'like', $action === 'like' ? 1 : -1);
return array(
'code' => 200,
'message' => $action === 'like' ? '点赞成功' : '取消点赞成功',
'data' => array(
'type' => 'recipe',
'id' => $id,
'action' => $action,
'like_count' => $newLikes
)
);
} else {
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$tableStat = $zbp->db->dbpre . 'ingredient_stat';
$checkSql = "SELECT ingredient_id FROM $tableIngredient WHERE ingredient_id = $id";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '食材不存在');
}
$statSql = "SELECT * FROM $tableStat WHERE ingredient_id = $id";
$statResult = $zbp->db->Query($statSql);
if (empty($statResult)) {
$insertSql = "INSERT INTO $tableStat (ingredient_id, like_nums, rate_nums, rate_score) VALUES ($id, 0, 0, 0.00)";
$zbp->db->Query($insertSql);
$currentLikes = 0;
} else {
$currentLikes = (int) ($statResult[0]['like_nums'] ?? 0);
}
if ($action === 'like') {
$newLikes = $currentLikes + 1;
} else {
$newLikes = max(0, $currentLikes - 1);
}
$updateSql = "UPDATE $tableStat SET like_nums = $newLikes WHERE ingredient_id = $id";
$zbp->db->Query($updateSql);
log_stat('ingredient', $id, 'like', $action === 'like' ? 1 : -1);
return array(
'code' => 200,
'message' => $action === 'like' ? '点赞成功' : '取消点赞成功',
'data' => array(
'type' => 'ingredient',
'id' => $id,
'action' => $action,
'like_count' => $newLikes
)
);
}
}
/**
* 提交评分
* IP限制每个IP每天可评分30次
* 评分范围1-5分
* 不可取消
*/
function submit_rating() {
global $zbp;
$type = strtolower(trim($_GET['type'] ?? 'recipe'));
$id = (int) ($_GET['id'] ?? 0);
$score = (int) ($_GET['score'] ?? 5);
if (!in_array($type, ['recipe', 'ingredient'])) {
return array('code' => 400, 'message' => 'type参数必须是 recipe 或 ingredient');
}
if ($id <= 0) {
return array('code' => 400, 'message' => 'id参数无效');
}
if ($score < 1 || $score > 5) {
return array('code' => 400, 'message' => 'score参数必须在1-5之间');
}
$ipStatus = check_ip_rate_limit();
if (!$ipStatus['allowed']) {
return array(
'code' => 429,
'message' => '今日评分次数已达上限(30次)',
'data' => array(
'ip' => get_client_ip(),
'today_rate_count' => $ipStatus['current'],
'remaining' => 0,
'daily_limit' => 30
)
);
}
$ip = get_client_ip();
if ($type === 'recipe') {
$tablePost = $zbp->db->dbpre . 'post';
$tableStat = $zbp->db->dbpre . 'post_stat';
$checkSql = "SELECT log_ID FROM $tablePost WHERE log_ID = $id AND log_Type = 0 AND log_Status = 0";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '菜谱不存在');
}
$statSql = "SELECT * FROM $tableStat WHERE log_id = $id";
$statResult = $zbp->db->Query($statSql);
if (empty($statResult)) {
$insertSql = "INSERT INTO $tableStat (log_id, like_nums, rate_nums, rate_score) VALUES ($id, 0, 0, 0.00)";
$zbp->db->Query($insertSql);
$currentNums = 0;
$currentScore = 0.00;
} else {
$currentNums = (int) ($statResult[0]['rate_nums'] ?? 0);
$currentScore = (float) ($statResult[0]['rate_score'] ?? 0.00);
}
$newNums = $currentNums + 1;
$newScore = ($currentScore * $currentNums + $score) / $newNums;
$updateSql = "UPDATE $tableStat SET rate_nums = $newNums, rate_score = $newScore WHERE log_id = $id";
$zbp->db->Query($updateSql);
increment_ip_rate();
log_rating_to_file('recipe', $id, $score, $ip);
log_stat('recipe', $id, 'rate', 1);
$ipStatus = check_ip_rate_limit();
return array(
'code' => 200,
'message' => '评分成功',
'data' => array(
'type' => 'recipe',
'id' => $id,
'score' => $score,
'rate_nums' => $newNums,
'rate_score' => round($newScore, 2),
'ip_remaining' => $ipStatus['remaining']
)
);
} else {
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$tableStat = $zbp->db->dbpre . 'ingredient_stat';
$checkSql = "SELECT ingredient_id FROM $tableIngredient WHERE ingredient_id = $id";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '食材不存在');
}
$statSql = "SELECT * FROM $tableStat WHERE ingredient_id = $id";
$statResult = $zbp->db->Query($statSql);
if (empty($statResult)) {
$insertSql = "INSERT INTO $tableStat (ingredient_id, like_nums, rate_nums, rate_score) VALUES ($id, 0, 0, 0.00)";
$zbp->db->Query($insertSql);
$currentNums = 0;
$currentScore = 0.00;
} else {
$currentNums = (int) ($statResult[0]['rate_nums'] ?? 0);
$currentScore = (float) ($statResult[0]['rate_score'] ?? 0.00);
}
$newNums = $currentNums + 1;
$newScore = ($currentScore * $currentNums + $score) / $newNums;
$updateSql = "UPDATE $tableStat SET rate_nums = $newNums, rate_score = $newScore WHERE ingredient_id = $id";
$zbp->db->Query($updateSql);
increment_ip_rate();
log_rating_to_file('ingredient', $id, $score, $ip);
log_stat('ingredient', $id, 'rate', 1);
$ipStatus = check_ip_rate_limit();
return array(
'code' => 200,
'message' => '评分成功',
'data' => array(
'type' => 'ingredient',
'id' => $id,
'score' => $score,
'rate_nums' => $newNums,
'rate_score' => round($newScore, 2),
'ip_remaining' => $ipStatus['remaining']
)
);
}
}
/**
* 增加浏览量
*/
function increase_view() {
global $zbp;
$type = strtolower(trim($_GET['type'] ?? 'recipe'));
$id = (int) ($_GET['id'] ?? 0);
$count = (int) ($_GET['count'] ?? 1);
if (!in_array($type, ['recipe', 'ingredient'])) {
return array('code' => 400, 'message' => 'type参数必须是 recipe 或 ingredient');
}
if ($id <= 0) {
return array('code' => 400, 'message' => 'id参数无效');
}
$count = max(1, min(100, $count));
if ($type === 'recipe') {
$tablePost = $zbp->db->dbpre . 'post';
$checkSql = "SELECT log_ID, log_ViewNums FROM $tablePost WHERE log_ID = $id AND log_Type = 0 AND log_Status = 0";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '菜谱不存在');
}
$currentViews = (int) ($checkResult[0]['log_ViewNums'] ?? 0);
$newViews = $currentViews + $count;
$updateSql = "UPDATE $tablePost SET log_ViewNums = $newViews WHERE log_ID = $id";
$zbp->db->Query($updateSql);
log_stat('recipe', $id, 'view', $count);
return array(
'code' => 200,
'message' => '浏览量已更新',
'data' => array(
'type' => 'recipe',
'id' => $id,
'view_count' => $newViews,
'increment' => $count
)
);
} else {
$tableIngredient = $zbp->db->dbpre . 'ingredient_detail';
$checkSql = "SELECT ingredient_id, view_count FROM $tableIngredient WHERE ingredient_id = $id";
$checkResult = $zbp->db->Query($checkSql);
if (empty($checkResult)) {
return array('code' => 404, 'message' => '食材不存在');
}
$currentViews = (int) ($checkResult[0]['view_count'] ?? 0);
$newViews = $currentViews + $count;
$updateSql = "UPDATE $tableIngredient SET view_count = $newViews WHERE ingredient_id = $id";
$zbp->db->Query($updateSql);
log_stat('ingredient', $id, 'view', $count);
return array(
'code' => 200,
'message' => '浏览量已更新',
'data' => array(
'type' => 'ingredient',
'id' => $id,
'view_count' => $newViews,
'increment' => $count
)
);
}
}