MMyBlog
← 返回博客
我做了一个水果商城:网站、管理后台、小程序三端一体
18 分钟·7,195 ·193 次阅读

我做了一个水果商城:网站、管理后台、小程序三端一体

🍎 在线演示

微信小程序端已经完成开发,真机可以正常运行,但没有提审上线——为什么"卡"在资质上,文章最后一节细说。上面的「小程序在线试玩」就是同一套代码编译出来的网页版,可以直接玩。

起因:想做一个「真的能开店」的系统

学编程这段时间,我写过的东西大多是"练手级"的:一个贪吃蛇、一个机器学习小 demo、一个网络调试工具。它们都有共同的问题——只覆盖一两个知识点,做完就放下了,撑不起一条完整的业务链路。

后来我想明白了一件事:最能逼着人把零散知识串起来的项目类型,就是电商。因为"买一件东西"这个看似简单的动作,背后是一整条链路:

一条完整的交易链路

每个环节背后都是一堆具体问题:登录要做鉴权,下单要防超卖,支付要考虑掉单,售后要有审核流程。所以我决定做一个水果商城,而不是图书或数码商城——水果是生鲜,天然带出"坏果包赔""限时秒杀""48 小时发货"这些有业务味道的需求,做起来比标准品更有意思。

我给自己定的标准是:不是"能演示",而是"能开业"。如果明天有人真想开一家水果店,这套系统改改就能用。

界面走读:不用看截图,直接玩

话不多说,先上硬货——下面嵌的就是正在运行的小程序。它是用 uni-app 把小程序代码编译出来的网页版(H5),和微信里的小程序是同一套代码,商品、价格、图片全部来自线上后端。你现在就可以点:逛逛限时秒杀、点进商品详情选个规格、加进购物车,把这条链路亲手走一遍,再往下看我是怎么把它做出来的:

如果上面的区域没有加载(个别浏览器策略较严),直接打开这个地址体验:http://116.62.60.53:5173/mp/

几个建议试试的细节:首页的轮播图是后台可配置的——去管理后台换一张图,回来刷新就变,秒杀倒计时也是真的在走;商品详情里切换多规格(标准装/自然熟/礼盒装/整箱),价格和划线价会跟着变,服务保障里的「坏果包赔」不是一句口号,背后真的接着售后退款流程;购物车加了东西,角标会实时更新。浏览和加购不需要登录,点「立即购买」才会要验证码——这一步涉及真实短信通道,就留给你在自己环境里试了。

顺带说下这个「网页版小程序」是怎么来的,因为它本身就是 uni-app 卖点的现场演示:npm run build:h5 一条命令,同一份代码编译出网页版;部署到网站同域的子路径(/mp/)下,接口走 Nginx 反代,天然没有跨域问题。中间踩了最后一个坑:uni-h5 会给所有以 / 开头的图片路径自动拼上路由前缀,导致后端图片集体加载失败,改成输出同源绝对地址才绕开。

小程序一共 22 个页面,从登录、领券中心,到售后拍照传凭证、帮助中心,全部做完,没有「敬请期待」式的占位页面。

另外两个端各有一张「证件照」。用户网站是三端里最适合大屏的一屏:产地直采的绿色主视觉、轮播图、热销榜、全部商品,PC 和手机浏览器都能打开:

用户网站首页

管理后台则是商家的驾驶舱——今日营业额、待发货订单、近 7 天销售趋势、分类营业额占比、订单状态分布、热销 TOP10,进店第一眼全部铺开:

管理后台数据看板

管理后台还带着商品/订单/售后/评价管理、会员卡、优惠券、轮播图配置和角色权限管理。地址在最顶上的引用块里,欢迎点开玩一玩。

系统全貌:三端一体,共用一个后端

整个系统分三个端:顾客用的小程序和网站,商家用的管理后台。它们共用同一套后端接口和同一个数据库:

水果商城架构图:三端一体

