Laravel中如何基于用户过敏原约束筛选符合要求的食谱
需求背景
我正在开发一款基于用户输入约束的膳食计划生成工具,过敏原是其中一项核心约束条件。最初设计了两张关联数据表:
- Recipes(食谱表):存储食谱基础信息
- Recipe_Allergens(食谱过敏原关联表):存储食谱对应的过敏原信息
二者为一对多关系,一个食谱可包含多种过敏原,通过Recipe ID关联。
示例数据表结构
Recipes表示例
| id | Recipe_Name | Type |
|---|---|---|
| 1 | Lasagna | Dinner |
Recipe_Allergens表示例
| id | Recipe_ID | Allergen_Description |
|---|---|---|
| 1 | 1 | Dairy |
| 2 | 1 | Gluten |
现有实现逻辑
用户输入表单中过敏原为复选框类型,其余字段为下拉选择框,因此先对过敏原参数做拼接/拆分处理:
$request->merge([ 'allergens' => implode(',', (array)$request->get('allergens')) ]); $meal = $request->meal_type; $allerg = $request->allergens; $allergenarray2 = explode(",", $allerg);
关联两张表查询符合约束的食谱ID:
$recipenew = Recipe::join('recipe_allergens', 'recipe_allergens.recipe_id', '=', 'recipe.id') ->where('recipe.type', $meal) ->whereNotIn('recipe_allergens.allergen_description', $allergenarray2) ->get('recipe.id');
将查询到的食谱插入膳食计划表:
$x = 0; while ($x < $days) { $recipeday = (new Carbon($request->date))->addDays($x); $recidnew = $recipenew[$x]->id; MealPlanDisplay::create([ 'Recipe_ID' => $recidnew, 'Day' => $recipeday, 'user_id' => $currentuserid, ]); $x = $x + 1; }
存在的逻辑问题
当前实现的查询逻辑存在漏洞:当用户同时提交「Dairy」和「Gluten」作为过敏原时,查询结果正常;但若用户仅提交「Dairy」作为过敏原,含有Dairy的Lasagna仍会被推荐。
原因是关联查询时,Lasagna对应的Gluten过敏原记录满足whereNotIn条件,会被单独返回,最终导致含有禁用过敏原的食谱被误判为符合要求。
修复方案
核心需求是:只要食谱含有任意一种用户提交的过敏原,就需要被排除。原来的行级过滤逻辑不符合要求,需要调整为基于食谱整体的校验逻辑,推荐两种实现方式:
方案1:使用Eloquent关联查询(推荐,更易维护)
首先在Recipe模型中定义和过敏原的关联关系:
// App\Models\Recipe.php public function allergens() { return $this->hasMany(RecipeAllergen::class, 'recipe_id'); }
查询时使用whereDoesntHave方法直接排除存在禁用过敏原的食谱:
$recipenew = Recipe::where('type', $meal) ->whereDoesntHave('allergens', function ($query) use ($allergenarray2) { // 筛选出关联过敏原在用户禁用列表中的食谱,上层doesntHave会排除这类食谱 $query->whereIn('allergen_description', $allergenarray2); }) ->get('id');
方案2:使用JOIN+分组聚合实现
如果需要直接用JOIN写法,可以按食谱ID分组,通过聚合函数判断该食谱是否含有禁用过敏原:
$recipenew = Recipe::join('recipe_allergens', 'recipe_allergens.recipe_id', '=', 'recipe.id') ->where('recipe.type', $meal) ->groupBy('recipe.id') // 统计该食谱下属于用户禁用过敏原的数量,数量为0才符合要求 ->havingRaw('SUM(CASE WHEN recipe_allergens.allergen_description IN (?) THEN 1 ELSE 0 END) = 0', [implode(',', $allergenarray2)]) ->get('recipe.id');
额外优化建议
现有写入逻辑未判断查询到的食谱数量是否满足需求天数,当符合要求的食谱数量小于$days时会出现数组越界错误,建议写入前先校验count($recipenew) >= $days,或增加随机取、重复利用符合要求的食谱的逻辑避免报错。
内容的提问来源于stack exchange,提问作者cleocoder
相关产品推荐
相关产品推荐

