Python全栈项目--智能旅游推荐系统

一、项目简介

旅游平台常见的痛点是信息多、选择难:用户需要在城市、预算、旅行天数、主题偏好之间反复比较。本文实现一个可运行的 Python 全栈项目--智能旅游推荐系统,通过用户输入的目的地、主题、季节、预算和天数,结合用户收藏行为与相似用户偏好,自动推荐合适景点,并支持收藏与行程保存。

项目不是单纯的推荐算法 Demo,而是完整的全栈应用:后端提供注册登录、Token 鉴权、景点库、协同过滤推荐、收藏、行程接口;前端使用 Vue 3 构建交互页面;数据库使用 SQLite 存储用户、景点、收藏和行程数据。

二、技术栈

  • 后端:FastAPI、SQLAlchemy、Pydantic、Passlib
  • 数据库:SQLite
  • 前端:Vue 3、Vite、Fetch API
  • 认证:密码哈希 + Bearer Token
  • 部署运行:Uvicorn + Vite Dev Server

前端主技术栈为 Vue 3/Vite,适合扩展为组件化单页应用。

三、系统架构

text 复制代码
用户浏览器
   │
   │ Vue 3 页面:注册、登录、推荐、收藏、行程
   ▼
FastAPI REST API
   │
   ├── auth.py:密码哈希、Token 创建、鉴权依赖
   ├── crud.py:用户、景点、推荐、收藏、行程业务逻辑
   ├── schemas.py:请求和响应结构
   └── models.py:SQLAlchemy ORM 模型
   ▼
SQLite 数据库 travel.db

系统采用前后端分离架构。前端登录成功后把 Token 保存到 localStorage,后续访问推荐、收藏、行程接口时通过 Authorization: Bearer <token> 请求头完成鉴权。

四、功能模块

  1. 用户模块

    • 用户注册
    • 用户登录
    • 登录状态恢复
    • 退出登录
    • Token 鉴权
  2. 景点库模块

    • 初始化示例景点数据
    • 按城市查询
    • 按关键词搜索景点名称、主题和标签
  3. 智能推荐模块

    • 支持城市、主题、季节、预算、旅行天数
    • 基于用户收藏行为构建用户-景点交互数据
    • 使用用户-用户协同过滤挖掘相似用户偏好
    • 结合景点主题、城市、标签做物品相似度补充
    • 支持城市、主题、季节、预算和游玩时长约束
    • 推荐接口需要登录后访问
  4. 收藏模块

    • 登录用户收藏景点
    • 查询个人收藏列表
  5. 行程模块

    • 将推荐结果保存为个人行程
    • 查询个人行程列表

五、数据库/数据模型设计

系统核心数据表如下:

表名 说明 关键字段
users 用户表 username、email、password_hash
tokens 登录令牌表 token、user_id、created_at
attractions 景点表 name、city、theme、tags、price、rating
favorites 收藏表 user_id、attraction_id
itineraries 行程表 user_id、title、city、days、budget、attraction_ids

models.py 中定义了 ORM 模型,例如景点模型:

python 复制代码
class Attraction(Base):
    __tablename__ = "attractions"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(100), index=True, nullable=False)
    city = Column(String(50), index=True, nullable=False)
    theme = Column(String(80), index=True, nullable=False)
    tags = Column(String(255), default="")
    season = Column(String(80), default="四季")
    price = Column(Float, default=0)
    rating = Column(Float, default=4.0)
    duration_hours = Column(Float, default=2.0)
    description = Column(Text, default="")

六、后端接口设计

方法 路径 鉴权 说明
POST /api/auth/register 注册并返回 Token
POST /api/auth/login 登录并返回 Token
GET /api/auth/me 获取当前用户
POST /api/auth/logout 退出登录
GET /api/attractions 景点查询
POST /api/recommendations 生成个性化推荐
GET /api/favorites 获取收藏
POST /api/favorites 添加收藏
GET /api/itineraries 获取行程
POST /api/itineraries 保存行程

鉴权依赖位于 auth.py

python 复制代码
def get_current_user(authorization: str = Header(default=""), db: Session = Depends(get_db)):
    if not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="缺少认证令牌")
    raw_token = authorization.replace("Bearer ", "", 1).strip()
    token = db.query(models.Token).filter(models.Token.token == raw_token).first()
    if not token:
        raise HTTPException(status_code=401, detail="令牌无效或已退出登录")
    return db.query(models.User).filter(models.User.id == token.user_id).first()

七、前端页面设计

