fix: interview creation show real error msg instead of generic failure

- Backend: wrap AI call in try/catch, throw HttpException(503)
  with msg 'AI 服务暂时不可用,请稍后重试'
- Backend: AllExceptionsFilter preserve original error message
  for non-HttpException errors
- Frontend: handle string/object res.data, show statusCode
  as fallback when message is unavailable
This commit is contained in:
yuzhiran
2026-07-03 15:32:22 +08:00
parent bd398f154a
commit 59fc35dad9
3 changed files with 17 additions and 8 deletions
@@ -16,7 +16,7 @@ export class AllExceptionsFilter implements ExceptionFilter {
const message = exception instanceof HttpException const message = exception instanceof HttpException
? exception.getResponse() ? exception.getResponse()
: '服务器内部错误'; : (exception as Error)?.message || '服务器内部错误';
const errorResponse = { const errorResponse = {
code: status, code: status,
@@ -1,4 +1,4 @@
import { Injectable, HttpException, HttpStatus } from '@nestjs/common' import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose' import { InjectModel } from '@nestjs/mongoose'
import { Model } from 'mongoose' import { Model } from 'mongoose'
import { Interview, InterviewDocument } from './interview.schema' import { Interview, InterviewDocument } from './interview.schema'
@@ -11,6 +11,8 @@ import { analyzeSpeech } from '../../common/utils/filler-words'
@Injectable() @Injectable()
export class InterviewService { export class InterviewService {
private readonly logger = new Logger(InterviewService.name)
constructor( constructor(
@InjectModel(Interview.name) private interviewModel: Model<InterviewDocument>, @InjectModel(Interview.name) private interviewModel: Model<InterviewDocument>,
@InjectModel(Progress.name) private progressModel: Model<ProgressDocument>, @InjectModel(Progress.name) private progressModel: Model<ProgressDocument>,
@@ -25,11 +27,17 @@ export class InterviewService {
const cost = await this.quotaService.checkInterview(userId) const cost = await this.quotaService.checkInterview(userId)
// Step 2: AI 生成第一个问题 // Step 2: AI 生成第一个问题
const firstQuestion = await this.aiService.call({ let firstQuestion: string
systemPrompt: `你是一位专业的${position}面试官。请针对校招该岗位提出第一个面试问题,要求具体且有针对性。直接输出问题,不要多余内容。`, try {
userMessage: `请为${position}岗位的校招候选人提出第一个面试问题。`, firstQuestion = await this.aiService.call({
temperature: 0.8, systemPrompt: `你是一位专业的${position}面试官。请针对校招该岗位提出第一个面试问题,要求具体且有针对性。直接输出问题,不要多余内容。`,
}) userMessage: `请为${position}岗位的校招候选人提出第一个面试问题。`,
temperature: 0.8,
})
} catch (e) {
this.logger.error(`AI call failed for interview create: ${(e as Error).message}`)
throw new HttpException('AI 服务暂时不可用,请稍后重试', HttpStatus.SERVICE_UNAVAILABLE)
}
// Step 3: AI 调用成功后,扣除引力值并创建面试记录 // Step 3: AI 调用成功后,扣除引力值并创建面试记录
await this.quotaService.deductInterview(userId, cost) await this.quotaService.deductInterview(userId, cost)
+2 -1
View File
@@ -242,7 +242,8 @@ const startInterview = async () => {
} else if (checkAuth(res)) { } else if (checkAuth(res)) {
return // token 过期,已清除并跳转登录 return // token 过期,已清除并跳转登录
} else { } else {
const msg = res.data?.message || '创建面试失败' const errMsg = typeof res.data === 'string' ? res.data : (res.data?.message || '')
const msg = errMsg || `创建面试失败(${res.statusCode}`
messages.value.push({ role: 'ai', content: msg }) messages.value.push({ role: 'ai', content: msg })
} }
} catch { } catch {