This commit is contained in:
richarjiang
2025-08-29 09:44:35 +08:00
parent 74faebd73d
commit 8a69f4f1af
2 changed files with 235 additions and 102 deletions

View File

@@ -15,109 +15,9 @@ export class FoodLibraryService {
) { }
/**
* 获取食物库列表,按分类组织
* 常见食物会被归类到"常见"分类中
* 将食物模型转换为DTO
*/
async getFoodLibrary(): Promise<FoodLibraryResponseDto> {
// 获取所有分类,按排序顺序
const categories = await this.foodCategoryModel.findAll({
order: [['sortOrder', 'ASC']],
});
const result: FoodCategoryDto[] = [];
for (const category of categories) {
let foods: FoodLibrary[] = [];
if (category.key === 'common') {
// 常见分类:获取所有标记为常见的食物
foods = await this.foodLibraryModel.findAll({
where: { isCommon: true },
order: [['sortOrder', 'ASC'], ['name', 'ASC']],
});
} else {
// 其他分类:获取该分类下的非常见食物
foods = await this.foodLibraryModel.findAll({
where: {
categoryKey: category.key,
isCommon: false
},
order: [['sortOrder', 'ASC'], ['name', 'ASC']],
});
}
const foodDtos: FoodItemDto[] = foods.map(food => ({
id: food.id,
name: food.name,
description: food.description,
caloriesPer100g: food.caloriesPer100g,
proteinPer100g: food.proteinPer100g,
carbohydratePer100g: food.carbohydratePer100g,
fatPer100g: food.fatPer100g,
fiberPer100g: food.fiberPer100g,
sugarPer100g: food.sugarPer100g,
sodiumPer100g: food.sodiumPer100g,
additionalNutrition: food.additionalNutrition,
isCommon: food.isCommon,
imageUrl: food.imageUrl,
sortOrder: food.sortOrder,
}));
result.push({
key: category.key,
name: category.name,
icon: category.icon,
sortOrder: category.sortOrder,
isSystem: category.isSystem,
foods: foodDtos,
});
}
return { categories: result };
}
/**
* 根据关键词搜索食物
*/
async searchFoods(keyword: string): Promise<FoodItemDto[]> {
const foods = await this.foodLibraryModel.findAll({
where: {
name: {
[Op.like]: `%${keyword}%`
}
},
order: [['isCommon', 'DESC'], ['name', 'ASC']],
limit: 50, // 限制搜索结果数量
});
return foods.map(food => ({
id: food.id,
name: food.name,
description: food.description,
caloriesPer100g: food.caloriesPer100g,
proteinPer100g: food.proteinPer100g,
carbohydratePer100g: food.carbohydratePer100g,
fatPer100g: food.fatPer100g,
fiberPer100g: food.fiberPer100g,
sugarPer100g: food.sugarPer100g,
sodiumPer100g: food.sodiumPer100g,
additionalNutrition: food.additionalNutrition,
isCommon: food.isCommon,
imageUrl: food.imageUrl,
sortOrder: food.sortOrder,
}));
}
/**
* 根据ID获取食物详情
*/
async getFoodById(id: number): Promise<FoodItemDto | null> {
const food = await this.foodLibraryModel.findByPk(id);
if (!food) {
return null;
}
private mapFoodToDto(food: FoodLibrary): FoodItemDto {
return {
id: food.id,
name: food.name,
@@ -135,4 +35,112 @@ export class FoodLibraryService {
sortOrder: food.sortOrder,
};
}
/**
* 获取食物库列表,按分类组织
* 常见食物会被归类到"常见"分类中
*/
async getFoodLibrary(): Promise<FoodLibraryResponseDto> {
try {
// 一次性获取所有分类和食物数据避免N+1查询
const [categories, allFoods, commonFoods] = await Promise.all([
// 获取所有分类
this.foodCategoryModel.findAll({
order: [['sortOrder', 'ASC']],
}),
// 获取所有非常见食物
this.foodLibraryModel.findAll({
where: { isCommon: false },
order: [['sortOrder', 'ASC'], ['name', 'ASC']],
}),
// 获取所有常见食物
this.foodLibraryModel.findAll({
where: { isCommon: true },
order: [['sortOrder', 'ASC'], ['name', 'ASC']],
}),
]);
// 将食物按分类分组
const foodsByCategory = new Map<string, FoodLibrary[]>();
allFoods.forEach(food => {
const categoryKey = food.categoryKey;
if (!foodsByCategory.has(categoryKey)) {
foodsByCategory.set(categoryKey, []);
}
foodsByCategory.get(categoryKey)!.push(food);
});
// 构建结果
const result: FoodCategoryDto[] = categories.map(category => {
let foods: FoodLibrary[] = [];
if (category.key === 'common') {
// 常见分类:使用常见食物
foods = commonFoods;
} else {
// 其他分类:使用该分类下的非常见食物
foods = foodsByCategory.get(category.key) || [];
}
return {
key: category.key,
name: category.name,
icon: category.icon,
sortOrder: category.sortOrder,
isSystem: category.isSystem,
foods: foods.map(food => this.mapFoodToDto(food)),
};
});
return { categories: result };
} catch (error) {
throw new Error(`Failed to get food library: ${error.message}`);
}
}
/**
* 根据关键词搜索食物
*/
async searchFoods(keyword: string): Promise<FoodItemDto[]> {
try {
if (!keyword || keyword.trim().length === 0) {
return [];
}
const foods = await this.foodLibraryModel.findAll({
where: {
name: {
[Op.like]: `%${keyword.trim()}%`
}
},
order: [['isCommon', 'DESC'], ['name', 'ASC']],
limit: 50,
});
return foods.map(food => this.mapFoodToDto(food));
} catch (error) {
throw new Error(`Failed to search foods: ${error.message}`);
}
}
/**
* 根据ID获取食物详情
*/
async getFoodById(id: number): Promise<FoodItemDto | null> {
try {
if (!id || id <= 0) {
return null;
}
const food = await this.foodLibraryModel.findByPk(id);
if (!food) {
return null;
}
return this.mapFoodToDto(food);
} catch (error) {
throw new Error(`Failed to get food by id: ${error.message}`);
}
}
}