FitMall-运动优选项目博客

FitMall-运动优选项目博客 FitMall 运动优选一个完整健身电商平台的技术全解析从零搭建的全栈健身电商平台涵盖商品交易、AI 智能助手、社区互动、训练管理四大核心模块。本文将拆解其技术选型、架构设计、关键实现细节与工程经验。目录项目概览技术栈一览架构设计那些有意思的技术细节HTTP-Only Cookie 会话认证用 Redis 替代 JWT手搓 Snowflake 分布式 ID 生成器双通道 ES 同步直写 消息队列兜底IK 分词器的双分析器策略AI 三端架构与优雅降级Dify Chatflow 工作流从意图识别到商品推荐订单超时取消与 Redis 分布式锁运行时数据库迁移WebSocket 实时聊天与在线状态追踪总结与思考1.项目概览FitMall 是一个面向运动健身人群的全栈 Web 应用承载了电商交易、课程学习、社区互动、训练管理、AI 智能助手五大业务线。项目采用前后端分离架构通过 Docker Compose 一键部署。核心数据流浏览器 → Nginx (80) → Vue 3 SPA → /api/* → FastAPI (8000) → MySQL / Redis / ES / RabbitMQ → Dify AI (云端)2.技术栈一览后端组件技术说明Web 框架FastAPI 0.138 Uvicorn全异步支持 SSE 流式响应数据库MySQL 8.0 SQLAlchemy 2.0异步引擎 aiomysql 驱动缓存 / 会话Redis 7会话存储、验证码、限流、分布式锁搜索引擎Elasticsearch 8.12 IK 分词器商品/课程/帖子/用户四个索引消息队列RabbitMQ 3.13ES 异步同步、订单超时、训练任务认证JWT HTTP-Only Cookie双通道鉴权Redis Session 存储支付支付宝沙箱RSA2-SHA256自签名实现不依赖官方 SDK存储阿里云 OSS带重试和预签名 URL 降级短信阿里云 DypnsapiRedis 存验证码5 分钟过期配置管理Pydantic Settings40 环境变量dev / prod 分离ID 生成自研 Snowflake41 位毫秒时间戳 10 位机器 12 位序列2.1 前端组件技术说明框架Vue 3.5 Composition API TypeScriptscript setup语法构建Vite 8秒级热更新状态管理Pinia 3user / admin / cart 三个 Store路由Vue Router 4嵌套路由 权限守卫样式TailwindCSS 3自定义 “Iron Chalk” 设计系统HTTPAxios 拦截器Cookie 自动携带 并发刷新拦截图表Chart.js体重趋势 / 训练统计WebSocket原生 WebSocket 自封装 Composable实时聊天 在线状态2.2 基础设施组件技术说明容器化Docker Compose6 个服务一键编排Web 服务器Nginx静态文件 API 反向代理内网穿透cpolar支付宝回调 / 开发调试镜像加速阿里云镜像apt / pip / npm 三端加速3.架构设计3.1 领域驱动模块划分后端采用按业务域垂直拆分的模块化结构每个模块内部遵循统一的四层范式module/ ├── models.py # SQLAlchemy 数据模型与数据库表一一对应 ├── schemas.py # Pydantic 请求/响应 SchemaAPI 契约 ├── router.py # FastAPI 路由定义薄层纯路由注册 └── service.py # 业务逻辑核心所有复杂逻辑在这里全局共12 个业务域auth goods course training community cart order ai admin user shared middleware每个域独立演进通过shared/层的公共组件数据库会话、Redis 客户端、ES 客户端、统一响应格式进行横向连接。3.2 请求生命周期HTTP 请求 → CORS 中间件跨域白名单校验 → Request-ID 中间件注入 UUID 追踪链路 → 日志中间件记录 method/path/status/duration → 路由匹配 → 依赖注入DB Session / Redis / Auth User → Service 层业务逻辑 外部调用 → 统一响应格式封装 → {code: 1, data: {...}, message: success} → 异常处理器兜底6 种自定义异常 → HTTP 状态码映射4.那些有意思的技术细节4.1 HTTP-Only Cookie 会话认证用 Redis 替代 JWT这是整个项目最具辨识度的架构决策。传统 JWT 方案的痛点Token 存在localStorage中任何注入的 XSS 脚本都能轻松窃取。一旦 Token 泄露在过期前无法主动撤销。FitMall 的方案登录 → 生成 Snowflake Session Token → 将用户信息存入 Rediskey: session:{token}, TTL: 24h → 将 Token 写入 HTTP-Only Cookie前端 JS 不可读 → 浏览器每次请求自动携带 Cookie → 后端解析 Cookie → Redis 查找 → 注入当前用户到请求上下文为什么比 JWT 更安全防 XSSHTTP-Only Cookie 对 JavaScript 完全不可见即使页面注入恶意脚本也无法窃取 Token即时失效删除 Redis 中的 Session Key 即可让任意 Token 立即失效无需等到过期用户/管理端隔离两套完全独立的 Cookie 名fitmall_sessionvsfitmall_admin_session和 Redis Key 前缀杜绝越权自动续期每次活跃请求自动刷新 Redis TTL只有真正离线的用户才会过期同时保留了 JWT 作为 WebSocket 的鉴权通道浏览器 WebSocket API 无法自定义 Cookie Header实现双通道鉴权。# dependencies.py — 双通道鉴权的核心实现asyncdefget_current_user(request:Request,db:AsyncSessionDepends(get_db),)-dict:# 通道 1Cookie Session主通道tokenrequest.cookies.get(settings.SESSION_COOKIE_NAME)iftoken:user_dataawaitredis_client.get(fsession:{token})ifuser_data:awaitredis_client.expire(fsession:{token},settings.SESSION_EXPIRE_HOURS*3600)returnjson.loads(user_data)# 通道 2Authorization BearerWebSocket 等场景auth_headerrequest.headers.get(Authorization)ifauth_headerandauth_header.startswith(Bearer ):returnverify_jwt_token(auth_header[7:])raiseUnauthorizedException(请先登录)4.2 手搓 Snowflake 分布式 ID 生成器不满足于pip install snowflake-id项目从零实现了一个符合 Twitter Snowflake 规范的 ID 生成器。64 位结构┌─┬──────────────────────────┬──────────────┬──────────────┐ │0│ 41-bit 时间戳 (ms) │ 10-bit 机器码 │ 12-bit 序列号 │ └─┴──────────────────────────┴──────────────┴──────────────┘ ↑ ↑ ↑ 保留位 数据中心(5)节点(5) 4096/ms实现亮点自定义纪元2024-01-01而非 Twitter 的2010-11-04使 ID 可用到 2094 年时钟回拨容错最多容忍 5ms 的时钟回拨超时则抛异常线程安全threading.Lock保护序列号递增双重输出generate_id()返回整数存数据库generate_id_str()返回 Hex 字符串用于 URL所有 Session Token、商品 SN 编号、订单号都通过 Snowflake 生成保证全局唯一且趋势递增。4.3 双通道 ES 同步直写 消息队列兜底当管理员创建/修改一个商品时搜索索引如何保持最新方案双通道写入┌─ 通道 1: 直接写入 ES用户立即搜到 CRUD 操作 ──────────┤ └─ 通道 2: 发布 RabbitMQ 消息 → 消费端异步写入 ↑ 兜底直写失败时由消息队列重试为什么不做纯异步如果管理员刚上架一个商品就去前台搜索纯异步方案可能因为消息延迟导致搜不到。直写方案用户立即可见消息队列作为Eventual Consistency的保障层即使 ES 临时不可用消息也会被持久化并在 ES 恢复后重放。消费端采用指数退避重试5s → 10s → 20s → 40s → 60s最多重试 5 次。# es_sync_tasks.py — 消费端重试逻辑retry_count0whileretry_countMAX_RETRIES:try:ifactionindex:awaitindex_document(index_name,str(doc_id),body)elifactiondelete:awaitdelete_document(index_name,str(doc_id))breakexceptExceptionase:retry_count1ifretry_countMAX_RETRIES:logger.error(ES 同步最终失败)breakdelaymin(INITIAL_RETRY_DELAY*(2**(retry_count-1)),MAX_RETRY_DELAY)awaitasyncio.sleep(delay)4.4 IK 分词器的双分析器策略Elasticsearch 默认分词器对中文很不友好逐字拆分。项目使用了 IK 中文分词器并采用了索引与搜索分析器分离的策略{analysis:{analyzer:{ik_index_analyzer:{tokenizer:ik_max_word},// 索引用细粒度高召回ik_search_analyzer:{tokenizer:ik_smart}// 搜索用粗粒度高精度}}}ik_max_word将文本切分为尽可能多的词条索引阶段用保证增肌蛋白粉能被蛋白粉、“增肌”、“蛋白”、粉等任意组合命中ik_smart做最粗粒度的语义切分搜索阶段用避免苹果手机被拆成苹果手机后匹配到水果效果对比搜索词ik_max_word 搜索ik_smart 搜索“蛋白粉”返回蛋白粉 含有蛋白或粉的商品只返回真正匹配蛋白粉的商品“瑜伽垫”返回瑜伽垫 瑜伽相关商品精确匹配瑜伽垫此外ES 兼容性处理值得一提elasticsearch-py8.19 默认发送compatible-with9头而服务器 ES 8.14 只接受 7 或 8通过 monkey-patch 固定为compatible-with8解决。4.5 AI 三端架构与优雅降级项目集成了 Dify 平台的三个独立 AI 应用应用API 类型降级策略AI 对话助手Chat Streaming (SSE)❌ 无降级正在添加训练计划生成Workflow (Blocking)✅ 模板计划4 种目标类型训练建议Workflow (Blocking)✅ BMI 规则引擎5 级分类SSE 流式对话实现# dify_client.py — 流式消费 Dify SSEasyncforlineinresponse.aiter_lines():ifnotline.startswith(data:):continuedatajson.loads(line[5:].strip())eventdata.get(event,)ifeventmessage:yield{content:data[answer],conversation_id:data[conversation_id]}elifeventerror:raiseDifyClientError(fDify 流式错误:{data[message]})每条消息增量推送给前端SSE 格式前端实时渲染打字效果。优雅降级模式# plan/service.py — 先调 AI失败则用模板try:client_get_dify_client()resultawaitclient.run_workflow(inputs{...})weekly_planresult.get(weekly_plan)exceptDifyClientErrorase:logger.warning(Dify 计划生成失败使用模板计划: %s,e)ifweekly_planisNone:weekly_plan_template_plan(targetdata.target,...)# 本地模板这意味着即使 Dify 宕机或 Token 过期用户依然能得到一份基于目标类型减脂/增肌/塑形/保持的可用训练计划只是缺少 AI 的个性化优化。4.6 Dify Chatflow 工作流从意图识别到商品推荐最新为 AI 助手设计的 Chatflow 工作流实现了意图识别 → 智能搜索 → 格式化推荐的完整链路用户输入想买蛋白粉 → LLM 意图识别 → {category: goods, keyword: 蛋白粉} → HTTP 节点 → GET /api/v1/ai/search?q蛋白粉typegoods → ES 返回 [{id, name, price_text, image, badge, url}, ...] → LLM 格式化 → 推荐文案含 product://123 特殊链接 → 前端渲染 → 可点击的商品卡片关键设计点意图分类用 LLM 而非关键词匹配高容错能理解最近跑步膝盖疼有什么护具推荐吗 goods “跑步护具”product://协议链接不直接用真实 URL而是用自定义协议让前端拦截并渲染成卡片避免 Dify 输出裸露的链接不好看HTTP 节点直接调公网 APIhttp://xxx.xxx.xxx.xxx/api/v1/ai/searchDify Cloud 可直达服务器4.7 订单超时取消与 Redis 分布式锁用户下单后 30 分钟内未支付系统自动取消并回滚库存。实现方式# 1. 下单时投递延迟消息publish_message(ORDER_TIMEOUT_QUEUE,{order_id:order.id,created_at:now.isoformat()})# 2. 消费端计算剩余时间sleep 后检查remaining30*60-elapsed_secondsifremaining0:time.sleep(remaining)# 3. Redis 分布式锁防止多实例重复处理lock_keyforder:timeout:lock:{order_id}acquiredredis_client.set(lock_key,1,nxTrue,ex120)# SET NX EXifnotacquired:return# 其他实例已在处理# 4. 检查订单状态 回滚库存 关闭订单为什么用time.sleep而不用 RabbitMQ TTL 死信队列简单直接一条消息进入队列消费者收到后自己算时间睡觉。避免了死信队列的复杂配置也避免了秒级 TTL 不准的问题RabbitMQ 的 TTL 到秒级才有保证。不过缺点也很明显sleep 期间消费者线程被占用。小规模场景单实例、单线程消费没问题大规模需要换方案。4.8 运行时数据库迁移不用 Alembic 生成迁移脚本而是在应用启动时直接跑增量 SQL# database.pyasyncdef_run_migrations(connection):migrations[ALTER TABLE users ADD COLUMN IF NOT EXISTS height INT DEFAULT NULL,ALTER TABLE spu ADD COLUMN IF NOT EXISTS original_price INT DEFAULT NULL,# ...]forsqlinmigrations:try:awaitconnection.execute(text(sql))exceptException:pass# 幂等列已存在则跳过适用场景小型团队快速迭代避免了编写、review、执行 Alembic 迁移的繁琐流程。每行 SQL 都是幂等的IF NOT EXISTS/ try-catch多次执行也不会出错。不适用场景大型团队需要严格 review 每次数据库变更或有回滚需求。此时应回归 Alembic 版本管理。4.9 WebSocket 实时聊天与在线状态追踪社区的私信功能基于 WebSocket 实现采用双通道保底WebSocket 通道实时双向通信在线状态推送HTTP 降级通道当 WebSocket 连接失败时自动 fallback 到 HTTP POST 发消息在线状态追踪的设计# 在内存中维护当前进程active_connections:dict[int,WebSocket]{}# 在 Redis 中维护跨进程 / 重启恢复awaitredis_client.sadd(chat:online_users,str(user_id))亮点用户上线时先用 JWTWebSocket 连接时携带的短效 Token鉴权然后将 WebSocket 对象注册到内存字典 Redis 集合。下线或断开时同步清理两端。未读消息计数存储在 Redis 中每次用户连接时推送未读数读到消息后实时更新。5.总结与思考5.1 适合学习的技术点HTTP-Only Cookie Redis Session的认证方案比传统 JWT 更安全值得在生产环境采用自研 Snowflake是一个很好的分布式系统入门实践理解 64 位 ID 的每一位含义ES 双分析器策略是中文搜索的标配方案IK 分词器的选择直接影响搜索体验AI 优雅降级是 AI 集成的最佳实践——AI 是锦上添花不是核心链路5.2 可以改进的地方订单超时的time.sleep方案在大流量下会成为瓶颈后期应改用 RabbitMQ 死信队列运行时迁移适合当前阶段但随着团队扩大应切换回 Alembic 版本管理ES 双通道同步存在极小概率的不一致关键场景如库存扣减应优先查 MySQLAI Chat 目前无降级Dify 不可用时会直接报错应添加兜底回复5.3 项目数据后端~8000 行 Python12 个业务模块前端~40 个 Vue 组件3 个 Pinia Store基础设施6 个 Docker 容器4 个 ES 索引外部集成Dify AI、支付宝、阿里云 OSS、阿里云短信、Elasticsearch、RabbitMQ、Redis5.4 后期优化基础设施升级方向Redis-分片集群、分布式锁、缓存、MySQL-主从读写分离、分库分表、MGR 多主、Elasticsearch-分片 副本自动分布式、RabbitMQ-镜像队列集群。6. 项目演示(由于图片没有找所以看起来会奇怪一些目前只是demo阶段)C端B端其他页面就不展示了随便展示了一些项目持续迭代中代码开源维护。如有问题或建议欢迎通过 Issue 或 PR 交流。