这个设计最直接的好处是数据只有一份:商家在管理后台把香蕉的价格从 5.9 改成 6.9,用户在网站和小程序上看到的就是新价格;后台配一张首页轮播图,三端首页同时生效;用户在小程序里加的购物车,换到网站上登录同一个账号,购物车还是那些东西——购物车是三端同步的,不会出现"后台改了前台没变"的尴尬。

代价是后端要同时伺候"顾客"和"商家"两种身份,权限边界必须划得很清楚。后面讲安全的那一节,我会说说我在这里踩的坑。

技术选型:每个选择都有它的理由

选型这件事我一开始也纠结过,比如后端到底用 Java 还是 Node。最后定下来的方案和理由:

技术选型:每个选择都有它的理由

回头看,这套组合没有一样是"最新最潮"的,但每一个都稳。对个人开发者来说,"遇到问题能搜到答案"比"技术新"重要得多。

数据库:24 张表不是画出来的,是「长」出来的

一开始我坐在那里想"先给我一个完美的数据库设计",后来发现根本做不到——业务没写完之前,你永远不知道哪张表会多出什么字段。所以我换了思路:跟着功能走,做一个功能,长一张表。

整个过程中数据库改了 14 个版本。我用 Flyway 做版本化管理,每次改动都是一个带编号的 SQL 文件(V1、V2……V14),谁在什么时候加了哪张表,一目了然:

水果商城数据库设计:六大业务域,24 张表

有两个是用"惊吓"换来的教训:

第一,钱一律用"分"来存。 金额字段全部存整数(单位是分),不存小数(单位是元)。因为浮点数做加减会有精度误差,0.1 + 0.2 不等于 0.3 这种事,在算钱的场景里是不可接受的。存分、展示的时候再除以 100,就绕开了这个坑。

第二,订单明细要做"快照"。 订单里的商品名、价格、图片,是下单那一刻复制一份存进订单表的,而不是去商品表里实时读。不然商家后来改了价格、下了架,用户翻历史订单时看到的价格就"穿越"了。

三个反复折腾过的技术点

1. 防超卖:我第一版的写法是错的

水果电商有个经典场景:搞秒杀,库存 50 箱,一秒钟涌进来几百个请求。每个请求都要"读库存 → 判断 → 减库存"。我最初写的减库存逻辑翻译成人话是:

1. 读出库存:50
2. 判断 50 > 0,可以卖
3. 写回库存:50 - 1 = 49

自己一个人点着测试没问题。但如果 100 个人同时走到第 2 步呢?大家都读到"库存 50",大家都通过判断,库存就被减成了负数——卖了 100 箱,仓库里只有 50 箱。这叫超卖,真商家遇上是要赔钱的。

为了验证这个问题真实存在(而不是我吓自己),我写了一个并发测试:100 个线程同时抢一个库存 50 的商品。用第一版逻辑跑,毫无悬念地卖超了。修复方式是把"判断"和"扣减"合并成一条数据库的原子操作:

UPDATE product_sku
SET stock = stock - 1
WHERE id = ? AND stock >= 1

这条语句在数据库内部执行时不受其他请求干扰:库存够就减掉并返回"成功",不够就直接返回"失败"。程序不再自己当裁判,裁判交给数据库。改完之后,同样的 100 线程压测,恰好售罄、一箱不多卖。这个测试我留在了代码里,以后每次动库存相关的逻辑,它都会再跑一遍。

2. 订单状态机:状态比我想的多得多

做之前我以为订单就三个状态:待支付、待发货、已完成。真做起来才发现,加上取消、超时、售后、驳回,状态远比想象的多:

订单状态机:主链与售后分支

最开始我用一个字符串字段随手记状态,想改成什么就改成什么。后来发现任何代码都能把订单改成任意状态——比如跳过"发货"直接"完成"。这才老老实实给状态迁移立了规矩:每种状态只能变到指定的几种状态,不合法的跳变直接拒绝。规矩立好之后,售后、超时取消这些功能反而好做了,因为大家都在同一张地图上走。

3. 安全:我差点给自己埋了一颗雷

系统写完之后,我做了一次自查,结果真的查出了问题:管理后台登录拿到的凭证(token),拿到用户端的接口上居然也能用

