1项目概述

AI面试官 是一款集智能简历优化、模拟面试实战、心理陪伴辅导于一体的全栈应用。不同于传统的求职平台,我们不仅关注"技术能力提升",更重视"心理状态关怀"——求职不仅是技能的比拼,更是心态的考验。

2技术架构

2.1整体架构图

🤖 Dify工作流

📁 数据库表

REST API

前端: HTML5 + CSS3 + JavaScript

后端: FastAPI

MySQL数据库

Dify AI工作流平台

Users

Profiles

InterviewSessions

InterviewMessages

ResumeOptimizations

PepTalkSessions

自我介绍生成器

模拟面试官

简历优化器

评估报告生成器

心理陪伴Chat

2.2技术栈选择

层次 技术 选型理由
前端 HTML5 + CSS3 + JavaScript 轻量快速,无需构建工具,快速交付
前端框架 原生JS + Axios 简单直接,适合中小项目快速迭代
后端框架 FastApi 高性能异步支持,自动生成API文档,开发体验极佳
ORM SQLAlchemy 成熟稳定,支持多种数据库,代码可读性强
数据库 MySQL 开源稳定,社区活跃,适合中小型应用
AI服务 Dify 低代码构建AI工作流,支持流式响应,便于快速迭代
认证 JWT 无状态认证,轻量级,易于扩展

3核心功能模块

3.1用户认证模块

设计特点:Token仅用于身份识

# backend/routers/auth_router.py
@app.post("/api/auth/login")
async def login(req: LoginRequest, db: Session = Depends(get_db)):
    """登录接口 — 验证用户并返回JWT Token"""
    user = db.query(User).filter(User.username == req.username).first()
    if not user or not bcrypt.verify(req.password, user.password_hash):
        raise HTTPException(401, "用户名或密码错误")
    
    access_token = create_access_token(data={"sub": user.username})
    return TokenResponse(access_token=access_token)

安全性考量

  • 使用 bcrypt 进行密码哈希存储(12轮加盐)
  • JWT Token 设置合理过期时间(默认1440分钟,即24小时)
  • 前端自动拦截401响应,跳转登录页并清除本地存储

3.2智能简历优化

核心流程:

上传文件 → 解析文本 → 调用Dify工作流 → 返回优化结果 → 保存历史

文件解析支持:
backend/services/file_parser.py

def parse_resume_file(filename: str, content: bytes) -> str:
    """解析不同格式的简历文件"""
    ext = filename.lower().split('.')[-1]
    
    if ext == 'txt':
        return content.decode('utf-8', errors='ignore')
    elif ext == 'pdf':
        with BytesIO(content) as f:
            reader = PyPDF2.PdfReader(f)
            return '\n'.join(page.extract_text() for page in reader.pages)
    elif ext == 'docx':
        with BytesIO(content) as f:
            doc = Document(f)
            return '\n'.join(para.text for para in doc.paragraphs)
    else:
        return content.decode('utf-8', errors='ignore')

优化效果:通过 Dify 工作流实现简历的智能分析和优化建议生成,支持自定义目标岗位进行针对性优化。

3.3模拟面试系统

核心流程:

用户配置面试参数 → 调用Dify工作流生成开场问题 → 多轮问答交互 → 结束面试生成评估报告。

关键环节:

  • 面试类型支持技术面/HR面/综合面
  • 基于简历快照个性化提问
  • 结束时通过独立工作流生成结构化报告(含各维度评分、优势分析、改进建议)。

面试类型:

  • 技术面:考察专业技能和技术深度
  • HR面:考察软技能和价值观匹配
  • 综合面:全面评估候选人能力

关键流程:
backend/routers/interview_router.py

