# starmall **Repository Path**: soulnew/starmall ## Basic Information - **Project Name**: starmall - **Description**: 一套商场小程序,支持在线购买+预定+微信风格的在线客服 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-04-07 - **Last Updated**: 2026-09-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README --- title: "starmall:一个微信小程序电商系统的全栈实战——预约、支付与订单架构解析" date: 2021-07-20T10:00:00+08:00 draft: false summary: "介绍我开发的小型电商系统 starmall(六爻星辰):uni-app 微信小程序前端 + SpringBoot 后端(基于 CRMEB 二次开发),支持商品购买、真实微信支付、老师时段预约、择吉日查询、WebSocket 长连接聊天等功能。本文深入解析后端能力全景、下单幂等、原子扣库存防超卖、支付回调防重、Redis 队列代替 MQ 等核心技术实现。" tags: ["Java", "Spring Boot", "uni-app", "微信小程序", "电商", "MySQL", "Redis", "开源项目"] categories: ["系统架构"] --- # starmall:一个微信小程序电商系统的全栈实战——预约、支付与订单架构解析 这是我 2021 年做的一套完整电商系统 **starmall**(六爻星辰):微信小程序端 + Java 后端 + PC 管理后台,支持商品购买、老师咨询时段预约、择吉日查询、在线咨询等业务。整理开源出来,写篇文章做一次完整的技术复盘。 - 前端(uni-app 小程序): - 后端(Java + 管理端): - 技术栈:Spring Boot 2.2 + MyBatis-Plus + MySQL + Redis / uni-app + uView / Vue 2 + Element UI(管理端) - 说明:后端是在开源商城 **CRMEB Java 版** 基础上二次开发的,分销/秒杀/拼团/砍价等营销能力来自底座,预约系统、择吉日、名片、咨询聊天等是自研部分 --- ## 一、整体架构 ```mermaid graph TD MP["微信小程序端
starmall uni-app + uView"] -->|"https://ftmao.com/api/front/**"| API["SpringBoot 单体后端
62 个 Mapper / 23 个前台 Controller"] H5["H5 / App
uni-app 多端编译"] --> API ADMIN["PC 管理后台
Vue 2 + Element UI + ECharts"] -->|"后台接口"| API API --> MYSQL["MySQL 5.7
62 张表 Druid 连接池"] API --> REDIS["Redis
token / 订单缓存 / 任务队列 / 配置缓存"] API --> WX["微信开放平台
登录 / 支付 V2 / 订阅消息"] API --> QN["七牛云 OSS
图片上传"] API --> QY["企业微信机器人
部署结果通知"] MP -->|"wss://... websocket"| CHAT["咨询聊天服务"] ``` 代码规模:后端约 850 个 Java 文件、12.6 万行;小程序前端业务代码约 1.2 万行;数据库初始化 SQL 全量在仓库里,共 62 张表。后端按业务分包:`front`(小程序接口)、`order/payment`、`marketing`、`seckill`、`task`(定时任务)、`wechat`,以及自研的 `appoint`(预约)、`lunar`(黄历)等模块。 --- ## 二、功能介绍 ### 2.1 商品购买全流程 标准的商城闭环:商品详情(多规格 SKU)→ 立即购买/加购物车 → 确认订单(优惠券、积分抵扣、运费计算)→ 创建订单 → 微信支付 → 轮询支付结果 → 收货评价 → 退款/售后。**支付是真实对接的微信支付 V2**——后端手工实现 XML 报文 + MD5 签名调统一下单(`WeChatPayServiceImpl`,746 行),前端弹起 `wx.requestPayment`,不是 mock。 此外还支持:余额支付、运费模板(满额包邮/指定城市包邮/按件数重量体积三种首续费)、物流查询、自提核销码、再次下单。营销玩法(秒杀、砍价、拼团、优惠券)来自 CRMEB 底座,一应俱全。 ### 2.2 老师咨询时段预约(自研核心) 这是这个项目区别于普通商城的地方——卖的不只是实物商品,还有"老师的时间": - 商品分类为"老师类"时,商品详情页出现一个**时段选择器**(每天 6 个时段:9/11/13/15/17/19 点); - `teacher_plan` 表按「日期 × 时段」预生成号源,被约走的时段前端置灰; - 下单时携带 `teacherPlanId`,**创建订单的同一个事务里**把该时段标记为已约——号源占用和订单创建要么一起成功要么一起回滚,不会出现"钱付了时段没了"; - 号源由定时任务 `TeacherPlanTask` 每 8 小时自动向前补齐未来 8 天,并清理过期的空闲号源。 ### 2.3 其他自研功能 - **择吉日查询**:4.7 万行黄历数据入库(`lunar` 系列表),按"宜"事反查吉日,比如查"最近适合搬家的日子"; - **在线咨询**:基于 WebSocket 长连接的实时聊天,支持文字/语音/图片消息(详见第三章「长连接聊天」); - **老师名片**、文章资讯、签到积分、充值、小程序订阅消息提醒; - **管理后台**:商品/订单/营销/分销/财务/短信/按钮级权限/数据统计大屏(ECharts)。 --- ## 三、后端能力全景 后端虽然是 SpringBoot 单体,但能力面相当完整:前台 23 个 Controller、62 个 MyBatis Mapper,按业务分包组织: ```mermaid graph TD subgraph M1["业务模块"] A1["front 前台接口
23 个 Controller"] A2["order / payment
订单与支付"] A3["marketing / seckill / bargain / combination
优惠券 秒杀 砍价 拼团"] A4["appoint / lunar / businessCard
预约 择吉日 名片(自研)"] A5["finance / statistics / export
充值提现 数据统计 Excel 导出"] A6["wechat / wechatMessage / sms
微信生态与短信"] A7["task
定时任务体系"] end subgraph M2["公共层"] B1["拦截器链
前台 Token / 后台 Token+权限 / Swagger"] B2["工具层 RedisUtil / WxPayUtil / CrmebUtil"] B3["ThreadLocal 上下文传递当前用户"] end A1 --> B1 A2 --> B2 B1 --> B3 ``` 商城主链路之外,后端还有一批容易被忽略的能力: - **微信公众号消息自动回复**:`wechatMessage` 模块支持文字/语音/图片/图文四种回复类型,关注、取关、扫码等事件都有对应处理; - **模板消息与订阅消息**:小程序订阅消息、公众号模板消息的增删查与群发,且发送走异步任务(`AsyncWeChatProgramTempMessage` 等),不阻塞主流程; - **渠道二维码与素材管理**:带参数的推广二维码、微信素材的上传管理; - **数据导出与统计**:POI 实现的 Excel 导出、交易/商品/用户多维统计接口(配合管理端 ECharts 大屏); - **短信与物流**:短信验证码/通知、快递物流轨迹查询; - **接口文档**:Swagger 在线文档 + 仓库内置 admin/app/公共三份离线接口文档。 ### 3.1 长连接聊天(WebSocket)架构 在线咨询是这个系统的"第二前台",采用的是 **WebSocket 长连接 + HTTP 接口**的组合方案。部署上两个服务跑在同一台服务器,由 nginx 按路径前缀分流: ```mermaid graph TD MP["小程序聊天页
pages/chat"] -->|"wss://.../websocket/userId/sessionId"| NGX["nginx 按路径分流"] MP -->|"HTTP /chat/session/** /chat/msg/**"| NGX MP -->|"HTTP /api/front/**"| NGX NGX --> CHAT["聊天服务
WebSocket 长连接 + 会话/消息接口"] NGX --> MALL["商城后端 crm_server
SpringBoot 18080"] ``` 从开源的小程序端实现和接口契约,可以看到这套长连接聊天的几个设计要点: 1. **连接即身份**:WebSocket 握手 URL 直接携带 `userId/sessionId`,连接建立的同时就完成了身份识别与会话路由,不需要额外的登录帧; 2. **实时走长连接,持久化走 HTTP**:收发消息走 WebSocket(消息体为 JSON);会话绑定(`session/bindSession`)、会话列表、删除会话、历史消息拉取(`msg/msgList`)都走 `/chat/` 前缀的 HTTP 接口——长连接管"实时推送",HTTP 管"可查询、可回溯",职责分得很干净; 3. **统一的消息类型协议**:`contentType` 1=文字、2=语音(带 `contentDuration` 时长)、3=图片(先经 `user/upload/image` 上传拿到 URL,再把 URL 作为消息内容发出); 4. **语音消息的移动端交互**:按住说话(`touchstart`/`touchend` 控制录音起止)、上滑取消(`touchmove`/`touchcancel`),体验和微信原生聊天对齐; 5. **发送体验乐观化**:消息发出后先本地 push 进消息列表立即上屏,`send` 的 fail 回调再 toast 兜底提示。 需要如实说明:聊天服务端的代码没有随这两个仓库开源(它部署在同一台服务器上,由 nginx 分流),仓库里能看到的生产实现是小程序端;`crm_server` 里的 `com/im/SocketServer.java` 只是当时练手的原生 TCP Socket demo(12345 端口),并非线上实现。 --- ## 四、技术亮点 ### 4.1 下单幂等:两段式 + 唯一键兜底 小程序网络环境下用户双击、重试、断网重发是常态,下单接口必须幂等。这里用了一个两段式设计: ```mermaid graph TD A["order/confirm 确认订单页"] --> B["订单数据序列化存 Redis
key = md5 当前时间
有效期 60 分钟"] B --> C["order/create/orderKey 创建订单"] C --> D{"第一步:查库判重
StoreOrder.unique = orderKey"} D -->|"已存在"| E["直接返回已有订单
不重复创建"] D -->|"不存在"| F{"第二步:校验 Redis 缓存
是否过期/被用过"} F -->|"过期"| G["拒绝 提示重新确认"] F -->|"有效"| H["事务内创建订单
成功后删除缓存"] ``` `unique` 字段是最后的兜底——即使 Redis 挂了、缓存丢了,数据库唯一约束也保证同一个 orderKey 不会建出两张单。 ### 4.2 原子扣库存防超卖 库存扣减没有用"先查后改",而是一条 SQL 完成: ```java // setSql + 条件改写,数据库层面原子完成 stock = stock - n 且要求 stock - n >= 0 ``` 数据库单行更新的原子性天然挡住并发超卖,不需要分布式锁。秒杀/砍价/拼团场景更复杂:同一事务内要扣**活动表 + 活动 SKU 表 + 商品表 + 商品 SKU 表**四份库存,任何一步失败整体回滚。 ### 4.3 编程式事务包住整个建单过程 创建订单是个多写操作:扣库存、占预约时段、写订单主表、写订单详情、核销优惠券、写订单日志。代码用 `TransactionTemplate` 编程式事务(而不是 `@Transactional` 注解)把这一串操作包起来——好处是事务边界精确可控,不会把整个方法(包括不需要事务的远程调用)都圈进去。订单核心逻辑集中在 `OrderUtils.java`(1500+ 行)和 `OrderServiceImpl.java`(近 1000 行)。 ### 4.4 支付回调:防重 + 异步化 微信支付的回调处理(`CallbackServiceImpl.weChat`,448 行)有几个关键点: 1. 解析 XML、校验 `return_code`/`result_code`,按 `attach` 里携带的 userId + type 区分是订单支付还是充值; 2. **已支付直接返回 SUCCESS**——微信会多次重推回调,幂等是回调接口的生死线; 3. 事务内完成 `updatePaid`(置订单已支付)+ 扣积分 + 拼团状态处理; 4. 后续的"慢动作"——发短信、推订阅消息、分销结算——**不放在回调里同步做**,而是 `lPush` 进 Redis 队列异步执行,回调接口尽快给微信返回,避免超时重推。 ### 4.5 没有 MQ:Redis List + 定时任务的务实替代 项目没有引入 RabbitMQ/RocketMQ,而是用 **Redis List 当队列 + Spring `@Scheduled` 定时任务消费**,撑起了一整套异步任务体系: ```mermaid graph LR A["下单成功
lPush ORDER_AUTO_CANCEL_KEY"] --> Q["Redis List 队列"] P["支付成功回调
lPush 后续动作"] --> Q Q --> T1["OrderAutoCancelTask
每分钟消费
超时未支付自动取消 + 回滚库存"] Q --> T2["自动收货 / 自动好评"] Q --> T3["优惠券过期 / 砍价截止
佣金解冻"] Q --> T4["TeacherPlanTask
每8小时补齐号源"] ``` `task/order/` 下共有 8 个定时任务。对这个体量的系统,省掉一个 MQ 的部署运维成本,换来的是完全够用的可靠性——这是小项目技术选型上我很认可的一个取舍。 ### 4.6 鉴权链路:UUID Token + Redis + ThreadLocal 没有用 JWT,而是更传统的方案:登录后签发 UUID 存 Redis(按模块区分 key 前缀),拦截器 `FrontTokenInterceptor` → `CheckFrontToken` → `TokenManagerImpl` 逐级校验,通过后把当前用户放进 `ThreadLocal` 供全链路取用。管理端是 Token + 权限双拦截器。好处是**服务端可随时踢人下线**(删 Redis key 即可),这是纯 JWT 做不到的。 ### 4.7 其他值得提的实现 - **订单快照**:下单时把购物车内容序列化成 JSON 存进 `StoreOrderInfo.info`,之后商品改价改名都不影响历史订单的展示; - **运费计算引擎**:`getOrderPriceGroup` 支持满额包邮、指定城市包邮(件数 + 金额双条件)、按件数/重量/体积三种首续计费模式; - **XSS 转义**:用户输入统一 `escapeHtml4`; - **部署脚本**:`start.sh` nohup 启动 + 轮询日志判断启动成功 + 企业微信机器人推送部署结果——单机部署也搞出了仪式感。 --- ## 五、复盘与遗憾 - **底座与自研的边界**:秒杀/拼团/砍价/分销这些"重"能力直接复用 CRMEB 底座,把精力集中在业务差异化(预约、择吉日、聊天)上——对个人开发者来说这是正确的杠杆; - **单体的局限**:所有东西在一个 SpringBoot 应用里,定时任务、支付、前台接口互相影响,量大了必然要拆; - **原子扣库存的返回值没检查**:`setSql` 扣减失败(库存不足)时影响的行数为 0,但代码没有校验这个返回值,极端情况下会漏判超卖——这是现在回头看想补的一个洞; - **聊天服务端未开源**:如第三章所述,长连接聊天的服务端实现没有随仓库开源,小程序端和接口契约是完整的,算是开源的一点遗憾。 --- ## 六、写在最后 这个项目让我完整走了一遍"真实收钱"的系统该有的样子:幂等、事务、防超卖、回调防重、异步补偿——每一个点背后都是电商系统趟过的坑。代码在 和 ,建库 SQL、接口文档都在仓库里,希望能给做小程序商城的朋友一个可参照的完整样本。