前端采用 Vue 3 单页应用结构:

  • 登录/注册卡片:支持用户认证与登录状态展示。
  • 推荐条件表单:填写城市、主题、季节、预算、天数。
  • 推荐结果区:卡片式展示推荐景点,可一键收藏。
  • 景点库区:支持关键词搜索。
  • 我的行程区:登录后展示个人保存的行程。

API 请求统一封装在 frontend/src/api.js 中:

javascript 复制代码
async function request(path, options = {}) {
  const headers = { 'Content-Type': 'application/json', ...(options.headers || {}) }
  const token = getToken()
  if (token) headers.Authorization = `Bearer ${token}`
  const res = await fetch(`${API_BASE}${path}`, { ...options, headers })
  const data = await res.json().catch(() => ({}))
  if (!res.ok) throw new Error(data.detail || '请求失败')
  return data
}

八、核心代码讲解

1. 密码哈希与 Token 登录

系统不保存明文密码,而是使用 Passlib 对密码进行哈希:

python 复制代码
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def hash_password(password: str) -> str:
    return pwd_context.hash(password)

def verify_password(password: str, password_hash: str) -> bool:
    return pwd_context.verify(password, password_hash)

登录成功后创建随机 Token,并写入数据库。前端保存 Token,后续访问受保护接口时自动携带。

2. 协同过滤推荐算法

推荐算法位于 crud.py。将用户收藏记录视为隐式反馈,构建"用户-景点"交互矩阵,再通过 Jaccard 相似度计算用户之间的相似程度,优先推荐相似用户收藏过、当前用户尚未收藏的景点。同时,系统会结合景点主题、城市和标签计算物品相似度,用来缓解示例数据量较小时的冷启动和稀疏问题:

python 复制代码
def recommend(db: Session, query: schemas.RecommendQuery, user_id: int):
    favorites = db.query(models.Favorite).all()
    user_favorites = {}
    popularity = {}
    for favorite in favorites:
        user_favorites.setdefault(favorite.user_id, set()).add(favorite.attraction_id)
        popularity[favorite.attraction_id] = popularity.get(favorite.attraction_id, 0) + 1

    current_liked = user_favorites.get(user_id, set())
    similar_users = {}
    for other_user_id, other_liked in user_favorites.items():
        if other_user_id == user_id:
            continue
        similarity = _jaccard(current_liked, other_liked)
        if similarity > 0:
            similar_users[other_user_id] = similarity

随后系统会对候选景点计算综合得分:相似用户贡献越高、与当前用户已收藏景点越相似、全站收藏热度越高,排名越靠前。最后再根据城市、季节、预算和每日可游玩时长过滤结果,返回最适合的景点组合。对于没有收藏记录的新用户,系统会自动使用全站收藏热度、景点评分和查询条件作为冷启动兜底。

3. Vue 登录状态处理

前端启动时会尝试读取本地 Token 并请求 /api/auth/me

javascript 复制代码
async function restoreSession() {
  if (!getToken()) return
  try {
    user.value = await api.me()
    await loadItineraries()
  } catch {
    clearToken()
  }
}

如果 Token 无效,则清除本地登录状态,避免前端显示错误用户信息。

九、部署与运行步骤

项目代码位于 project/ 目录。

1. 启动后端

bash 复制代码
cd project/backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

默认后端地址:http://127.0.0.1:8000。首次启动会自动创建 SQLite 数据库并写入示例景点。

2. 启动前端

bash 复制代码
cd project/frontend
npm install
npm run dev

默认 Vite 地址通常是 http://127.0.0.1:5173。如果后端地址不同,可设置:

bash 复制代码
export VITE_API_BASE=http://127.0.0.1:8000
npm run dev

3. 体验流程

  1. 注册新用户或使用页面默认示例信息注册。
  2. 登录后选择城市、主题偏好、旅行季节、预算和天数。
  3. 点击"生成智能推荐"。
  4. 收藏喜欢的景点。
  5. 将当前推荐结果保存为行程。

十、项目总结

本文完成了一个围绕旅游场景的 Python 全栈项目:后端使用 FastAPI 提供 REST API,SQLite 存储核心数据,Vue 3/Vite 构建前端页面,并实现了完整的用户注册、登录、Token 鉴权、协同过滤景点推荐、收藏和行程管理。

这个项目适合作为全栈练习模板继续扩展,例如:

  • 接入真实景点和地图数据;
  • 扩展矩阵分解、向量召回或深度学习推荐模型;
  • 增加酒店、交通、美食模块;
  • 增加管理员后台维护景点库;
  • 使用 Docker Compose 部署前后端。

通过该项目,可以系统掌握 FastAPI 后端接口、SQLite 数据建模、Vue 3 前端状态管理以及前后端认证协作的完整开发流程。

项目代码

下载链接