@router.post("/start")
async def do_start_interview(req: StartInterviewRequest, ...):
    """开始新面试会话"""
    # 调用Dify工作流获取开场问题
    result = await start_interview(
        req.interview_type, 
        req.position, 
        req.resume_snapshot
    )
    
    # 创建会话记录
    session = InterviewSession(
        user_id=current_user.id,
        interview_type=req.interview_type,
        position=req.position,
        ...
    )
    db.add(session)
    db.commit()
    
    return StartInterviewResponse(
        session_id=session.id, 
        opening_message=result["answer"]
    )

评估报告生成:

@router.post("/{session_id}/end")
async def do_end_interview(session_id: int, ...):
    """结束面试,生成评估报告"""
    # 调用Dify评估报告工作流
    report_result = await generate_interview_report(
        interview_type=session.interview_type,
        position=session.position,
        messages=[{"role": m.role, "content": m.content} for m in messages]
    )
    
    session.overall_score = report_result.get("overall_score", 7)
    session.report_json = report_result["report_json"]
    session.end_time = datetime.utcnow()
    db.commit()

报告内容结构:
📊 各维度评分(技术能力、沟通能力、表达能力等)
✅ 优势亮点
🔧 需要提升
💡 提升建议
🔄 逐轮反馈
如图:
在这里插入图片描述

3.4心理陪伴系统

设计理念:温暖共情+危机干预

3.4.1 AI情感陪伴对话

backend/services/dify.py

async def chat_pep_talk(user_id: int, conversation_id: str, message: str) -> dict:
    """心理陪伴对话 — 检测危机信号"""
    r = await _call_chat(DIFY_PEPTALK_API_KEY, {}, message, conversation_id, user=str(user_id))
    answer = r["answer"]
    
    # 危机检测
    is_crisis = _detect_crisis(message)
    if is_crisis:
        answer += "\n\n" + CRISIS_HELPLINE
    
    return {"ai_message": answer, "conversation_id": r["conversation_id"], "is_crisis": is_crisis}

危机干预机制:

def _detect_crisis(text: str) -> bool:
    """检测自伤/自杀倾向关键词"""
    keywords = ["自伤", "自杀", "不想活", "结束生命", "伤害自己", "自残", "想死", "活不下去", "死了算了"]
    return any(kw in text for kw in keywords)

触发危机干预时自动追加援助热线:
热线名称 电话号码
北京心理危机干预中心 010-82951332
全国24小时心理援助热线 400-161-9995
生命热线 400-821-1215
改进计划:当前方案简单有效,但未来会引入情感分析模型(如基于BERT的轻量级分类器)提高准确率,同时设计用户反馈机制来优化关键词库。

3.4.2 每日励志语录

DAILY_QUOTES = [
    "每一次面试都是一次成长的机会,不要害怕失败。",
    "你的价值不是由一份工作决定的,而是由你的坚持和勇气定义的。",
    "求职路上,不放弃就已经赢了一半。今天也要加油!",
    "面试不是审判,而是一次双向选择的对话。你同样在评估他们。",
    "没有人一出生就会面试,所有技巧都是练出来的。",
    "焦虑的反面不是平静,而是行动。迈出第一步,焦虑就会减半。",
    "今天你投出的每一份简历,都在为明天的offer铺路。",
    "你不是在找工作,你是在找一段新的成长旅程。",
]

@router.get("/daily-quote")
def get_daily_quote():
    """每日一句励志语录 — 根据日期生成固定索引"""
    today = date.today()
    day_index = today.toordinal() % len(DAILY_QUOTES)
    return {"quote": DAILY_QUOTES[day_index], "date": today.isoformat()}

技术改进:最初使用 random.seed(date) 实现每日一句,但发现random是全局种子可能影响其他随机功能。改用 date.toordinal() % len(DAILY_QUOTES) 更简洁且安全。

3.4.3情绪匹配推荐

根据用户情绪状态推荐音乐和视频资源:

情绪 推荐类型 示例
😰 焦虑 轻音乐 Weightless - Marconi Union(科学验证降低65%焦虑)
😕 迷茫 励志视频 TED演讲《如何找到你真正热爱的工作》
😴 疲惫 白噪音 雨声、海浪声助眠
😞 挫败 激励音乐 《追梦赤子心》- GALA

