本文记录了笔者从 0 到 1 完成的一个课程设计项目:用微信小程序 + FastAPI + DeepSeek 大模型做一款 AI 智能行程规划工具。文章涵盖需求分析、技术选型、系统架构、数据库设计、核心代码实现、真实踩坑与解决方案,适合正在做类似项目或想了解"前端 + 后端 + 大模型"全链路的同学参考。


一、项目背景与痛点分析

1.1 真实痛点

短途旅行(1-4 天)是大学生、年轻上班族最高频的出行场景。但实际做攻略的过程极其碎片化:

  • 在小红书搜景点 → 在美团比票价 → 在高德看路线 → 在大众点评找美食 → 在携程订票;

  • 收藏了几十篇笔记,真正成行时却"收藏从未停止,行动从未开始";

  • 景点之间怎么串、几点去避峰、哪个机位出片、附近吃什么,全靠人肉拼凑。

核心问题:信息分散、决策成本高、缺乏"一键成行"的工具。

1.2 项目目标

做一个一站式 AI 行程规划小程序:用户输入城市、天数、人群、预算、偏好,AI 直接生成一份可执行的逐日行程,包含景点、路线、拍照机位、附近美食、购票入口。


二、需求分析

2.1 功能需求

模块功能点
行程创建AI 表单规划、一句话快规划、截图识行程、手动编排(4 种模式)
路线策略大众经典路线 + 小众宝藏路线双版本
智能问答任意行程相关问题(天气、避峰、交通、备选)
第三方直达打车 / 酒店 / 门票 / 机票高铁一键跳转对应小程序
模板广场发布、收藏、复用优质路线
会员体系月卡 19.9、年卡 129,解锁高级生成次数

2.2 非功能需求

  • 接口响应快(生成类异步 + 兜底,主流程不卡死);

  • 安全(JWT 鉴权、ORM 防注入);

  • 易演示(支持 Mock 模式,无后端也能跑通界面)。


三、技术选型与理由

选型为什么
前端微信原生小程序直接调微信登录、跳转其他小程序、真机预览,最契合"小程序跳小程序"需求
后端FastAPI异步性能好、自带 OpenAPI 文档、类型提示强、开发快
数据库MySQL 8.0关系型数据(用户/行程/订单)稳定可靠,utf8mb4 支持 emoji
缓存Redis限流、热点模板缓存、会话态
AIDeepSeek 大模型中文行程生成质量高、成本低、API 简单
地图高德开放平台POI 搜索、路径规划、地理编码一站齐全

为什么不用 uni-app / Taro?本项目强依赖微信原生能力(如 navigateToMiniProgram 跳第三方、真机预览),原生最稳;且单人开发,原生心智负担更小。


四、系统总体架构

4.1 分层架构

 ┌─────────────────────────────┐
 │   微信小程序(WXML/WXSS/JS)  │  表现层
 └──────────────┬──────────────┘
                │  HTTPS + JWT(Bearer)
 ┌──────────────▼──────────────┐
 │   FastAPI 网关               │
 │   ├─ routers  (参数校验/路由)│
 │   ├─ services (业务逻辑)    │
 │   ├─ schemas  (Pydantic 模型)│
 │   └─ deps     (鉴权依赖)    │
 └──────┬───────┬───────┬──────┘
        │       │       │
    ┌───▼─┐ ┌─▼──┐ ┌─▼────────┐ ┌─────────┐
    │MySQL │ │Redis│ │DeepSeek  │ │高德 API  │
    │持久化│ │限流 │ │行程生成  │ │地图服务  │
    └──────┘ └─────┘ └─────────┘ └─────────┘

4.2 项目目录(后端)

 server/
 ├── app/
 │   ├── main.py          # 入口,挂 StaticFiles、CORS、路由
 │   ├── config.py        # 配置(DB/Redis/JWT/高德 Key)
 │   ├── database.py      # SQLAlchemy 引擎 + Session
 │   ├── deps.py          # get_current_user 鉴权依赖
 │   ├── utils/jwt_utils.py
 │   ├── routers/         # plan / trip / user / template / chat / membership / ocr
 │   └── services/        # plan_service / trip_service / image_service / route_service ...
 └── .env                 # 密钥与 Key(gitignore)

五、数据库设计(MySQL 8.0)

8 张核心表usertriptrip_daytrip_spottemplatecollectionorderchat_historycityservice_link(含派生表)。行程内容用 JSON 字段存结构化日程,兼顾灵活性与查询性能。

5.1 核心表结构(节选)

user 用户表

 CREATE TABLE `user` (
     `id`                BIGINT       NOT NULL AUTO_INCREMENT,
     `openid`            VARCHAR(64)  NOT NULL,
     `nickname`          VARCHAR(64)  DEFAULT NULL,
     `avatar_url`        VARCHAR(512) DEFAULT NULL,
     `role`              ENUM('free','vip') NOT NULL DEFAULT 'free',
     `vip_expire_time`   DATETIME     DEFAULT NULL,
     `daily_plan_count`  INT          NOT NULL DEFAULT 3,
     `daily_count_date`  DATE         DEFAULT NULL,
     `created_at`        DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
     `updated_at`        DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     PRIMARY KEY (`id`),
     UNIQUE KEY `uk_openid` (`openid`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

trip 行程表

 CREATE TABLE `trip` (
     `id`            BIGINT      NOT NULL AUTO_INCREMENT,
     `user_id`       BIGINT      NOT NULL,
     `city_id`       INT         NOT NULL,
     `title`         VARCHAR(128),
     `days`          INT,
     `content`       JSON,        -- 完整逐日行程(天/景点/餐食/交通/拍照攻略)
     `route_type`    ENUM('popular','niche'),
     `budget_level`  ENUM('low','mid','high'),
     `crowd_type`    VARCHAR(32),
     `transport_mode` VARCHAR(32),
     `status`        ENUM('draft','saved','deleted'),
     PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

设计取舍:行程是"文档型"数据,字段结构随 AI 生成变化大,用 JSON 存主内容,关系型字段(user_id/city_id)保证可关联统计。


六、后端核心实现

6.1 AI 行程生成接口

前端提交表单 → 后端拼 Prompt → 调 DeepSeek → 校验 JSON → 落库。关键路由:

 # server/app/routers/plan.py
 @router.post("/form")
 async def plan_form(req: PlanFormReq, user=Depends(get_current_user)):
     # 个人主体不扣积分;企业/个体户开启变现后才计费
     if settings.monetization_enabled and not await points_service.consume_plan(user["user_id"]):
         return fail("积分不足,请充值后再生成行程", code=4001)
     trip = await plan_service.generate_from_form(req)
     return ok({"trip": {"id": None, **trip}})   # 预览态 id 为 None,保存时走新建

细节:预览态行程 id 设为 None。前端保存时,后端检测到 id 不是合法整数就自动"新建",避免把占位符当已有记录更新而报错(详见第八节踩坑 1)。

6.2 JWT 无状态鉴权(真实代码)

登录:wx.logincode → 后端换 openid不返回前端)→ HMAC 签名签发 token。

 # server/app/utils/jwt_utils.py
 import datetime, jwt
 from app.config import settings
 ​
 def create_token(payload: dict) -> str:
     payload = dict(payload)
     payload["exp"] = datetime.datetime.utcnow() + datetime.timedelta(hours=settings.jwt_expire_hours)
     return jwt.encode(payload, settings.jwt_secret, algorithm="HS256")
 ​
 def decode_token(token: str) -> dict:
     return jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])

接口通过依赖统一校验:

 # server/app/deps.py
 async def get_current_user(authorization: str = Header(None)):
     if not authorization or not authorization.startswith("Bearer "):
         raise HTTPException(status_code=401, detail="未登录")
     token = authorization[7:]
     try:
         return decode_token(token)          # 验签名 + 过期,失败抛 401
     except Exception:
         raise HTTPException(status_code=401, detail="token 无效")

为什么安全:token 里没有 openid(防泄露),服务端不存 session(易扩展),HS256 签名保证不可伪造,带 exp 自动过期。

6.3 ORM 防 SQL 注入

全程用 SQLAlchemy 参数化查询,绝不手拼 SQL:

 # 查询:参数化,注入无效
 stmt = select(User).where(User.id == uid, User.status == 'active')
 result = await session.execute(stmt)
 ​
 # 更新:ORM 对象赋值,框架自动转义
 user.nickname = new_name
 await session.commit()

用户输入永远作为"值"传递,数据库驱动会转义,从根本上杜绝 ' OR '1'='1 这类注入。

6.4 高德 API 集成与超时兜底

生成行程后需调高德补全 POI 图片/坐标、公交路径规划。原实现串行调用且超时 12-15s,Key 配额超限时会拖垮主流程。优化:

 # image_service.py / route_service.py 关键改动
 r = requests.get(url, timeout=5)   # 原 12-15s → 5s,快速失败

并在 plan_service._normalize 中给高德调用包 try/except

 try:
     info = image_service.get_spot_info(spot.get("name", ""), city)
     spot["image"] = info.get("image") or spot.get("image") or ""
 except Exception as e:
     logger.warning("高德补全失败,跳过: %s", e)   # 失败不阻塞整体行程返回

6.5 城市封面图三级兜底

外部图床(loremflickr)常 500,直接白屏。采用三级兜底,保证永不白屏:

 本地真实图 /images/cities/城市.jpg   → 不存在则
 在线图床 picsum/seed 兜底           → 再失败则
 显示 emoji 占位

前端 binderror 监听加载失败,自动逐级降级。后续把真实摄影图丢进文件夹即自动生效。


七、前端核心实现(微信原生小程序)

7.1 页面结构(12 个核心页面)

index(首页) / form(AI 表单) / quick(一句话) / ocr(截图识行程) / detail(行程详情) / trips(我的行程) / template(模板广场) / published(我发布的) / collection(收藏) / chat(智能问答) / member(会员) / profile(个人中心)。

