refactor(api): 统一API响应格式规范

重构营养成分分析相关接口,统一使用base.dto.ts中定义的通用响应结构体ApiResponseDto,规范所有接口返回格式。更新AI模型prompt以返回标准化的code、msg、data结构,并添加相应的验证装饰器确保数据完整性。
This commit is contained in:
richarjiang
2025-10-16 11:16:33 +08:00
parent 2f2901a0bf
commit 91cac3134e
8 changed files with 138 additions and 66 deletions

View File

@@ -1,6 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { OpenAI } from 'openai';
import { ResponseCode } from '../../base.dto';
/**
* 营养成分分析结果接口
@@ -14,6 +15,7 @@ export interface NutritionAnalysisResult {
/**
* 营养成分分析响应接口
* 保持向后兼容,内部使用,外部使用 NutritionAnalysisResponseDto
*/
export interface NutritionAnalysisResponse {
success: boolean;
@@ -78,7 +80,7 @@ export class NutritionAnalysisService {
const completion = await this.makeVisionApiCall(prompt, [imageUrl]);
const rawResult = completion.choices?.[0]?.message?.content || '[]';
const rawResult = completion.choices?.[0]?.message?.content || '{"code": 1, "msg": "未获取到AI模型响应", "data": []}';
this.logger.log(`营养成分分析原始结果: ${rawResult}`);
return this.parseNutritionAnalysisResult(rawResult);
@@ -152,24 +154,36 @@ export class NutritionAnalysisService {
**任务要求:**
1. 识别图片中的营养成分表,提取所有可见的营养素信息
2. 为每个营养素提供详细的健康建议和分析
3. 返回严格的JSON数组格式,包含任何额外的解释或对话文本
3. 返回严格的JSON格式包含code、msg和data字段
**输出格式要求:**
请严格按照以下JSON数组格式返回,每个对象包含四个字段
[
{
"key": "energy_kcal",
"name": "热量",
"value": "840千焦",
"analysis": "840千焦约等于201卡路里占成人每日推荐摄入总热量的10%,属于中等热量水平。"
},
{
"key": "protein",
"name": "蛋白质",
"value": "12.5g",
"analysis": "12.5克蛋白质占成人每日推荐摄入量的21%,是良好的蛋白质来源,有助于肌肉修复和生长。"
}
]
请严格按照以下JSON格式返回
{
"code": 0,
"msg": "分析成功",
"data": [
{
"key": "energy_kcal",
"name": "热量",
"value": "840千焦",
"analysis": "840千焦约等于201卡路里占成人每日推荐摄入总热量的10%,属于中等热量水平。"
},
{
"key": "protein",
"name": "蛋白质",
"value": "12.5g",
"analysis": "12.5克蛋白质占成人每日推荐摄入量的21%,是良好的蛋白质来源,有助于肌肉修复和生长。"
}
]
}
**失败情况格式:**
如果无法识别营养成分表或分析失败,请返回:
{
"code": 1,
"msg": "失败原因的具体描述",
"data": []
}
**营养素标识符对照表:**
- 热量/能量: energy_kcal
@@ -197,16 +211,18 @@ export class NutritionAnalysisService {
- 其他营养素: other_nutrient
**分析要求:**
1. 如果图片中没有营养成分表,返回空数组 []
2. 为每个识别到的营养素提供具体的健康建议
3. 建议应包含营养素的作用、摄入量参考和健康影响
4. 数值分析要准确,建议要专业且实用
5. 只返回JSON数组不要包含任何其他文本
1. 如果成功识别营养成分表,code设为0msg为"分析成功"
2. 如果无法识别或分析失败code设为1msg详细说明失败原因
3. 为每个识别到的营养素提供具体的健康建议
4. 建议应包含营养素的作用、摄入量参考和健康影响
5. 数值分析要准确,建议要专业且实用
6. 只返回JSON对象不要包含任何其他文本
**重要提醒:**
- 严格按照JSON数组格式返回
- 严格按照JSON对象格式返回包含code、msg和data字段
- 不要添加任何解释性文字或对话内容
- 确保JSON格式正确可以被直接解析`;
- 确保JSON格式正确可以被直接解析
- 必须返回完整的JSON结构即使分析失败也要返回code和msg字段`;
}
/**
@@ -230,9 +246,9 @@ export class NutritionAnalysisService {
};
}
// 确保结果是数组
if (!Array.isArray(parsedResult)) {
this.logger.error(`营养成分分析结果不是数组格式: ${typeof parsedResult}`);
// 检查响应格式 {code, msg, data}
if (!parsedResult || typeof parsedResult !== 'object' || !('code' in parsedResult)) {
this.logger.error(`营养成分分析结果格式不正确: ${JSON.stringify(parsedResult)}`);
return {
success: false,
data: [],
@@ -240,10 +256,32 @@ export class NutritionAnalysisService {
};
}
const { code, msg, data } = parsedResult;
// 如果大模型返回失败状态
if (code === ResponseCode.ERROR) {
this.logger.warn(`大模型分析失败: ${msg || '未知错误'}`);
return {
success: false,
data: [],
message: msg || '营养成分表分析失败'
};
}
// 检查data是否为数组
if (!Array.isArray(data)) {
this.logger.error(`营养成分分析data字段不是数组格式: ${typeof data}`);
return {
success: false,
data: [],
message: '营养成分表格式错误data字段应为数组'
};
}
// 验证和标准化每个营养素项
const nutritionData: NutritionAnalysisResult[] = [];
for (const item of parsedResult) {
for (const item of data) {
if (item && typeof item === 'object' && item.key && item.name && item.value && item.analysis) {
nutritionData.push({
key: String(item.key).trim(),
@@ -260,7 +298,7 @@ export class NutritionAnalysisService {
return {
success: false,
data: [],
message: '图片中未检测到有效的营养成分表信息'
message: msg || '图片中未检测到有效的营养成分表信息'
};
}
@@ -270,6 +308,7 @@ export class NutritionAnalysisService {
success: true,
data: nutritionData
};
} catch (error) {
this.logger.error(`营养成分分析结果处理失败: ${error instanceof Error ? error.message : String(error)}`);
return {