4为什么选择dify而不是直接调用大模型API?

最终选择Dify主要有以下几个原因:

  1. 低代码编排工作流:简历优化、面试评估等复杂prompt链 以可视化调整,无需改后端代码。这对于快速迭代非常重要。
  2. 内置RAG和知识库:未来计划对接企业面试题库,Dify已经提供了完整的知识库管理功能。
  3. 成本可控:相比自建LLM网关,Dify提供免费社区版和清晰的API计费,适合初创项目。
  4. 流式响应支持:心理陪伴模块需要实时响应,Dify的Chat API天然支持流式输出。
  5. 团队协作友好:可以邀请团队成员共同编辑工作流,便于知识沉淀和协作开发。

5数据库设计

核心数据表关系

has

has

has

has

contains

contains

Users

int

id

PK

varchar

username

UK

varchar

password_hash

datetime

created_at

Profiles

int

id

PK

int

user_id

FK

varchar

full_name

varchar

education

int

work_years

varchar

target_position

text

resume_text

text

self_intro_text

InterviewSessions

int

id

PK

int

user_id

FK

varchar

interview_type

varchar

position

int

duration

json

report_json

datetime

start_time

datetime

end_time

ResumeOptimizations

int

id

PK

int

user_id

FK

text

original_resume

text

optimized_resume

text

suggestions

datetime

created_at

PepTalkSessions

int

id

PK

int

user_id

FK

datetime

start_time

datetime

end_time

varchar

topic

InterviewMessages

int

id

PK

int

session_id

FK

text

message_content

datetime

timestamp

varchar

sender_type

PepTalkMessages

int

id

PK

int

session_id

FK

text

message

datetime

timestamp

varchar

sender

表关系说明:

  • Users Profiles 是一对一关系(一个用户对应一个档案)
  • Users InterviewSessions 是一对多关系(一个用户可以有多次面试)
  • InterviewSessions InterviewMessages 是一对多关系(一次面试有多条消息)

6前端实现

6.1路由管理

frontend/js/router.js

const Router = {
    currentPage: 'profile',
    
    init() {
        this.renderSidebar();
        this.go('profile');
    },
    
    go(page) {
        this.currentPage = page;
        document.querySelectorAll('.page').forEach(p => p.style.display = 'none');
        document.getElementById(`page-${page}`).style.display = 'block';
        this.updateSidebar();
        
        // 调用对应页面渲染函数
        const pageHandlers = {
            profile: () => PageProfile.render(),
            interview: () => PageInterview.render(),
            resume: () => PageResume.render(),
            peptalk: () => PagePepTalk.render(),
            history: () => PageHistory.render(),
        };
        pageHandlers[page]?.();
    },
    ...
};

页面模块说明:每个页面模块(如 PageProfile)负责动态渲染HTML和绑定事件,代码位于 frontend/js/pages/ 目录下。

6.2聊天界面实现

// 消息发送流程
const PageInterview = {
    async send() {
        const text = document.getElementById('iv-user-input').value.trim();
        if (!text || !ivSessionId) return;
        
        // 显示用户消息
        this.addMsg('user', text);
        
        // 显示加载状态
        const loadingId = this.addMsg('ai', '<div class="spinner"></div>');
        
        // 调用API
        const res = await InterviewAPI.respond(ivSessionId, text);
        
        // 更新AI回复
        this.updateMsg(loadingId, res.data.ai_message);
    },
    
    addMsg(role, content) {
        const div = document.createElement('div');
        div.className = `msg-bubble ${role}`;
        div.innerHTML = `<div class="msg-label">${role==='ai'?'🤖 面试官':'👤 你'}</div><div>${content}</div>`;
        document.getElementById('iv-messages').appendChild(div);
    },
    ...
};

7踩过的坑

7.1坑1:Dify工作流超时与异步任务解耦

