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

903 lines
33 KiB
PHP
Raw Normal View History

2026-02-03 18:56:47 +08:00
<?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;
}
}