这个问题的危险性在于,管理端的 token 权限很大,如果它在用户端接口上畅通无阻,一旦泄露,破坏面是全局的。修复不算难——用户端接口现在会主动拒绝带"管理员身份"的 token,管理端那边本来就这样做了,两边这才算闭环。

顺手一起加固的还有几处:

  • 短信验证码风控:同一手机号 60 秒只能发一次,一小时最多 10 条;验证码 5 分钟过期,试错 5 次直接作废
  • XSS 防护:用户输入的评价内容、收货地址,入库前先"洗"一遍,把 <script> 这类危险代码剥掉
  • CORS 收紧:接口不再允许"任何网站"来调,改成白名单
  • JWT 续期:登录凭证快过期时可以换新,不用动不动重新登录

这些事单看每一件都不大,但"上线前做"和"出事后补",中间隔着的是用户的钱和数据。

小程序端的坑:三个我印象最深的

小程序和普通网页长得很像,但运行环境完全不同,坑就藏在这些"不同"里。

坑一:图标全变成白块。 我的图标原本都是 SVG 格式,浏览器里好好的,到了小程序里全部显示不出来。查了文档才知道,小程序的 image 组件不支持本地 SVG 文件。最后把 17 个图标全部转成 PNG 才解决。教训:跨端开发,先看目标平台的"禁区清单",再动手写代码。

坑二:整页空白,报错却牛头不对马嘴。 某个页面一打开就白屏,控制台报 data is not a function。排查了半天才发现,我在 data() 里直接引用了一个工具函数——浏览器里加载顺序碰巧没事,小程序的运行环境里函数还没初始化就被引用了,整个页面直接挂掉。这类"环境差异型" bug 的特点是报错信息完全不指向真正的原因,最后是靠逐步注释代码二分定位的。

坑三:订单页一打开就报 URLSearchParams is not defined 这个 API 每个浏览器都有,但小程序的 JavaScript 核心(JSCore)里没有。最后自己写了个简单的解析函数替代。从那以后我记住了一条:不能假设"浏览器有的,小程序就有"

为什么小程序做完了,却没有上线

这是别人知道我做了这个项目后,问我的第一个问题。也是整个项目里最"非技术"的一课。

小程序上线(提审发布)不是写完代码就行。以"生鲜水果"这个类目为例,微信官方的资质要求是:

  • 营业执照(个体工商户或企业)
  • 食品经营许可证(经营范围要覆盖生鲜/水果零售)
  • 想接微信支付?→ 还得先用营业执照申请微信支付商户号

也就是说,卡住上线的不是代码,而是资质。营业执照要真实注册,食品经营许可证有经营场所的要求,这些不是"多写几天代码"能解决的事。

所以最后我做了一个权衡,用一张图说清楚:

上线方案:网站先上线,小程序等资质

这件事教会我:工程项目的边界,往往不在技术里,而在技术外面。资质、合规、成本这些东西,应该在立项时就调查清楚,而不是代码写完了才发现走不通。好在这个项目里,被"卡住"的部分我都设计成了可插拔的——等资质到位,填上密钥就能上线,不需要返工。

写在最后

这个项目从设计文档到三端跑通,前前后后做了几个月。累计下来:后端 24 张表、64 个集成测试全绿、GitHub Actions 持续集成、Docker 一键部署配置;网站、管理后台、小程序三个端加起来 40 多个页面。

没做完的也如实说:Redis 缓存还没上(防超卖目前靠数据库原子操作扛,流量大了需要加缓存);秒杀模块建了表和后台管理入口,完整的活动逻辑还没实现;真实微信支付在等资质。

对我自己来说,最大的收获不是"学会了 Spring Boot",而是第一次完整地走过一条业务链路,知道了一个功能从"看起来简单"到"真的稳"中间隔着多少细节。等营业执照办下来,我会把真实支付和小程序提审补完,到时候再来写续集。


本文是个人项目复盘,写给同样在学习路上的同学。文中的演示地址都是真实可访问的环境,欢迎点开逛逛,也欢迎交流指正。

文章链接: