微信小程序网约车项目实战:FastAPI+Vue3从需求到上线
微信小程序网约车打车项目是 Python 方向毕业设计和课程设计里最常见的一类选题用户在小程序里选择起点和终点、下单叫车后端负责订单流转、司机匹配和状态管理管理后台处理司机与订单数据。下面按照 FastAPI Vue3 微信小程序这套组合从需求拆解、系统架构、数据库设计、接口实现、前端联调到常见问题排查完整走一遍。如果正在准备课程设计答辩或毕业设计开题这篇文章可以直接当作项目设计说明书来参考。读完以后可以回答三个问题这个项目由哪些部分组成每个部分各自负责什么功能小程序、后端和管理后台之间是怎么通过接口串联起来的。1. 先想清楚这个网约车项目到底要做哪些事1.1 项目定位与读者收益网约车小程序不是简单把“叫车”两个字做成一个按钮。它要处理的是多角色、多状态、实时位置、订单流转和权限控制。这类项目非常适合做毕业设计因为它既有移动端又有后台既有普通 CRUD 又有状态机和地理位置处理既能展示后端能力也能展示前端交互。这套项目适合以下读者正在选 Python 毕业设计题目的学生。需要把 FastAPI 作为后端完成一个小程序项目的开发者。想理解 Vue3 后台管理系统如何连接真实接口的人。准备把课程设计升级成可演示、可答辩作品的同学。阅读过程中会看到完整的项目目录、数据库建表语句、核心接口代码、小程序请求封装、Vue3 管理后台页面以及一套可以照着排查的问题清单。1.2 系统角色与核心功能拆解网约车项目至少要拆成三个角色乘客微信小程序用户可以查看当前位置、选择上车点和下车点、创建订单、等待司机接单、查看订单状态。司机可以接单、更新自己的实时位置、改变订单状态。毕业设计里司机端可以用同一个微信小程序通过角色切换实现也可以做成 Vue3 里的一个“司机工作台”页面。管理员在 Vue3 管理后台里审核司机、查看订单列表、查看用户数据必要时手动取消异常订单。围绕这三个角色核心功能可以列成一张表端核心功能依赖的关键技术微信小程序微信登录、位置获取、上下车点选择、创建订单、等待接单、查看订单进度wx.login、wx.getLocation、wx.requestFastAPI 后端用户认证、司机管理、订单创建、状态流转、位置距离计算、统一返回格式FastAPI、SQLAlchemy、PyJWT、RedisVue3 管理后台管理员登录、订单列表、司机审核、用户列表、统计概览Vue3、Vite、Element Plus、Axios基础服务数据持久化、缓存与实时位置、订单号生成MySQL、Redis这里要注意真实打车软件里的司机端和乘客端通常分成两个 App但毕业设计不需要完全复刻商业产品。把司机端简化成一个角色切换入口或者把司机接单功能放到管理后台里已经足够体现完整业务逻辑。1.3 为什么选择 FastAPI Vue3 微信小程序这套组合FastAPI 是目前 Python 后端里非常适合微信小程序项目的选择。它有自动接口文档写路由时用类型注解就能生成 Pydantic 校验开发节奏快同时支持异步后续如果接入 WebSocket 推送司机位置也不需要换框架。Vue3 适合做管理后台因为它组件化思路清晰生态成熟。Element Plus 提供了现成的表格、表单、弹窗和菜单组件做订单列表和司机审核页面非常快。微信小程序是乘客端最合适的形式。用户不需要安装 App微信开发者工具可以直接预览和调试登录可以使用 wx.login 拿到临时 code后端再请求微信接口换取用户身份。开发语言上既可以用原生 WXML也可以用 uni-app 写 Vue3 语法再编译成小程序后文会说明两种方式怎么选。2. 系统架构与数据库设计先把订单流走通2.1 整体分层与请求链路从请求链路看这套项目的结构可以分成四层表现层微信小程序乘客端、Vue3 管理后台。接口层FastAPI 应用接收请求、参数校验、返回 JSON。服务层用户认证、订单状态流转、司机匹配、距离计算。数据层MySQL 存业务数据Redis 存在线司机位置和缓存。在本地开发环境小程序会通过局域网地址访问后端部署到公网时需要在 FastAPI 前面加一个 Nginx 网关层并且让后端接口走 HTTPS。小程序真机调试有域名限制必须在小程序管理后台配置 request 合法域名否则请求会直接被拦截。2.2 三张核心表用户、司机、订单数据库设计不追求复杂但要把业务关系表达清楚。推荐先用三张表把主流程跑通用户表、司机表、订单表。用户表CREATE TABLE user ( id int unsigned NOT NULL AUTO_INCREMENT, openid varchar(128) NOT NULL COMMENT 微信 openid, nickname varchar(64) DEFAULT COMMENT 昵称, avatar varchar(255) DEFAULT COMMENT 头像, phone varchar(20) DEFAULT COMMENT 手机号可后补, role tinyint NOT NULL DEFAULT 1 COMMENT 1 乘客2 司机3 管理员, status tinyint NOT NULL DEFAULT 1 COMMENT 1 正常0 禁用, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;司机表CREATE TABLE driver ( id int unsigned NOT NULL AUTO_INCREMENT, user_id int unsigned NOT NULL COMMENT 关联用户表, real_name varchar(32) NOT NULL COMMENT 真实姓名, phone varchar(20) NOT NULL COMMENT 联系电话, car_no varchar(16) NOT NULL COMMENT 车牌号, car_type varchar(32) DEFAULT COMMENT 车型, online_status tinyint NOT NULL DEFAULT 0 COMMENT 0 下线1 在线, current_lat decimal(10,6) DEFAULT NULL COMMENT 当前纬度, current_lng decimal(10,6) DEFAULT NULL COMMENT 当前经度, audit_status tinyint NOT NULL DEFAULT 0 COMMENT 0 待审核1 通过2 拒绝, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单表CREATE TABLE order ( id int unsigned NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, passenger_id int unsigned NOT NULL COMMENT 乘客用户ID, driver_id int unsigned DEFAULT NULL COMMENT 接单司机用户ID, start_lng decimal(10,6) NOT NULL, start_lat decimal(10,6) NOT NULL, start_address varchar(255) NOT NULL, end_lng decimal(10,6) NOT NULL, end_lat decimal(10,6) NOT NULL, end_address varchar(255) NOT NULL, estimate_amount decimal(10,2) DEFAULT 0.00 COMMENT 预估金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态, cancel_reason varchar(255) DEFAULT , created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, accepted_at datetime DEFAULT NULL, finished_at datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_passenger_id (passenger_id), KEY idx_driver_id (driver_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计订单表时有一个建议不要用字符串存状态本身而是用 tinyint 存数字状态码在代码里定义状态枚举。这样数据库存储更紧凑查询和索引也更快。除此之外订单号要单独用业务规则生成不要依赖自增主键因为订单号要体现时间信息也会传给前端展示。2.3 订单状态机从待接单到已完成订单状态是整个项目最容易写乱的地方。推荐先画一张状态流转表再写代码。状态值状态名可进入的下一个状态说明0待接单已接单、已取消乘客创建订单后的初始状态1已接单已上车、已取消司机接单后进入2已上车已完成司机接到乘客后进入3已完成无订单正常结束4已取消无乘客或司机取消状态机可以用一个字典集中管理ORDER_STATUS { WAITING: 0, ACCEPTED: 1, PICKED: 2, FINISHED: 3, CANCELLED: 4, } TRANSITIONS { ORDER_STATUS[WAITING]: [ORDER_STATUS[ACCEPTED], ORDER_STATUS[CANCELLED]], ORDER_STATUS[ACCEPTED]: [ORDER_STATUS[PICKED], ORDER_STATUS[CANCELLED]], ORDER_STATUS[PICKED]: [ORDER_STATUS[FINISHED]], }在接口里做状态更新前先判断当前状态是否允许跳转到目标状态。比如“已取消”的订单不能再变成“已完成”否则订单数据会出现逻辑错误。3. 环境准备本机先跑通 Python 后端和 Vue3 管理后台3.1 环境清单与版本建议开始动手前先确认本机环境。下面的版本是我建议的不是官方强制版本软件建议版本作用Python3.10 或 3.11运行 FastAPI 后端MySQL8.0保存用户、司机、订单数据Redis7.x司机在线位置缓存、后续可做会话扩展Node.js18 LTS 或 20 LTS运行 Vue3 管理后台npm随 Node.js 安装安装前端依赖微信开发者工具最新稳定版预览和调试微信小程序IDEVS Code / PyCharm编写代码这些版本不是绝对固定但 Python 和 Node 版本不要太老。FastAPI 在 Python 3.7 以上都能跑但使用新版语法和库时Python 3.10 以上的兼容性更好。3.2 后端依赖与数据库初始化推荐在后端项目根目录单独建一个虚拟环境避免污染全局 Python。创建虚拟环境并激活python -m venv venv source venv/bin/activateWindows 环境激活命令是venv\Scripts\activate激活虚拟环境后创建 requirements.txtfastapi0.115.0 uvicorn[standard]0.30.0 sqlalchemy2.0.30 pymysql1.1.0 redis5.0.0 python-dotenv1.0.0 pydantic-settings2.2.0 PyJWT2.8.0 httpx0.26.0安装依赖pip install -r requirements.txt然后创建数据库CREATE DATABASE taxi_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;这里把字符集设置成 utf8mb4是为了让地址、昵称、司机姓名里的中文和特殊符号都能正常存储。3.3 Vue3 管理后台初始化管理后台建议使用 Vite 创建npm create vitelatest taxi-admin -- --template vue cd taxi-admin npm install npm install element-plus axios pinia vue-router安装之后把 src 目录下的默认内容清理掉先搭建一个最简结构src/api 放 axios 请求封装。src/router 放路由配置。src/stores 放登录状态。src/views 放登录页、订单列表页、司机管理页。开发模式下启动命令npm run dev启动后默认访问http://localhost:5173。4. FastAPI 后端实现登录、统一响应、订单接口4.1 后端目录结构与配置推荐后端目录按模块拆分不要把所有路由都堆在 main.py 里。下面是一个适合课程设计的结构fastapi_server/ app/ __init__.py main.py core/ config.py security.py db/ base.py session.py models/ user.py driver.py order.py schemas/ auth.py order.py common.py api/ v1/ __init__.py auth.py order.py driver.py utils/ response.py order_no.py geo.py requirements.txt .env在 app/core/config.py 里读取环境变量from pydantic_settings import BaseSettings class Settings(BaseSettings): app_name: str taxi-api database_url: str mysqlpymysql://root:123456127.0.0.1:3306/taxi_demo redis_url: str redis://127.0.0.1:6379/0 jwt_secret: str change-me-in-env jwt_expire_minutes: int 720 wx_appid: str wx_secret: str class Config: env_file .env settings Settings().env 文件内容DATABASE_URLmysqlpymysql://root:123456127.0.0.1:3306/taxi_demo REDIS_URLredis://127.0.0.1:6379/0 JWT_SECRETplease-change-me JWT_EXPIRE_MINUTES720 WX_APPID你的小程序AppID WX_SECRET你的小程序AppSecret绝对不能把 AppSecret 写进小程序前端代码。它只能保存在后端环境变量里因为后端请求微信的 code2session 接口时才需要它。4.2 微信登录接口与 JWT 签发微信小程序登录的正规流程是小程序调用wx.login()获取临时 code把 code 传给后端后端拿着 code、appid、appsecret 请求微信接口换取 openid 和 session_key后端用 openid 找到或创建用户然后签发自己的 JWT 返回给小程序。FastAPI 登录接口from fastapi import APIRouter, HTTPException import httpx from app.core.config import settings from app.utils.response import success router APIRouter() router.post(/auth/wx_login) async def wx_login(payload: dict): code payload.get(code) if not code: raise HTTPException(status_code400, detailcode is required) url https://api.weixin.qq.com/sns/jscode2session params { appid: settings.wx_appid, secret: settings.wx_secret, js_code: code, grant_type: authorization_code, } async with httpx.AsyncClient() as client: resp await client.get(url, paramsparams) data resp.json() if openid not in data: raise HTTPException(status_code401, detailfwx login failed: {data}) openid data[openid] user get_user_by_openid(openid) if not user: user create_user(openid) token create_access_token(user_iduser.id, roleuser.role) return success({token: token, userId: user.id, role: user.role})这里解释一下为什么后端要自己再签一个 JWT微信返回的 session_key 是会话密钥不应该直接返回给前端长期保存。后端用 openid 找到用户后签一个自己的 token后续所有需要登录的接口都用这个 token 进行身份校验这也方便管理后台和管理员共用同一套认证体系。JWT 签发代码import time import jwt from app.core.config import settings def create_access_token(user_id: int, role: int) - str: payload { user_id: user_id, role: role, exp: int(time.time()) settings.jwt_expire_minutes * 60, } return jwt.encode(payload, settings.jwt_secret, algorithmHS256) def decode_token(token: str) - dict: return jwt.decode(token, settings.jwt_secret, algorithms[HS256])4.3 统一响应格式与全局异常小程序和 Vue3 前端都不希望后端有时候返回字符串、有时候返回对象、有时候直接抛异常。建议所有接口返回统一结构{ code: 0, message: ok, data: {} }FastAPI 里可以封装一个工具函数from fastapi.responses import JSONResponse def success(dataNone, messageok): return JSONResponse( content{code: 0, message: message, data: data} ) def fail(messageerror, code1, http_status400): return JSONResponse( status_codehttp_status, content{code: code, message: message, data: None}, )同时注册一个全局异常处理器把未捕获异常转换成统一格式from fastapi import Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code422, content{code: 422, message: str(exc.errors()), data: None}, )统一返回格式的好处不只是好看。小程序端封装请求时只需要判断res.data.code 0就可以用一个函数统一处理成功和失败不需要在每一页里重复判断 HTTP 状态码。4.4 创建订单与状态流转创建订单的接口要做的几件事校验乘客身份。校验起始点和终点不能为空。生成唯一订单号。插入订单记录初始状态为待接单。返回订单号和当前状态。订单号生成可以使用时间戳加随机数import time import random def generate_order_no(): ts time.strftime(%Y%m%d%H%M%S) rand random.randint(1000, 9999) return f{ts}{rand}创建订单接口简化版router.post(/orders) async def create_order(order_data: OrderCreate, userDepends(get_current_user)): order Order( order_nogenerate_order_no(), passenger_iduser.id, start_lngorder_data.start_lng, start_latorder_data.start_lat, start_addressorder_data.start_address, end_lngorder_data.end_lng, end_latorder_data.end_lat, end_addressorder_data.end_address, statusORDER_STATUS[WAITING], ) db.add(order) db.commit() db.refresh(order) return success(order_to_dict(order))状态更新时要先查询订单再判断当前状态和目标状态是否在TRANSITIONS里router.post(/orders/{order_id}/status) async def update_order_status(order_id: int, req: StatusUpdate, userDepends(get_current_user)): order db.get(Order, order_id) if not order: return fail(订单不存在, http_status404) target req.status if target not in TRANSITIONS[order.status]: return fail(非法状态流转, http_status400) order.status target if target ORDER_STATUS[ACCEPTED]: order.driver_id user.id order.accepted_at datetime.now() if target ORDER_STATUS[FINISHED]: order.finished_at datetime.now() db.commit() return success({orderId: order.id, status: order.status})这里最重要的设计是不要在所有接口里随意修改order.status而是把状态流转规则收口在一张状态机表里。如果后面要加入“司机取消”“乘客取消”等场景只需要扩展 TRANSITIONS不会影响其他接口。4.5 附近司机搜索用 Haversine 还是 Redis GEO乘客下单以后系统要找到附近的在线司机。最简单的做法是遍历司机表里所有在线司机计算两点距离取最近的一个。这个方案数据量小时没问题但司机数量增长后性能会下降。距离计算公式使用 Haversine 公式import math def haversine(lat1: float, lng1: float, lat2: float, lng2: float) - float: R 6371.0 dlat math.radians(lat2 - lat1) dlng math.radians(lng2 - lng1) a ( math.sin(dlat / 2) ** 2 math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlng / 2) ** 2 ) return R * 2 * math.asin(math.sqrt(a))如果使用 Redis GEO可以在司机上线时把自己的经纬度写入 RedisGEOADD driver_location 经度 纬度 driver_id寻找附近司机GEORADIUS driver_location 经度 纬度 3 km ASC COUNT 10这个方法比 MySQL 全表扫描快也比较贴近真实打车系统。课程设计版本可以先在 MySQL 里直接查把 Haversine 公式和 Redis GEO 的对比写进文档答辩时能讲清楚为什么生产环境要选 Redis GEO。5. 微信小程序端定位、登录、下单5.1 原生小程序还是 uni-app如果小程序端只需要跑在微信里原生小程序足够调试方便微信官方文档也最直接。如果以后想同时发布到支付宝小程序或 H5可以选 uni-app用 Vue 语法写代码再编译成微信小程序。两种方式的关键点方式优点需要注意原生小程序官方支持最稳调试直观WXML 和 Vue 模板语法有差异uni-app Vue3一套代码多端可用编译链路多一层部分组件兼容性要测试课程设计建议优先用原生小程序因为它能把微信登录、位置接口、request 域名这些机制看得更清楚。下面的示例按原生小程序来写。5.2 封装 wx.request 和 token小程序里所有请求都走wx.request建议在 utils/request.js 里做统一封装const BASE_URL http://127.0.0.1:8000 function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.data.code 0) { resolve(res.data.data) } else { wx.showToast({ title: res.data.message, icon: none }) reject(res.data) } }, fail(err) { wx.showToast({ title: 网络请求失败, icon: none }) reject(err) } }) }) } module.exports { request, BASE_URL }注意BASE_URL在开发阶段使用http://127.0.0.1:8000微信开发者工具要勾选“不校验合法域名”。真机预览时这个地址必须换成已配置合法域名的 HTTPS 地址否则请求会被拦截。5.3 微信登录与会话保持在小程序首页onLoad里判断本地是否有 token没有就登录const { request } require(../../utils/request) Page({ onLoad() { const token wx.getStorageSync(token) if (token) { this.fetchUserInfo() } else { this.wxLogin() } }, wxLogin() { wx.login({ success: async (res) { if (res.code) { const data await request(/api/v1/auth/wx_login, POST, { code: res.code }) wx.setStorageSync(token, data.token) wx.setStorageSync(userId, data.userId) } } }) } })这里最常见的坑是后端已经返回了 token但小程序没有保存导致后续接口始终 401。一定要在登录成功回调里把 token 写进 Storage并且 request 封装里每次请求都带上 Authorization 头。5.4 获取位置并创建订单小程序获取定位使用wx.getLocationgetLocation() { wx.getLocation({ type: gcj02, success: (res) { this.setData({ startLat: res.latitude, startLng: res.longitude }) }, fail: () { wx.showToast({ title: 定位失败, icon: none }) } }) }创建订单时把起点、终点、经纬度一起传给后端async createOrder() { const data await request(/api/v1/orders, POST, { startLng: this.data.startLng, startLat: this.data.startLat, startAddress: this.data.startAddress, endLng: this.data.endLng, endLat: this.data.endLat, endAddress: this.data.endAddress }) this.setData({ orderId: data.orderId, orderNo: data.orderNo }) this.startPolling() }小程序没有后端 WebSocket 推送时可以用轮询查订单状态startPolling() { this.timer setInterval(async () { const data await request(/api/v1/orders/${this.data.orderId}, GET) this.setData({ orderStatus: data.status }) if (data.status 3 || data.status 4) { clearInterval(this.timer) } }, 3000) }轮询间隔不要设成 1 秒频繁请求会浪费后端资源3 到 5 秒对课程设计足够。后续可以升级成 WebSocket 推送减少无效请求。6. Vue3 管理后台登录、订单列表、司机管理6.1 管理后台初始化与路由守卫管理后台需要先跑通登录和路由守卫。只有登录成功后才能进入订单列表和司机管理页面。安装依赖后在src/api/auth.js封装登录接口import axios from axios const http axios.create({ baseURL: http://127.0.0.1:8000/api/v1, timeout: 10000 }) http.interceptors.request.use(config { const token localStorage.getItem(adminToken) if (token) { config.headers.Authorization token } return config }) export function adminLogin(data) { return http.post(/auth/admin_login, data) }管理员登录接口和微信登录要分开。管理员通常使用账号密码登录后端需要一张管理员账号表或者给 user 表增加一个管理员角色并额外存密码字段。为了控制篇幅可以把管理员账号写在环境变量里也可以单独建一张 admin 表。路由守卫使用 Vue Routerimport { createRouter, createWebHistory } from vue-router const router createRouter({ history: createWebHistory(), routes: [ { path: /login, component: () import(../views/Login.vue) }, { path: /, component: () import(../views/Layout.vue), children: [ { path: orders, component: () import(../views/OrderList.vue) }, { path: drivers, component: () import(../views/DriverList.vue) } ]} ] }) router.beforeEach((to, from, next) { const token localStorage.getItem(adminToken) if (to.path ! /login !token) { next(/login) } else { next() } })6.2 订单列表页与接口联调订单列表页使用 Element Plus 的 el-tabletemplate el-table :dataorders v-loadingloading el-table-column proporderNo label订单号 width180 / el-table-column propstartAddress label起点 / el-table-column propendAddress label终点 / el-table-column label状态 template #default{ row } {{ statusText(row.status) }} /template /el-table-column el-table-column propcreatedAt label创建时间 width180 / /el-table /template script setup import { ref, onMounted } from vue import { getOrders } from ../api/order const orders ref([]) const loading ref(false) onMounted(async () { loading.value true const data await getOrders() orders.value data.list loading.value false }) /script管理后台联调时要注意Vue3 开发服务器默认运行在 5173 端口后端运行在 8000 端口两者端口不同会产生跨域。FastAPI 需要在 main.py 里配置 CORSfrom fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[http://localhost:5173, http://127.0.0.1:5173], allow_credentialsTrue, allow_methods[*], allow_headers[*], )6.3 Vue2 到 Vue3 的几个差异提醒如果以前用过 Vue2在写管理后台时要注意几个变化this.$router在组合式 API 里用useRouter()。data()在script setup里用ref和reactive。组件插槽语法从slot-scope改成了#default{ row }。Element Plus 组件引入方式需要注册不能直接把 Element UI 的 Vue2 写法搬过来。这些差异也是 Vue3 面试题里常考的内容写项目时多动手会比死记硬背更有用。7. 联调验证后端、小程序、管理后台一次跑通7.1 用 FastAPI 自动文档验证核心接口FastAPI 自带的 Swagger 文档地址是http://127.0.0.1:8000/docs。启动后端以后先看三个核心接口是否正常POST /api/v1/auth/wx_login传一个测试 code。POST /api/v1/orders在认证通过后创建订单。GET /api/v1/orders/{id}查询订单详情。由于 wx_login 在本地没有真实微信 code可以用测试方式绕过在后端写一个仅供开发环境的测试登录接口用固定 openid 返回 token。等小程序真机联调时再使用真实 wx.login 流程。启动命令uvicorn app.main:app --reload --port 8000看到终端输出Uvicorn running on http://127.0.0.1:8000后打开浏览器访问/docs。7.2 小程序开发者工具中的联调步骤小程序联调按以下顺序操作在微信开发者工具中导入小程序项目填入自己的 AppID。把utils/request.js里的 BASE_URL 改成http://127.0.0.1:8000。在开发者工具的“详情 - 本地设置”里勾选“不校验合法域名”。编译后查看 Console 是否出现登录成功日志。点击创建订单观察后端日志是否打印订单创建记录。预期结果小程序控制台显示 token后端 MySQL 里新增一条 status 为 0 的订单记录。7.3 预期结果与日志验证后端建议加一个简单请求日志中间件方便联调时观察每个请求的结果app.middleware(http) async def log_requests(request, call_next): print(f{request.method} {request.url.path}) response await call_next(request) print(fstatus: {response.status_code}) return response管理后台进入订单列表后能看到刚才小程序创建的订单状态显示“待接单”。如果管理后台能查到这个订单说明登录、创建订单、跨域、Vue3 列表渲染四条链路都通了。8. 常见问题与排查链路8.1 微信登录失败code、appid、secret、域名逐个排查微信登录失败是出现频率最高的问题。常见现象是小程序打印wx login failed或后端日志出现401、40029、40013。排查顺序检查小程序 AppID 是否填错AppID 要和代码里的WX_APPID一致。检查 AppSecret 是否填错不能用开发工具的“测试号” AppSecret 和后端正式号混用。检查 code 是否已经使用过。wx.login拿到的 code 只能使用一次不能重复提交。检查后端请求微信接口时是否拿到了openid如果返回errcode把微信返回的原始信息打印出来。检查小程序后台是否配置了 request 合法域名。这一步在本地开发者工具里可以通过“不校验合法域名”跳过但真机测试阶段必须配置。8.2 小程序请求不到后端从合法域名到 CORS小程序请求失败经常不是后端代码问题而是请求在客户端就被拦截了。问题现象常见原因检查方式处理建议小程序请求报“url not in domain list”使用了 http 或未配置合法域名小程序后台查看 request 合法域名配置 HTTPS 域名并加入合法域名列表管理后台请求跨域报错后端没有配置 CORS看浏览器 Console 的 CORS 提示在 FastAPI 加入 CORSMiddleware手机真机请求失败但开发者工具正常真机不能访问 127.0.0.1打印 BASE_URL 确认改为局域网 IP 或部署后的域名后端收到请求但返回 404路由前缀不匹配在 FastAPI 文档里确认接口路径统一/api/v1前缀管理8.3 WebSocket 连接不稳定如果给司机位置推送接入了 WebSocket小程序端经常会遇到连接断开的问题。常见原因和检查方式小程序进入后台后 WebSocket 连接会被系统断开需要在小程序切回前台时重新连接。FastAPI 的 WebSocket 端点要配合心跳检测客户端定期发送 ping 包服务端超时后主动关闭。如果不处理断线重连前端会一直拿不到位置更新表现就是订单状态卡住。生产环境里不建议在小程序端直接使用原生 WebSocket 做稳定长连接真实项目可以用微信小程序的服务端消息推送能力或者用轮询先保证功能完整。8.4 位置偏移与地图选点不准微信wx.getLocation返回的是 gcj02 坐标系而部分地图服务使用 wgs84 坐标系。如果后端直接拿两套坐标做距离计算可能出现几十米甚至上百米的偏差。建议在小程序端统一使用 gcj02 坐标上传后端只负责保存和计算不反复转换坐标系。如果接入了地图选点组件选点返回的坐标也要统一成同一个坐标系。8.5 问题速查表把最常见的场景整理成一张速查表问题现象可能原因检查点解决方案小程序登录失败code 无效、AppID 不匹配、Secret 错误后端日志中微信接口返回值核对配置并重新调用 wx.login订单创建后状态不更新状态机校验失败查看 TRANSITIONS 定义检查当前状态和跳转目标管理后台空白页Vue 路由配置错误浏览器 Console、路由路径检查路由 path 与 component 路径经纬度保存后精度丢失数据库字段用了 float查看表结构使用 decimal(10,6) 保存坐标下单超时后端查询司机逻辑过慢SQL 查询时间、MySQL 索引给 status、经纬度字段加索引中文乱码数据库字符集不是 utf8mb4查看表字符集建表时使用 utf8mb49. 毕业设计交付清单与生产化扩展方向9.1 课程设计和毕业设计交付清单答辩前不要只交一份源码。建议按下面清单整理交付材料源码目录后端、小程序、管理后台三个目录分开存放。README.md写清楚运行步骤、环境版本、账号说明、启动命令。SQL 文件包含建库、建表、初始数据脚本。接口文档FastAPI 的 /docs 地址截图或者封装的接口说明文档。设计文档包含需求说明、数据库 ER 图、订单状态机、接口列表。演示视频录 5 到 10 分钟覆盖登录、下单、后台查看订单的完整流程。README 里至少写清三个命令如何启动后端、如何启动管理后台、如何在微信开发者工具里打开小程序。9.2 学习环境与生产环境的差异课程设计跑通即可但要清楚学习环境与生产环境的区别学习环境使用 http 和 127.0.0.1生产环境必须使用 HTTPS 和已备案域名。学习环境数据库密码写在 .env 里生产环境应使用密钥管理服务或更严格的环境变量管理。学习环境不做访问频率限制生产环境需要限流、日志、监控和报警。学习环境订单状态直接写库生产环境要考虑消息队列、异步结算、支付回调。学习环境不接入真实支付生产环境要申请微信支付商户号并实现支付回调验签。答辩时能讲清楚这些差异会给老师留下“不是只抄代码”的印象。9.3 可以继续扩展的方向这套项目可以扩展出很多方向按工作量从低到高排列增加订单超时自动取消下单后 5 分钟无人接单自动取消。增加司机实时位置上报用 WebSocket 或定时上报在地图上展示司机位置。增加订单费用规则按距离和时间动态计算预估金额。增加微信订阅消息订单被接单后给乘客推送进度通知。增加支付模块接入微信支付后订单完成后自动扣款。增加数据统计在管理后台展示每日订单量、营收和平均接单时长。建议先从“订单超时自动取消”开始扩展因为它不依赖外部服务只改后端状态机和定时任务可以很快看到效果。选择扩展方向时优先选能体现技术深度、又能在答辩现场稳定演示的功能不要一次接太多外部依赖。