import { Controller, Get, Post, Body, Param, HttpCode, HttpStatus, Put, Delete, Query, Logger, UseGuards, NotFoundException, } from '@nestjs/common'; import { ApiOperation, ApiBody, ApiResponse, ApiTags, ApiQuery } from '@nestjs/swagger'; import { DietRecordsService } from './diet-records.service'; import { NutritionAnalysisService } from './services/nutrition-analysis.service'; import { CreateDietRecordDto, UpdateDietRecordDto, GetDietHistoryQueryDto, DietRecordResponseDto, DietHistoryResponseDto, NutritionSummaryDto, FoodRecognitionRequestDto, FoodRecognitionResponseDto, FoodRecognitionToDietRecordsResponseDto } from '../users/dto/diet-record.dto'; import { NutritionAnalysisResponseDto } from './dto/nutrition-analysis.dto'; import { NutritionAnalysisRequestDto } from './dto/nutrition-analysis-request.dto'; import { NutritionAnalysisRecordsResponseDto, GetNutritionAnalysisRecordsQueryDto, NutritionAnalysisRecordDto, DeleteNutritionAnalysisRecordResponseDto } from './dto/nutrition-analysis-record.dto'; import { JwtAuthGuard } from '../common/guards/jwt-auth.guard'; import { CurrentUser } from '../common/decorators/current-user.decorator'; import { AccessTokenPayload } from '../users/services/apple-auth.service'; @ApiTags('diet-records') @Controller('diet-records') export class DietRecordsController { private readonly logger = new Logger(DietRecordsController.name); constructor( private readonly dietRecordsService: DietRecordsService, private readonly nutritionAnalysisService: NutritionAnalysisService, ) { } /** * 添加饮食记录 */ @UseGuards(JwtAuthGuard) @Post() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: '添加饮食记录' }) @ApiBody({ type: CreateDietRecordDto }) @ApiResponse({ status: 201, description: '成功添加饮食记录', type: DietRecordResponseDto }) async addDietRecord( @Body() createDto: CreateDietRecordDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`添加饮食记录 - 用户ID: ${user.sub}, 食物: ${createDto.foodName}`); return this.dietRecordsService.addDietRecord(user.sub, createDto); } /** * 获取饮食记录历史 */ @UseGuards(JwtAuthGuard) @Get() @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '获取饮食记录历史' }) @ApiQuery({ name: 'startDate', required: false, description: '开始日期' }) @ApiQuery({ name: 'endDate', required: false, description: '结束日期' }) @ApiQuery({ name: 'mealType', required: false, description: '餐次类型' }) @ApiQuery({ name: 'page', required: false, description: '页码' }) @ApiQuery({ name: 'limit', required: false, description: '每页数量' }) @ApiResponse({ status: 200, description: '成功获取饮食记录', type: DietHistoryResponseDto }) async getDietHistory( @Query() query: GetDietHistoryQueryDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`获取饮食记录 - 用户ID: ${user.sub}`); return this.dietRecordsService.getDietHistory(user.sub, query); } /** * 更新饮食记录 */ @UseGuards(JwtAuthGuard) @Put(':id') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '更新饮食记录' }) @ApiBody({ type: UpdateDietRecordDto }) @ApiResponse({ status: 200, description: '成功更新饮食记录', type: DietRecordResponseDto }) async updateDietRecord( @Param('id') recordId: string, @Body() updateDto: UpdateDietRecordDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`更新饮食记录 - 用户ID: ${user.sub}, 记录ID: ${recordId}`); return this.dietRecordsService.updateDietRecord(user.sub, parseInt(recordId), updateDto); } /** * 删除饮食记录 */ @UseGuards(JwtAuthGuard) @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: '删除饮食记录' }) @ApiResponse({ status: 204, description: '成功删除饮食记录' }) async deleteDietRecord( @Param('id') recordId: string, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`删除饮食记录 - 用户ID: ${user.sub}, 记录ID: ${recordId}`); const success = await this.dietRecordsService.deleteDietRecord(user.sub, parseInt(recordId)); if (!success) { throw new NotFoundException('饮食记录不存在'); } } /** * 获取营养汇总分析 */ @UseGuards(JwtAuthGuard) @Get('nutrition-summary') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '获取营养汇总分析' }) @ApiQuery({ name: 'mealCount', required: false, description: '分析的餐次数量,默认10' }) @ApiResponse({ status: 200, description: '成功获取营养汇总', type: NutritionSummaryDto }) async getNutritionSummary( @Query('mealCount') mealCount: string, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`获取营养汇总 - 用户ID: ${user.sub}`); const count = mealCount ? parseInt(mealCount) : 10; return this.dietRecordsService.getRecentNutritionSummary(user.sub, count); } /** * 根据图片URL识别食物并转换为饮食记录格式 */ @UseGuards(JwtAuthGuard) @Post('recognize-food-to-records') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '根据图片URL识别食物并转换为饮食记录格式' }) @ApiBody({ type: FoodRecognitionRequestDto }) @ApiResponse({ status: 200, description: '成功识别食物并转换为饮食记录格式', type: FoodRecognitionToDietRecordsResponseDto }) async recognizeFoodToDietRecords( @Body() requestDto: FoodRecognitionRequestDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`识别食物转饮食记录 - 用户ID: ${user.sub}, 图片URL: ${requestDto.imageUrl}`); return this.dietRecordsService.recognizeFoodToDietRecords( requestDto.imageUrl, requestDto.mealType ); } /** * 根据图片URL识别食物(原始格式) */ @UseGuards(JwtAuthGuard) @Post('recognize-food') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '根据图片URL识别食物' }) @ApiBody({ type: FoodRecognitionRequestDto }) @ApiResponse({ status: 200, description: '成功识别食物', type: FoodRecognitionResponseDto }) async recognizeFood( @Body() requestDto: FoodRecognitionRequestDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`识别食物 - 用户ID: ${user.sub}, 图片URL: ${requestDto.imageUrl}`); return this.dietRecordsService.recognizeFood( requestDto.imageUrl, requestDto.mealType ); } /** * 分析食物营养成分表图片 */ @UseGuards(JwtAuthGuard) @Post('analyze-nutrition-image') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '分析食物营养成分表图片' }) @ApiBody({ type: NutritionAnalysisRequestDto }) @ApiResponse({ status: 200, description: '成功分析营养成分表', type: NutritionAnalysisResponseDto }) @ApiResponse({ status: 400, description: '请求参数错误' }) @ApiResponse({ status: 401, description: '未授权访问' }) @ApiResponse({ status: 500, description: '服务器内部错误' }) async analyzeNutritionImage( @Body() requestDto: NutritionAnalysisRequestDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`分析营养成分表 - 用户ID: ${user.sub}, 图片URL: ${requestDto.imageUrl}`); if (!requestDto.imageUrl) { return NutritionAnalysisResponseDto.createError('请提供图片URL'); } try { // 传递用户ID以便保存分析记录 const result = await this.nutritionAnalysisService.analyzeNutritionImage(requestDto.imageUrl, user.sub); this.logger.log(`营养成分表分析完成 - 用户ID: ${user.sub}, 成功: ${result.success}, 营养素数量: ${result.data.length}`); // 转换旧的响应格式到新的通用格式 if (result.success) { return NutritionAnalysisResponseDto.createSuccess(result.data, result.message || '分析成功'); } else { return NutritionAnalysisResponseDto.createError(result.message || '分析失败'); } } catch (error) { this.logger.error(`营养成分表分析失败 - 用户ID: ${user.sub}, 错误: ${error instanceof Error ? error.message : String(error)}`); return NutritionAnalysisResponseDto.createError('营养成分表分析失败,请稍后重试'); } } /** * 获取营养成分分析记录 */ @UseGuards(JwtAuthGuard) @Get('nutrition-analysis-records') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '获取营养成分分析记录' }) @ApiQuery({ name: 'startDate', required: false, description: '开始日期' }) @ApiQuery({ name: 'endDate', required: false, description: '结束日期' }) @ApiQuery({ name: 'status', required: false, description: '分析状态' }) @ApiQuery({ name: 'page', required: false, description: '页码' }) @ApiQuery({ name: 'limit', required: false, description: '每页数量' }) @ApiResponse({ status: 200, description: '成功获取营养成分分析记录', type: NutritionAnalysisRecordsResponseDto }) async getNutritionAnalysisRecords( @Query() query: GetNutritionAnalysisRecordsQueryDto, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`获取营养成分分析记录 - 用户ID: ${user.sub}`); try { // 转换查询参数中的字符串为数字 const convertedQuery = { page: query.page ? parseInt(query.page, 10) : undefined, limit: query.limit ? parseInt(query.limit, 10) : undefined, startDate: query.startDate, endDate: query.endDate, status: query.status, }; const result = await this.nutritionAnalysisService.getAnalysisRecords(user.sub, convertedQuery); // 转换为DTO格式 const recordDtos: NutritionAnalysisRecordDto[] = result.records.map(record => ({ id: record.id, userId: record.userId, imageUrl: record.imageUrl, analysisResult: record.analysisResult, status: record.status || '', message: record.message || '', aiProvider: record.aiProvider || '', aiModel: record.aiModel || '', nutritionCount: record.nutritionCount || 0, createdAt: record.createdAt, updatedAt: record.updatedAt, })); return NutritionAnalysisRecordsResponseDto.createSuccess( recordDtos, result.total, result.page, result.limit ); } catch (error) { this.logger.error(`获取营养成分分析记录失败 - 用户ID: ${user.sub}, 错误: ${error instanceof Error ? error.message : String(error)}`); return NutritionAnalysisRecordsResponseDto.createError('获取营养成分分析记录失败,请稍后重试'); } } /** * 删除营养成分分析记录 */ @UseGuards(JwtAuthGuard) @Delete('nutrition-analysis-records/:id') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: '删除营养成分分析记录' }) @ApiResponse({ status: 200, description: '成功删除营养成分分析记录', type: DeleteNutritionAnalysisRecordResponseDto }) @ApiResponse({ status: 404, description: '营养分析记录不存在' }) async deleteNutritionAnalysisRecord( @Param('id') recordId: string, @CurrentUser() user: AccessTokenPayload, ): Promise { this.logger.log(`删除营养成分分析记录 - 用户ID: ${user.sub}, 记录ID: ${recordId}`); try { const success = await this.nutritionAnalysisService.deleteAnalysisRecord(user.sub, parseInt(recordId)); if (!success) { this.logger.warn(`删除营养成分分析记录失败 - 用户ID: ${user.sub}, 记录ID: ${recordId}, 记录不存在或无权限`); return DeleteNutritionAnalysisRecordResponseDto.createError('营养分析记录不存在或无权限删除'); } this.logger.log(`营养成分分析记录删除成功 - 用户ID: ${user.sub}, 记录ID: ${recordId}`); return DeleteNutritionAnalysisRecordResponseDto.createSuccess(); } catch (error) { this.logger.error(`删除营养成分分析记录失败 - 用户ID: ${user.sub}, 记录ID: ${recordId}, 错误: ${error instanceof Error ? error.message : String(error)}`); return DeleteNutritionAnalysisRecordResponseDto.createError('删除营养分析记录失败,请稍后重试'); } } }