SchoolPhysicalExamination/application/KitchenScale2/controller/app/Barcode.php

903 lines
33 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
namespace app\KitchenScale2\controller\app;
use think\Db;
use think\Request;
use app\KitchenScale2\controller\app\Base;
class Barcode extends Base{
protected $barcode_db_msg = [
'tiaoma'=>'app_commodity_barcode',//条码商品表
];
################################################################接口################################################################
################################################################接口################################################################
################################################################接口################################################################
/**
* 查询条码商品信息(对外调用接口)
* @return \think\response\Json
*/
public function search_food_barcode(){
// 尝试捕获异常
try {
$data = input('post.');
// 验证参数
if(!array_key_exists('barcode', $data)){
return $this->msg(10001,'未发现条形码'); // 10001: 关键参数缺失
}
if(!$this->verify_data_is_ok($data['barcode'],'intnum')){
return $this->msg(10005,'请将镜头对准商品条形码'); // 10005: 参数格式错误
}
$return_data = $this->search_food_barcode_action($data);
return $return_data;
} catch (\Exception $e) {
// 捕获异常
$logContent["flie"] = $e->getFile();
$logContent["line"] = $e->getLine();
$logContent['all_content'] = "异常信息:\n";
$logContent['all_content'] .= "消息: " . $e->getMessage() . "\n";
$logContent['all_content'] .= "代码: " . $e->getCode() . "\n";
$logContent['all_content'] .= "文件: " . $e->getFile() . "\n";
$logContent['all_content'] .= "行号: " . $e->getLine() . "\n";
$logContent['all_content'] .= "跟踪信息:\n" . $e->getTraceAsString() . "\n";
// 记录日志
$this->record_api_log($data ?? [], $logContent, null);
return $this->msg(99999); // 99999: 网络异常,请稍后重试
}
}
/**
* 查询条码商品信息的具体逻辑
* @param array $data 包含barcode的数据
* @return \think\response\Json
*/
private function search_food_barcode_action($data)
{
$barcode = $data['barcode'];
try {
// 1. 先查询本地数据库
$localProduct = $this->getProductFromLocalDB($barcode);
// 2. 检查本地数据是否需要更新
$needApiFetch = true;
if ($localProduct) {
$needApiFetch = $this->shouldUpdateProduct($localProduct);
if (!$needApiFetch) {
// 本地数据有效,直接返回
$formattedData = $this->buildResponseData($localProduct);
return $this->msg($formattedData, '成功获取商品信息');
}
}
// 3. 需要从API获取数据
if ($needApiFetch) {
$apiResult = $this->getOpenFoodFactsProduct($barcode);
if (!$apiResult['error'] && isset($apiResult['data'])) {
// API获取成功处理并保存数据
$processedData = $this->processApiProductData($apiResult['data'], $barcode);
// 验证处理后的数据是否有效
if (empty($processedData['Calorie']) || $processedData['Calorie'] === '0') {
// 如果卡路里为0说明营养数据可能不完整
if ($localProduct) {
// 有本地数据,返回本地数据
$formattedData = $this->buildResponseData($localProduct);
return $this->msg($formattedData, 'API数据不完整使用缓存数据');
} else {
// 使用10004: 未找到有效数据
return $this->msg(10004, '获取的商品营养数据不完整');
}
}
// 保存到数据库,获取保存结果
$saveResult = $this->saveProductToDatabaseAndGetId($processedData, $barcode, $localProduct);
if ($saveResult['success']) {
// 保存成功使用数据库真实ID构建返回数据
$savedProduct = $saveResult['product_data'];
$formattedData = $this->buildResponseData($savedProduct);
$message = '成功获取商品信息';
return $this->msg($formattedData, $message);
} else {
// 保存失败返回10002
return $this->msg(10002, '商品信息保存失败');
}
} else {
// API获取失败
if ($localProduct) {
// 有本地数据,返回本地数据
$formattedData = $this->buildResponseData($localProduct);
return $this->msg($formattedData, 'API获取失败使用缓存数据');
} else {
// 使用10004: 未找到有效数据
return $this->msg(10004, '未找到该条码对应的商品信息');
}
}
}
} catch (\Exception $e) {
// 记录内部逻辑异常
$logContent = [
'action' => 'search_food_barcode_action',
'barcode' => $barcode,
'status' => 'error',
'error_message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
];
$this->record_api_log($data, $logContent, null);
// 使用10002: 操作失败
return $this->msg(10002, '商品信息查询失败');
}
}
/**
* 直接根据条码查询的接口兼容GET请求
* @param string $barcode
* @return \think\response\Json
*/
public function getProductByBarcode($barcode = '')
{
try {
$request = Request::instance();
$data = [
'barcode' => $barcode ?: $request->param('barcode', '')
];
if (empty($data['barcode'])) {
return $this->msg(10001, 'barcode is miss'); // 10001: 关键参数缺失
}
if (!$this->verify_data_is_ok($data['barcode'], 'intnum')) {
return $this->msg(10005, 'barcode type is error'); // 10005: 参数格式错误
}
$result = $this->search_food_barcode_action($data);
return $result;
} catch (\Exception $e) {
$logContent = [
'file' => $e->getFile(),
'line' => $e->getLine(),
'error_message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
];
$this->record_api_log(['barcode' => $barcode], $logContent, null);
return $this->msg(99999); // 99999: 网络异常,请稍后重试
}
}
/**
* 批量查询条码接口
* @return \think\response\Json
*/
public function batchSearchFoodBarcode()
{
try {
$data = input('post.');
if(!array_key_exists('barcodes', $data) || !is_array($data['barcodes'])){
return $this->msg(10001, 'barcodes参数缺失或格式错误'); // 10001: 关键参数缺失
}
$results = [];
$successCount = 0;
foreach ($data['barcodes'] as $barcode) {
if ($this->verify_data_is_ok($barcode, 'intnum')) {
$result = $this->search_food_barcode_action(['barcode' => $barcode]);
// 判断result是Json对象还是数组
if ($result instanceof \think\response\Json) {
$resultData = json_decode($result->getContent(), true);
} else {
$resultData = $result;
}
if (isset($resultData['code']) && $resultData['code'] === 0) {
$successCount++;
$results[$barcode] = [
'status' => 'success',
'data' => $resultData['data']
];
} else {
$results[$barcode] = [
'status' => 'error',
'message' => $resultData['msg'] ?? '查询失败',
'code' => $resultData['code'] ?? 10002 // 10002: 操作失败
];
}
} else {
$results[$barcode] = [
'status' => 'error',
'message' => '无效的条码格式',
'code' => 10005 // 10005: 参数格式错误
];
}
}
$responseData = [
'results' => $results,
'total' => count($data['barcodes']),
'success_count' => $successCount,
'fail_count' => count($data['barcodes']) - $successCount
];
$message = "批量查询完成,成功{$successCount}个,失败" . (count($data['barcodes']) - $successCount) . "";
return $this->msg($responseData, $message);
} catch (\Exception $e) {
$logContent = [
'file' => $e->getFile(),
'line' => $e->getLine(),
'error_message' => $e->getMessage(),
'trace' => $e->getTraceAsString()
];
$this->record_api_log($data ?? [], $logContent, null);
return $this->msg(99999); // 99999: 网络异常,请稍后重试
}
}
#######################################################################小工具#######################################################################
#######################################################################小工具#######################################################################
#######################################################################小工具#######################################################################
/**
* 从本地数据库查询商品
* @param string $barcode
* @return array|null
*/
private function getProductFromLocalDB($barcode)
{
try {
$cfc = Db::connect('cfc_db');
$result = $cfc->table($this->barcode_db_msg['tiaoma'])
->where(['code' => $barcode])
->find();
return $result ?: null;
} catch (\Exception $e) {
// 数据库查询异常返回null
return null;
}
}
/**
* 保存商品到数据库并获取数据库ID
* @param array $productData
* @param string $barcode
* @param array|null $localProduct 本地已存在的数据
* @return array ['success' => bool, 'product_data' => array, 'id' => int|string]
*/
private function saveProductToDatabaseAndGetId($productData, $barcode, $localProduct = null)
{
try {
$cfc = Db::connect('cfc_db');
if ($localProduct) {
// 更新现有记录
$result = $cfc->table($this->barcode_db_msg['tiaoma'])
->where(['code' => $barcode])
->update($productData);
if ($result !== false) {
// 更新成功后重新查询获取完整数据
$updatedProduct = $this->getProductFromLocalDB($barcode);
if ($updatedProduct) {
return [
'success' => true,
'product_data' => $updatedProduct,
'id' => $updatedProduct['id']
];
}
}
} else {
// 插入新记录
$result = $cfc->table($this->barcode_db_msg['tiaoma'])
->insertGetId($productData);
if ($result !== false) {
// 获取新插入的数据
$newProduct = $this->getProductFromLocalDB($barcode);
if ($newProduct) {
return [
'success' => true,
'product_data' => $newProduct,
'id' => $result
];
}
}
}
return [
'success' => false,
'product_data' => null,
'id' => null
];
} catch (\Exception $e) {
error_log("保存商品到数据库失败: " . $e->getMessage());
return [
'success' => false,
'product_data' => null,
'id' => null
];
}
}
/**
* 检查是否需要更新商品信息
* @param array $product
* @return bool
*/
private function shouldUpdateProduct($product)
{
// 如果数据是最近30天内创建的不需要更新
if (isset($product['create_time'])) {
$createTime = strtotime($product['create_time']);
if (time() - $createTime < 30 * 24 * 3600) { // 30天内
return false;
}
}
// 如果数据不完整(四大营养素有缺失),需要更新
$essentialFields = ['Calorie', 'Protein', 'Fat', 'Carbohydrate'];
foreach ($essentialFields as $field) {
if (empty($product[$field]) || $product[$field] == '0' || $product[$field] == '') {
return true;
}
}
return false;
}
/**
* 调用Open Food Facts API获取商品信息
* @param string $barcode
* @return array
*/
private function getOpenFoodFactsProduct($barcode)
{
// 尝试多个API端点
$endpoints = [
['country' => 'world', 'timeout' => 8], // 全球站
['country' => 'cn', 'timeout' => 5], // 中国站
['country' => 'us', 'timeout' => 8], // 美国站
['country' => 'uk', 'timeout' => 8], // 英国站
];
$lastError = null;
foreach ($endpoints as $endpoint) {
try {
$result = $this->callOpenFoodFactsAPI($barcode, $endpoint['country'], $endpoint['timeout']);
if (!$result['error']) {
return $result; // 成功获取
}
$lastError = $result; // 记录错误
// 如果商品明确不存在(404),不需要尝试其他端点
if ($result['http_code'] == 404) {
break;
}
} catch (\Exception $e) {
$lastError = [
'error' => true,
'message' => 'API调用异常: ' . $e->getMessage(),
'http_code' => 0
];
}
// 短暂延迟,避免请求过快
usleep(100000); // 0.1秒
}
return $lastError ?: [
'error' => true,
'message' => '所有API端点都失败',
'http_code' => 0
];
}
/**
* 调用Open Food Facts API
* @param string $barcode
* @param string $countryCode
* @param int $timeout
* @return array
*/
private function callOpenFoodFactsAPI($barcode, $countryCode = 'world', $timeout = 10)
{
// 清理条码,只保留数字
$cleanBarcode = preg_replace('/[^0-9]/', '', $barcode);
if (empty($cleanBarcode)) {
return [
'error' => true,
'message' => '无效的条码格式',
'http_code' => 0
];
}
// 构建API URL
$baseUrl = "https://{$countryCode}.openfoodfacts.org";
$apiUrl = "{$baseUrl}/api/v0/product/{$cleanBarcode}.json";
// 初始化cURL
$ch = curl_init();
// 设置cURL选项
curl_setopt_array($ch, [
CURLOPT_URL => $apiUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_USERAGENT => 'KitchenScaleApp/1.0 (PHP-cURL)',
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Accept-Charset: utf-8'
]
]);
// 执行请求
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// 错误处理
if ($response === false) {
$errorMsg = curl_error($ch);
curl_close($ch);
return [
'error' => true,
'message' => "cURL请求失败: {$errorMsg}",
'http_code' => $httpCode
];
}
curl_close($ch);
// 解析JSON响应
$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'error' => true,
'message' => 'JSON解析失败: ' . json_last_error_msg(),
'http_code' => $httpCode,
'raw_response' => $response
];
}
// 检查API返回状态
if (!isset($data['status']) || $data['status'] !== 1) {
return [
'error' => true,
'message' => '商品未找到或条码无效',
'http_code' => 404,
'api_status' => $data['status'] ?? 'unknown'
];
}
// 返回成功结果
return [
'error' => false,
'data' => $data['product'],
'http_code' => $httpCode,
'barcode' => $cleanBarcode,
'api_source' => $countryCode
];
}
/**
* 处理API返回的商品数据
* @param array $productData
* @param string $barcode
* @return array
*/
private function processApiProductData($productData, $barcode)
{
$result = [
'code' => $barcode,
'name' => $this->getProductName($productData),
'pic_sp' => $this->getProductImage($productData),
'pic_yy' => '',
'Calorie' => '0',
'Protein' => '0',
'Fat' => '0',
'Carbohydrate' => '0',
'other_nutrition' => '',
'original_data' => json_encode($productData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT),
'create_time' => date('Y-m-d H:i:s')
];
// 提取营养信息每100克
$nutriments = $productData['nutriments'] ?? [];
// 处理四大营养素 - 优先使用_per_100g字段
$energyKcal = $nutriments['energy-kcal_100g'] ?? $nutriments['energy-kcal'] ?? 0;
$protein = $nutriments['proteins_100g'] ?? $nutriments['proteins'] ?? 0;
$fat = $nutriments['fat_100g'] ?? $nutriments['fat'] ?? 0;
$carbohydrate = $nutriments['carbohydrates_100g'] ?? $nutriments['carbohydrates'] ?? 0;
$result['Calorie'] = $this->formatNutritionValue($energyKcal);
$result['Protein'] = $this->formatNutritionValue($protein);
$result['Fat'] = $this->formatNutritionValue($fat);
$result['Carbohydrate'] = $this->formatNutritionValue($carbohydrate);
// 处理其他营养素
$otherNutrients = $this->extractOtherNutrients($nutriments);
if (!empty($otherNutrients)) {
$result['other_nutrition'] = json_encode($otherNutrients, JSON_UNESCAPED_UNICODE);
}
return $result;
}
/**
* 提取其他营养素信息
* @param array $nutriments
* @return array
*/
private function extractOtherNutrients($nutriments)
{
$nutrients = [];
// 定义营养素映射关系
$nutrientMap = [
// 糖类
'sugars_100g' => ['name' => 'Sugar', 'name_ch' => '糖', 'unit' => 'g', 'type' => 1, 'type_name' => '能量及宏量营养素'],
// 纤维
'fiber_100g' => ['name' => 'Fiber', 'name_ch' => '膳食纤维', 'unit' => 'g', 'type' => 1, 'type_name' => '能量及宏量营养素'],
// 钠盐
'salt_100g' => ['name' => 'Salt', 'name_ch' => '盐', 'unit' => 'g', 'type' => 3, 'type_name' => '矿物质'],
'sodium_100g' => ['name' => 'Sodium', 'name_ch' => '钠', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
// 维生素
'vitamin-a_100g' => ['name' => 'VitaminA', 'name_ch' => '维生素A', 'unit' => 'μg RAE', 'type' => 2, 'type_name' => '维生素'],
'vitamin-c_100g' => ['name' => 'VitaminC', 'name_ch' => '维生素C', 'unit' => 'mg', 'type' => 2, 'type_name' => '维生素'],
'vitamin-d_100g' => ['name' => 'VitaminD', 'name_ch' => '维生素D', 'unit' => 'μg', 'type' => 2, 'type_name' => '维生素'],
'vitamin-e_100g' => ['name' => 'VitaminE', 'name_ch' => '维生素E', 'unit' => 'mg α-TE', 'type' => 2, 'type_name' => '维生素'],
// 矿物质
'calcium_100g' => ['name' => 'Calcium', 'name_ch' => '钙', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
'iron_100g' => ['name' => 'Iron', 'name_ch' => '铁', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
'magnesium_100g' => ['name' => 'Magnesium', 'name_ch' => '镁', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
'phosphorus_100g' => ['name' => 'Phosphorus', 'name_ch' => '磷', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
'potassium_100g' => ['name' => 'Potassium', 'name_ch' => '钾', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
'zinc_100g' => ['name' => 'Zinc', 'name_ch' => '锌', 'unit' => 'mg', 'type' => 3, 'type_name' => '矿物质'],
];
foreach ($nutrientMap as $key => $info) {
$value = $nutriments[$key] ?? 0;
// 跳过值为0或空的营养素
if ($value === 0 || $value === '' || $value === null || floatval($value) == 0) {
continue;
}
$formattedValue = $this->formatNutritionValue($value);
$nutrients[] = [
'name' => $info['name'],
'name_ch' => $info['name_ch'],
'unit' => $info['unit'],
'value' => $formattedValue,
'type' => $info['type'],
'type_name' => $info['type_name'],
'color' => $this->getNutrientColor($info['type'])
];
}
return $nutrients;
}
/**
* 构建响应数据
* @param array $product
* @return array
*/
private function buildResponseData($product)
{
// 从数据库获取ID如果没有则生成唯一ID
$recordId = $product['id'] ?? uniqid();
// 解析other_nutrition
$otherNutrition = [];
if (!empty($product['other_nutrition'])) {
$otherNutrition = json_decode($product['other_nutrition'], true) ?: [];
}
// 计算四大营养素比例
$protein = floatval($product['Protein'] ?? 0);
$fat = floatval($product['Fat'] ?? 0);
$carb = floatval($product['Carbohydrate'] ?? 0);
$total = $protein + $fat + $carb;
$proteinPercent = $total > 0 ? round(($protein / $total) * 100) : 0;
$fatPercent = $total > 0 ? round(($fat / $total) * 100) : 0;
$carbPercent = $total > 0 ? round(($carb / $total) * 100) : 0;
// 构建四大营养素数据
$nutrientsFour = [
[
'name' => '卡路里',
'unit' => 'kcal',
'color' => '',
'value' => $product['Calorie'] ?? '0',
'proportion' => 0
],
[
'name' => '蛋白质',
'unit' => 'g',
'color' => '#5180D8',
'value' => $product['Protein'] ?? '0',
'proportion' => (string)$proteinPercent
],
[
'name' => '脂肪',
'unit' => 'g',
'color' => '#ED7886',
'value' => $product['Fat'] ?? '0',
'proportion' => (string)$fatPercent
],
[
'name' => '碳水',
'unit' => 'g',
'color' => '#FFB169',
'value' => $product['Carbohydrate'] ?? '0',
'proportion' => (string)$carbPercent
]
];
// 构建完整营养素列表
$nutrientsList = [];
// 添加四大营养素
$nutrientsList[] = [
'name' => 'Calorie',
'name_ch' => '卡路里',
'unit' => 'kcal',
'value' => $product['Calorie'] ?? '0',
'type' => '1',
'type_name' => '能量及宏量营养素',
'color' => '#C4FFE0'
];
$nutrientsList[] = [
'name' => 'Protein',
'name_ch' => '蛋白质',
'unit' => 'g',
'value' => $product['Protein'] ?? '0',
'type' => '1',
'type_name' => '能量及宏量营养素',
'color' => '#C4FFE0'
];
$nutrientsList[] = [
'name' => 'Fat',
'name_ch' => '脂肪',
'unit' => 'g',
'value' => $product['Fat'] ?? '0',
'type' => '1',
'type_name' => '能量及宏量营养素',
'color' => '#C4FFE0'
];
$nutrientsList[] = [
'name' => 'Carbohydrate',
'name_ch' => '碳水化合物',
'unit' => 'g',
'value' => $product['Carbohydrate'] ?? '0',
'type' => '1',
'type_name' => '能量及宏量营养素',
'color' => '#C4FFE0'
];
// 添加其他营养素
foreach ($otherNutrition as $nutrient) {
$nutrientsList[] = $nutrient;
}
return [
'id' => (string)$recordId,
'record_id' => (string)$recordId,
'food_type' => 'product',
'name' => $product['name'] ?? '未知商品',
'pic_url' => $product['pic_sp'] ?? 'https://tc.pcxbc.com/food_img/none.png',
'kcal' => $product['Calorie'] ?? '0',
'unit' => 'g', // 每100克
'nutrients_four' => $nutrientsFour,
'nutrients_list' => $nutrientsList
];
}
/**
* 获取商品名称
* @param array $productData
* @return string
*/
private function getProductName($productData)
{
$possibleFields = [
'product_name',
'product_name_zh',
'product_name_cn',
'product_name_fr',
'product_name_en',
'generic_name',
'brands'
];
foreach ($possibleFields as $field) {
if (!empty($productData[$field]) && trim($productData[$field]) !== '') {
$name = trim($productData[$field]);
// 如果包含品牌,可以适当处理
if ($field === 'brands' && isset($productData['product_name'])) {
$name = $productData['product_name'];
}
return $name;
}
}
if (!empty($productData['categories'])) {
$categories = explode(',', $productData['categories']);
return trim($categories[0]) . ' (未命名商品)';
}
return '未知商品';
}
/**
* 获取商品图片
* @param array $productData
* @return string
*/
private function getProductImage($productData)
{
$imageFields = [
'image_url',
'image_front_url',
'image_small_url',
'image_thumb_url',
];
foreach ($imageFields as $field) {
if (!empty($productData[$field]) && filter_var($productData[$field], FILTER_VALIDATE_URL)) {
return $productData[$field];
}
}
return '';
}
/**
* 格式化营养值
* @param mixed $value
* @return string
*/
private function formatNutritionValue($value)
{
if (is_numeric($value)) {
$floatValue = (float)$value;
if ($floatValue == (int)$floatValue) {
return (string)(int)$floatValue;
}
$formatted = round($floatValue, 2);
return rtrim(rtrim($formatted, '0'), '.');
}
return '0';
}
/**
* 获取营养素颜色
* @param int $type
* @return string
*/
private function getNutrientColor($type)
{
$colors = [
1 => '#C4FFE0', // 能量及宏量营养素
2 => '#FFEFB7', // 维生素
3 => '#7DA8E0', // 矿物质
];
return $colors[$type] ?? '#CCCCCC';
}
/**
* 计算三大营养素比例(蛋白质、脂肪、碳水化合物)
* 使用bcmath函数进行高精度计算
*
* @param string $protein 蛋白质值
* @param string $fat 脂肪值
* @param string $carb 碳水化合物值
* @return array 包含三个比例值的数组 [protein_percent, fat_percent, carb_percent]
*/
private function calculateNutrientProportions($protein, $fat, $carb)
{
// 转换为字符串确保bc函数正常工作
$protein = (string)($protein ?? '0');
$fat = (string)($fat ?? '0');
$carb = (string)($carb ?? '0');
// 初始化比例为0
$proteinPercent = '0';
$fatPercent = '0';
$carbPercent = '0';
// 计算三大营养素总和
$total = '0';
$total = bcadd($total, $protein, 20);
$total = bcadd($total, $fat, 20);
$total = bcadd($total, $carb, 20);
// 如果总和大于0计算比例
if (bccomp($total, '0', 20) > 0) {
// 蛋白质比例 = (蛋白质 / 总和) * 100
if (bccomp($protein, '0', 20) > 0) {
$proteinPercent = bcmul(bcdiv($protein, $total, 20), '100', 20);
}
// 脂肪比例 = (脂肪 / 总和) * 100
if (bccomp($fat, '0', 20) > 0) {
$fatPercent = bcmul(bcdiv($fat, $total, 20), '100', 20);
}
// 碳水化合物比例 = (碳水化合物 / 总和) * 100
if (bccomp($carb, '0', 20) > 0) {
$carbPercent = bcmul(bcdiv($carb, $total, 20), '100', 20);
}
}
// 格式化比例保留两位小数去除多余的0
$proteinPercent = $this->formatPercentage($proteinPercent);
$fatPercent = $this->formatPercentage($fatPercent);
$carbPercent = $this->formatPercentage($carbPercent);
return [
'protein' => $proteinPercent,
'fat' => $fatPercent,
'carb' => $carbPercent
];
}
/**
* 格式化百分比数值
* 保留两位小数去除多余的0
*
* @param string $percentage 百分比字符串
* @return string 格式化后的百分比
*/
private function formatPercentage($percentage)
{
// 保留两位小数
$formatted = bcadd($percentage, '0', 2);
// 去除多余的0和小数点
$formatted = rtrim(rtrim($formatted, '0'), '.');
// 如果是空字符串返回0
return $formatted === '' ? '0' : $formatted;
}
}