7.2 请求封装

统一在 request.js 中拼接 BASE_URL + /api/xxx,自动携带 Authorization 头,统一处理 code !== 0 的业务错误,避免每个页面重复写。

7.3 第三方小程序跳转

首页"服务直达"通过 wx.navigateToMiniProgram 跳美团 / 携程 / 腾讯地图等,需在 app.jsonnavigateToMiniProgramAppIdList 配置白名单。查不到真实 AppID 的渠道(如大众点评、12306)降级为"复制搜索词",保证功能不缺且真机不报错。

7.4 行程详情:拍照攻略 + 美食攻略双栏

每个景点下方展示:

  • 📸 拍照攻略(1 条核心机位:位置 + 光线 + 避峰标签);

  • 🍽 美食攻略(AI 按景点就近推荐的餐厅:店名 + 人均 + 团购渠道);

  • 底部大按钮:公众号预约 / 门票购买

美食匹配逻辑:优先用景点自带 nearby_food → 按 time_slot(上午配早/午,晚配晚)→ 按地址区域相似度 → 兜底时间最近,避免全天都推荐同一家店。


八、API 接口清单(部分)

后端共 29 个接口,命名统一 /api/模块/动作,返回 {code, msg, data}

接口说明
POST /api/plan/formAI 表单规划
POST /api/plan/quick一句话快规划
POST /api/plan/chat-adjust对话微调行程
POST /api/trip/save保存行程(新建/更新)
GET /api/trip/list我的行程列表
GET /api/trip/detail行程详情
POST /api/user/avatar头像永久上传
GET /api/template/hot热门模板
POST /api/template/publish发布到广场
POST /api/collection/add收藏
POST /api/chat/qa旅游智能问答
POST /api/membership/order会员下单
POST /api/ocr/recognize截图识行程

九、真实踩坑记录(含前后对比)

坑 1:保存行程报「行程 id 格式错误 gen」

  • 现象:点保存提示 gen 格式错误。

  • 根因plan.py 预览态返回 "id": "gen" 占位符,前端当成真实 id 传给 /api/trip/saveint("gen") 转型失败。

  • 修复:预览态 id 改为 Nonetrip_service.save() 仅当 id 能转整数才走更新,否则兜底新建。

  • 前后对比return ok({"trip": {"id": "gen", **trip}})return ok({"trip": {"id": None, **trip}})

坑 2:高德 Key 配额超限,AI 生成卡死

  • 现象:一直"AI 规划中"转圈。

  • 根因:后端日志 CUQPS_HAS_EXCEEDED_THE_LIMIT,生成后要串行调高德 POI/路径规划,每请求超时十几秒,累积超时前端断开。

  • 修复:高德调用包 try/except 不阻塞主流程 + 超时 12-15s 缩到 5s + 更换为"Web服务"类型新 Key。

坑 3:跳转第三方小程序失败

  • 现象:点"机票高铁"弹"跳转失败,请检查白名单"。

  • 根因:白名单里 12306 AppID wxb84362f15c06b3f2 查不到权威值,真机严格校验失败。

  • 修复:换成已确证的携程 AppID wx0e6ed4f51db9d078;大众点评同理降级为"复制搜索词"。

坑 4:图片加载失败连带 DOM 警告

  • 现象:控制台刷 removedNodeloremflickr 500

  • 修复:三级兜底 + binderror 清 src,控制台恢复干净。


十、部署与运行

 # 后端(项目自带 venv,避免系统 Python 缺依赖)
 cd server
 venv/Scripts/python.exe -m uvicorn app.main:app --host 0.0.0.0 --port 8009 --reload
 ​
 # 前端
 微信开发者工具导入 miniprogram/,USE_MOCK=true 可纯前端预览;
 切真实后端:env.js 设 USE_MOCK=false + BASE_URL 指向后端地址。

注意:BASE_URL 不含 /api(前端统一以 /api 开头拼接),否则会双前缀 404。


十一、总结与展望

11.1 收获

  • 打通"前端 + 后端 + 大模型 + 第三方 API"全链路;

  • JWT 无状态鉴权、ORM 防注入、异步限流、超时兜底 从概念变成实操;

  • 学会"外部依赖不可控时必须降级"的工程思维。

11.2 展望

  • 接入真实视觉识别做"截图识行程";

  • 用户画像驱动个性化推荐;

  • 行程分享海报 + 社交裂变。


十二、项目声明

本文为笔者在校期间独立完成的课程设计/项目实践作品,文中系统架构、核心代码、界面设计均为本人原创或基于开源协议合法使用。项目依赖的微信小程序、FastAPI、MySQL、Redis、DeepSeek LLM、高德地图等均为公开技术栈,代码仅供学习交流,不做真实票务交易、不涉及真实支付收款。转载请注明出处,禁止用于商业用途。


如果这篇文章对你做课程设计 / 毕设有帮助,欢迎点赞收藏~ 有疑问评论区交流,看到会回。

更多推荐