问题深度剖析:

  • Dify工作流执行评估报告时,涉及多轮LLM调用和复杂逻辑处理,平均耗时45-90秒
  • 前端HTTP请求默认超时30秒,导致用户端显示失败但后端仍在执行
  • 重试机制缺失,失败后需用户手动重新提交
  • 高并发场景下,长时间阻塞的请求会占用大量服务器资源。
# backend/services/task_queue.py - 引入异步任务队列
from celery import Celery
from celery.exceptions import TimeoutError
from config import REDIS_URL

celery_app = Celery('tasks', broker=REDIS_URL, backend=REDIS_URL)

@celery_app.task(bind=True, max_retries=3, retry_backoff=2)
def async_generate_report(self, interview_type, position, messages):
    """异步生成评估报告,支持重试和任务追踪"""
    try:
        return generate_interview_report(interview_type, position, messages)
    except Exception as e:
        # 指数退避重试
        self.retry(exc=e, countdown=2 ** self.request.retries)
# backend/routers/interview_router.py - 重构后的结束面试接口
@router.post("/{session_id}/end")
async def do_end_interview(session_id: int, ...):
    """结束面试 - 异步生成报告,返回任务ID"""
    # 1. 先更新会话状态为"处理中"
    session.status = "processing"
    db.commit()
    
    # 2. 提交异步任务
    task = async_generate_report.delay(
        interview_type=session.interview_type,
        position=session.position,
        messages=[{"role": m.role, "content": m.content} for m in messages]
    )
    
    # 3. 返回任务ID,前端轮询获取结果
    return {"task_id": task.id, "status": "processing"}

@router.get("/report/task/{task_id}")
async def get_report_task(task_id: str):
    """查询报告生成任务状态"""
    task = async_generate_report.AsyncResult(task_id)
    
    if task.state == 'SUCCESS':
        # 更新数据库并返回结果
        result = task.result
        return {"status": "completed", "report": result}
    elif task.state == 'PENDING':
        return {"status": "processing"}
    elif task.state == 'FAILURE':
        return {"status": "failed", "error": str(task.info)}

架构优化要点:

  • 使用Celery + Redis实现异步任务队列
  • 前端通过轮询或WebSocket获取任务状态
  • 实现指数退避重试策略(2^n秒间隔)
  • 添加任务超时监控和告警机制

7.2坑2:PDF解析中文乱码

问题:某些PDF文件使用GBK编码,直接用UTF-8解析会出现乱码。

解决:检测编码并指定合适的解码方式。

try:
    return content.decode('utf-8')
except UnicodeDecodeError:
    return content.decode('gbk', errors='ignore')

7.3坑3:前端状态管理与JWT安全实践

问题深度剖析:

  • localStorage存在XSS攻击风险
  • Token过期后需要无感刷新
  • 多Tab页签登录状态不同步
  • 敏感操作缺少双重验证
// frontend/js/auth.js - 安全的认证管理
class AuthManager {
    constructor() {
        this.tokenKey = 'ai_interview_token';
        this.refreshTokenKey = 'ai_interview_refresh_token';
        this.setupInterceptors();
        this.setupStorageSync();
    }

    // 使用HttpOnly Cookie存储Refresh Token
    getToken() {
        return localStorage.getItem(this.tokenKey);
    }

    setToken(accessToken, refreshToken) {
        localStorage.setItem(this.tokenKey, accessToken);
        // Refresh Token通过HttpOnly Cookie传递,前端不可访问
        document.cookie = `refresh_token=${refreshToken}; HttpOnly; Secure; SameSite=Strict`;
    }

    // 无感Token刷新
    async refreshToken() {
        try {
            const response = await api.post('/api/auth/refresh');
            const newToken = response.data.access_token;
            localStorage.setItem(this.tokenKey, newToken);
            return newToken;
        } catch {
            this.clearAuth();
            throw new Error('登录已过期,请重新登录');
        }
    }

    // 请求拦截器:自动重试和Token刷新
    setupInterceptors() {
        api.interceptors.response.use(
            response => response,
            async error => {
                const originalRequest = error.config;
                
                if (error.response.status === 401 && !originalRequest._retry) {
                    originalRequest._retry = true;
                    try {
                        await this.refreshToken();
                        originalRequest.headers.Authorization = `Bearer ${this.getToken()}`;
                        return api(originalRequest);
                    } catch {
                        window.location.href = '/login.html';
                    }
                }
                return Promise.reject(error);
            }
        );
    }

    // 跨Tab页签状态同步
    setupStorageSync() {
        window.addEventListener('storage', (e) => {
            if (e.key === this.tokenKey && !e.newValue) {
                // 其他页签登出,同步当前页签
                this.clearAuth();
                window.location.href = '/login.html';
            }
        });
    }

    clearAuth() {
        localStorage.removeItem(this.tokenKey);
        document.cookie = 'refresh_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    }
}

const authManager = new AuthManager();
# backend/routers/auth_router.py - 安全的Token刷新机制
@app.post("/api/auth/refresh")
async def refresh_token(request: Request):
    """通过HttpOnly Cookie中的Refresh Token刷新Access Token"""
    refresh_token = request.cookies.get("refresh_token")
    if not refresh_token:
        raise HTTPException(401, "未提供刷新Token")
    
    try:
        payload = jwt.decode(refresh_token, REFRESH_SECRET_KEY, algorithms=[ALGORITHM])
        username = payload.get("sub")
        
        user = db.query(User).filter(User.username == username).first()
        if not user:
            raise HTTPException(401, "用户不存在")
        
        # 生成新的Access Token
        access_token = create_access_token(data={"sub": username})
        return {"access_token": access_token}
    
    except JWTError:
        raise HTTPException(401, "无效的刷新Token")

安全要点:

  • Access Token存储在localStorage(便于前端访问)
  • Refresh Token存储在HttpOnly Cookie(防止XSS)
  • 实现无感Token刷新
  • 跨Tab页签登录状态同步
  • 设置Token过期时间和刷新策略

8安全性与性能优化

8.1安全措施

措施 说明
密码安全 使用 bcrypt 哈希存储,禁止明文存储
JWT 认证 无状态认证,Token设置过期时间
输入验证 使用 Pydantic 模型进行请求参数验证
CORS 配置 生产环境限制允许的域名
文件上传限制 限制文件大小(10MB)和类型

8.2性能优化

优化项 说明
异步处理 使用FastAPI异步端点 + httpx异步客户端
数据库索引 对常用查询字段(user_id, username)建立索引
请求超时 设置合理的超时时间(120秒)
批量操作 使用 bulk_save_objects 优化批量写入

9部署与运行

9.1 环境依赖

# 后端依赖
fastapi==0.110.0
uvicorn==0.27.0
sqlalchemy==2.0.25
pymysql==1.1.0
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-dotenv==1.0.0
httpx==0.26.0
PyPDF2==3.0.1
python-docx==0.8.11

9.2 .env.example文件

# 数据库配置
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=ai_interview

# JWT 配置
SECRET_KEY=your-secret-key-change-in-production
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=1440

# Dify API 配置
DIFY_API_BASE=https://api.dify.ai/v1
DIFY_SELF_INTRO_API_KEY=your_key
DIFY_INTERVIEW_API_KEY=your_key
DIFY_RESUME_API_KEY=your_key
DIFY_PEPTALK_API_KEY=your_key
DIFY_REPORT_API_KEY=your_key

10 项目复盘与未来规划

当前成果

  1. 完成核心功能开发(简历优化、模拟面试、心理陪伴)
  2. 通过内测验证功能可用性
  3. 建立完整的API文档

11总结

AI面试官 项目不仅是一个技术练习,更是我对求职焦虑的解决方案。通过整合 FastAPI、Dify AI 工作流和前端原生技术,我们构建了一个既有技术深度又有人文关怀的求职辅助平台。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