资讯详情

资讯详情

运动户外电商小程序全栈实践:SpringBoot+Vue3+小程序商城开发

做运动户外电商小程序是不是一定得先搞个商品管理后台再写用户端很多同学从大厂开源项目里复制一个商城模板结果改了一周连商品 SKU 都还没理顺最后只能把需求砍掉。这次要拆解的“运动户外交易小程序”定位很明确后端用 SpringBoot 系列管理端用 Vue3用户端是小程序商城。如果你手里正缺一个能跑通“商品、下单、支付、管理”全链路的项目参考或者准备把一套传统 Web 电商迁到微信生态里这篇就把它的架构逻辑、核心代码和部署验证关键点一次讲透。这个项目真正有价值的地方不只是“能买能卖”而是它的技术选型很适合从零搭建中小型电商SpringBoot 负责提供稳定 REST APIVue3 负责后台商品管理和订单处理小程序端承接 C 端流量。三者组合可以拆开用也可以整体复用。很多二开项目之所以改到一半就放弃是因为一开始没规划清楚三端边界。本文会从需求拆分、表结构设计、后端 API 结构、Vue3 管理端和小程序端代码示例一直讲到联调排错适合正在做毕业设计、私活外包或公司内部电商中台的同学收藏对照。1. 为什么运动户外类商城适合做小程序而不是纯 H5先给结论户外运动商品的购买决策越来越依赖场景化内容和即时比价而小程序正好卡在“打开成本低、用户留存可控、支付链路完整”这个位置。相比传统电商 App小程序不需要用户去应用商店下载相比 H5 商城小程序在微信内可以通过扫一扫、搜索、公众号菜单、分享卡片直接进入。特别是户外运动类目比如露营装备、跑步服饰、骑行配件用户经常是在微信群看到活动讨论后临时起意购买。小程序分享到群里可以带商品图片和价格信息体验比 H5 链接更原生打开路径也短。从开发侧看运动户外商城和小程序结合还有几个隐形优势服务端可以只输出 JSON 接口。前端不管是 Vue3 管理后台、小程序还是未来的 App都能共用一套 SpringBoot API。库存和订单逻辑集中在后端处理。户外装备有些规格复杂比如帐篷分几人帐、颜色分几款SKU 设计好坏直接决定代码量。微信公众号生态可以二次引流。运动户外用户常看攻略类推文文章插入小程序卡片后购买转化路径非常短。但如果反过来问什么时候不太适合小程序答案是如果你有很强的品牌会员体系和私域复购需求需要复杂 CRM 和个性化首页那原生 App 或者更大体量的电商平台更合适。小程序更适合场景驱动、轻量交易、依赖社交传播的类目。运动户外恰好符合这个画像。2. 项目整体技术架构与核心模块划分这个运动户外电商项目从结构上看基本是经典的“管理端 用户端 服务端”三端分离。管理端承载运营人员操作用户端就是用户在微信里看到的小程序商城服务端提供所有数据接口、权限校验、业务规则。2.1 后端技术栈与核心设计后端主框架是 SpringBoot。按当前行业习惯如果是 SpringBoot3 及以上的版本会引入 Spring Security 做认证授权搭配 JWT 做无状态登录。数据库主要以 MySQL 为主缓存可以使用 Redis。因为在商城场景里首页商品列表和热门推荐对响应速度要求高直接查 MySQL 也能跑但并发上来后 Redis 的价值会立刻体现。后端模块规划上比较推荐的拆分方式是- controller 接收前端请求做参数校验 - service 处理业务逻辑比如下单、支付回调、库存扣减 - mapper 数据库操作层 - entity 数据库实体映射 - dto 前端接口传输对象避免直接暴露数据库字段 - vo 给前端展示用的视图对象 - config 各种配置类比如跨域、安全配置 - utils 通用工具比如 JWT、日期处理2.2 Vue3 管理后台的设计方向管理端技术栈为 Vue3。相比 Vue2Vue3 的 Composition API 在复杂表单、商品编辑、订单筛选这类页面里非常有优势。你可以把商品分类、品牌管理、SKU 规格、订单状态全部抽象成独立的 Composable 函数多个页面复用同一套逻辑不用像 Options API 那样在 data、methods、computed 之间来回跳。管理端核心页面建议至少包含仪表盘今日订单量、销售额、待发货数量商品管理商品列表、新增/编辑商品、SKU 库存管理、上下架分类管理运动户外类目树形管理订单管理订单列表、订单详情、发货操作、退款处理用户管理会员列表、用户状态Banner 管理首页轮播图、活动位配置2.3 小程序端技术选型小程序端没有采用纯微信原生开发而是用更通用的小程序跨端方案。原因很简单运动户外商城如果以后要扩展到支付宝小程序或抖音小程序纯原生微信代码几乎无法复用。跨端框架让你保留一套代码编译到多个小程序平台。跨端框架下的页面结构通常采用“页面 组件”的组织方式。首页、分类页、购物车、个人中心、商品详情、订单确认、支付结果等页面在工程目录里都应有清晰的对应关系。一个典型的小程序端目录结构参考src ├── api // 接口请求 ├── components // 通用组件如商品卡片、价格标签 ├── pages // 页面 │ ├── index // 首页 │ ├── category // 分类 │ ├── cart // 购物车 │ ├── user // 个人中心 │ ├── goods // 商品详情 │ └── order // 订单确认与列表 ├── static // 静态资源 ├── stores // 全局状态管理 └── utils // 请求封装、登录态管理3. 核心数据表设计与关系梳理电商项目跑不掉的几张核心表是用户表、商品表、分类表、SKU 库存表、购物车表、订单表、订单明细表。做运动户外商城时还要根据类目特点在商品表中预留品牌、适用季节、活动场景等字段。这些字段的规划直接决定你后期做筛选时是想直接写 SQL 还是需要单独做搜索引擎。下面是一个最简可用的表结构参考。先不要急着加几十个字段第一版能满足下单流程后续再迭代。3.1 用户表CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) DEFAULT NULL COMMENT 微信小程序用户openid, nickname VARCHAR(64) DEFAULT NULL COMMENT 昵称, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像, status TINYINT DEFAULT 1 COMMENT 状态 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;3.2 商品表和 SKU 表CREATE TABLE product ( id BIGINT NOT NULL AUTO_INCREMENT, category_id BIGINT NOT NULL COMMENT 分类id, name VARCHAR(128) NOT NULL COMMENT 商品名称, subtitle VARCHAR(255) DEFAULT NULL COMMENT 副标题, main_image VARCHAR(255) DEFAULT NULL COMMENT 主图, detail TEXT COMMENT 商品详情, status TINYINT DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; CREATE TABLE sku ( id BIGINT NOT NULL AUTO_INCREMENT, product_id BIGINT NOT NULL COMMENT 商品id, spec_info VARCHAR(255) DEFAULT NULL COMMENT 规格描述 如 黑色/L, price DECIMAL(10,2) NOT NULL COMMENT 价格, stock INT NOT NULL DEFAULT 0 COMMENT 库存, image VARCHAR(255) DEFAULT NULL COMMENT 规格图片, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品SKU表;3.3 订单表与订单明细表订单表主要记录一笔订单的整体信息订单明细表记录订单下的每个 SKU。这种主从表设计是电商订单的标准做法退款、统计时拆开查都很方便。CREATE TABLE orders ( id BIGINT NOT NULL AUTO_INCREMENT, order_no VARCHAR(64) NOT NULL COMMENT 订单编号, user_id BIGINT NOT NULL COMMENT 用户id, total_amount DECIMAL(10,2) NOT NULL COMMENT 订单总金额, pay_amount DECIMAL(10,2) NOT NULL COMMENT 实付金额, pay_status TINYINT DEFAULT 0 COMMENT 0未支付 1已支付 2已退款, receiver_name VARCHAR(32) DEFAULT NULL, receiver_phone VARCHAR(20) DEFAULT NULL, receiver_address VARCHAR(255) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE order_item ( id BIGINT NOT NULL AUTO_INCREMENT, order_id BIGINT NOT NULL, product_id BIGINT NOT NULL, sku_id BIGINT NOT NULL, product_name VARCHAR(128) NOT NULL, spec_info VARCHAR(255) DEFAULT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;3.4 表设计时容易踩的坑金额类型必须用 DECIMAL不要用 DOUBLE否则到分以后对账会出现奇奇怪怪的误差。订单号要单独加唯一索引。生成时建议用“时间戳 用户ID后几位 随机数”的组合避免用数据库自增 ID 直接当订单号容易被别人看出单量。商品表的详情字段建议用 TEXT但如果你以后要存富文本格式详情最好把详情迁移到对象存储数据库里只保存 HTML 或者渲染地址。SKU 的规格描述不要拆得过碎。比如“颜色 黑 尺码 L”可以合并成一个字符串黑色/L在购物车页展示时直接显示即可避免为了一个展示逻辑写一大堆动态查询。4. SpringBoot 后端接口设计与代码实现后端代码是整个系统的核心。代码结构再清晰如果接口设计不规范前端对接时仍然会毫无头绪。下面从登录鉴权、商品浏览、下单三个关键链路分别给出核心思路和示例。4.1 登录鉴权方案移动电商场景里小程序端一般使用微信登录。流程是小程序端先调用微信的登录接口拿到code然后传给后端后端拿着code请求微信接口换openid如果用户是第一次登录自动注册老用户直接登录成功。服务端实现一个面向小程序端的通用登录接口RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; PostMapping(/login) public ResultString login(RequestBody LoginRequest request) { if (request.getCode() null || request.getCode().isEmpty()) { return Result.error(code不能为空); } String token userService.loginByWechatCode(request.getCode()); return Result.success(token); } }loginByWechatCode方法中携带code调用微信接口拿到 openid 后根据业务签名生成自己的登录 token。这里需要注意小程序端登录成功后Token 统一放在请求头Authorization里传回给后端后端通过拦截器校验。4.2 首页商品列表接口首页通常需要返回分类、Banner 和推荐商品。为避免前端一次请求太多次可以从聚合角度做一个首页数据接口。GetMapping(/index) public ResultIndexVO index() { IndexVO vo new IndexVO(); vo.setBannerList(productService.listBanners()); vo.setCategoryList(categoryService.listAllCategories()); vo.setHotProducts(productService.listHotProducts()); return Result.success(vo); }注意IndexVO这个对象是聚合返回前端数据的载体。很多初学者直接把数据库实体 List 返回给前端一旦某天表结构变化前端就会不稳定。所以后端接口层尽量定义清楚的 VO把想要的字段放进去数据库字段变化时不影响对外协议。4.3 商品详情接口商品详情页展示的信息通常很丰富包括商品基本信息、图集、SKU 列表、销售属性。小程序端打开商品详情页时建议一次性拿到 SKU 数据前端做规格选择不需要用户每次点击规格都请求后端。商品详情接口示例GetMapping(/detail/{productId}) public ResultProductDetailVO detail(PathVariable Long productId) { ProductDetailVO detail new ProductDetailVO(); Product product productService.getById(productId); BeanUtils.copyProperties(product, detail); ListSkuVO skus skuService.listByProductId(productId); detail.setSkuList(skus); return Result.success(detail); }这里有个小技巧如果商品详情中的图片较多可以返回到一个ListString前端用 Swiper 渲染即可不需要返回拼接好的 HTML。后续如果要把详情改成视频介绍或者其他多媒体内容只需要在 VO 里增加字段不影响现有字段。4.4 购物车逻辑购物车功能在小程序端管理比较合适因为购物车本身实时性要求不高可以把数据保存在前端状态中。但也存在一个问题用户换手机或者清掉微信缓存后购物车可能会丢失。更稳妥的方案是登录状态下购物车数据存后端。比如用户添加购物车时调用接口写入一张购物车表。如果未登录再使用前端本地缓存。很多商城项目第一版为了快点上线前后端各做一套结果登录用户和非登录用户的数据很难合并工程复杂度反而上去了。建议第一版直接做后端购物车表结构也比较简单public class CartItem { private Long id; private Long userId; private Long skuId; private Integer quantity; private Integer checked; // 是否选中 }购物车不需要存价格和商品名展示购物车时再联表查询 SKU 表和商品表。这样价格变化时购物车列表能立刻展示最新价格不会出现“加购时 99 元订单页变成 89 元”之后还要额外判断的历史包袱。4.5 下单接口与库存扣减下单是整个电商项目中逻辑最复杂的一个接口。这里给一个最小可跑通的顺序从前端拿到 skuId、数量、收货地址等信息。后端查询 SKU 当前库存检查是否足够。扣减库存。计算订单总金额生成订单主表和订单明细。如果支付成功更新订单状态如果未支付超过时间后取消订单恢复库存。在单机环境下使用数据库行锁或乐观锁控制库存足够Transactional(rollbackFor Exception.class) public Long createOrder(OrderCreateRequest request, Long userId) { for (OrderItemRequest itemRequest : request.getItems()) { Sku sku skuMapper.selectBySkuIdForUpdate(itemRequest.getSkuId()); if (sku null || sku.getStock() itemRequest.getQuantity()) { throw new ServiceException(库存不足); } } Orders order buildOrder(request, userId); ordersMapper.insert(order); for (OrderItemRequest itemRequest : request.getItems()) { orderItemMapper.insert(buildOrderItem(order.getId(), itemRequest)); skuMapper.decreaseStock(itemRequest.getSkuId(), itemRequest.getQuantity()); } return order.getId(); }这里有一个必须注意的点Transactional注解只对运行时异常回滚。如果你在业务逻辑里手动 catch 了异常并且不抛出事务是不会回滚的。所以在下单接口里库存不足等异常要主动抛给上层让事务统一回滚。否则很可能出现一种情况订单没创建成功但接口返回成功用户以为下单成功了数据库库存也没有扣减后面对账时异常麻烦。小额交易场景里使用selectBySkuIdForUpdate这种行锁方式已经能保证防超卖。但它的代价是锁库存的并发性能有限。如果未来商城单量变大再考虑 Redis Lua 脚本做预扣减或者引入消息队列做异步下单。项目第一版不要为了性能把架构搞得太复杂能稳定跑通核心链路更重要。5. Vue3 管理后台从登录到商品上下架管理后台用的是 Vue3配合 Element Plus 之类的组件库。Vue3 相比 Vue2 最大的体感差异是代码组织更灵活状态管理和数据请求可以独立封装页面组件不再被复杂的this搞得稀里糊涂。5.1 创建项目与基本配置创建 Vue3 项目有两种常用路径Vite 和 Vue CLI。Vite 目前更主流创建命令很直接。npm create vitelatest sports-admin -- --template vue cd sports-admin npm install npm run dev如果需要使用 TypeScript可以把vue模板改为vue-ts。但中小型管理后台不一定非要用 TS团队习惯优先。安装 Element Plus 和路由npm install element-plus npm install vue-router4 npm install axios在main.js中注册 Element Plusimport { createApp } from vue import ElementPlus from element-plus import element-plus/dist/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(ElementPlus) app.use(router) app.mount(#app)5.2 封装 Axios 请求商城项目中管理员登录成功后返回的 Token 需要附带在每次请求的 Header 中。这里建议统一封装 Axios 实例响应层统一处理错误码和登录过期而不是每个页面单独写拦截器。// src/utils/request.js import axios from axios import { ElMessage } from element-plus import { useUserStore } from ../stores/user const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const userStore useUserStore() if (userStore.token) { config.headers.Authorization userStore.token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { ElMessage.error(登录状态已过期请重新登录) // 跳转登录页 } else { ElMessage.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request5.3 商品列表页商品列表页用el-table展示每个商品行需要有编辑、上下架按钮。Vue3 的 Composition API 写起来很自然template div classproduct-list el-card el-button typeprimary clickgoCreate新增商品/el-button /el-card el-table :dataproductList v-loadingloading border el-table-column propid labelID width80 / el-table-column propname label商品名称 min-width200 / el-table-column propprice label价格 width100 / el-table-column label状态 width100 template #default{ row } el-tag :typerow.status 1 ? success : info {{ row.status 1 ? 上架 : 下架 }} /el-tag /template /el-table-column el-table-column label操作 width200 template #default{ row } el-button sizesmall clickgoEdit(row.id)编辑/el-button el-button sizesmall :typerow.status 1 ? warning : success clicktoggleStatus(row) {{ row.status 1 ? 下架 : 上架 }} /el-button /template /el-table-column /el-table /div /template script setup import { ref, onMounted } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import request from ../../utils/request const router useRouter() const productList ref([]) const loading ref(false) const loadProductList async () { loading.value true const res await request.get(/admin/product/page, { params: { page: 1, limit: 10 } }) productList.value res.data.records loading.value false } const toggleStatus async row { await request.put(/admin/product/status/${row.id}) ElMessage.success(操作成功) loadProductList() } const goCreate () router.push(/product/edit) const goEdit id router.push(/product/edit/${id}) onMounted(loadProductList) /script5.4 商品编辑页商品编辑页是整个管理后台里比较复杂的一个。至少包含基本信息、图片上传、SKU 设置三块。一个较实用的做法是后端先接收商品基础信息并返回商品 ID然后再单独调用 SKU 接口保存 SKU 列表避免嵌套数据校验麻烦也方便改动 SKU。例如保存完商品基本信息后再调用 SKU 保存接口一次性把前端收集到的 SKU 数组传给后端后端先删掉旧的 SKU 再批量插入新的 SKU。这种“先删后插”的方式在中小项目里很常见。但要注意一个前提在下单过程中如果有用户正在查询该商品详情会短暂看不到 SKU。这时候最好在事务里执行保证删除和插入要么同时成功要么同时回滚。6. 运动户外小程序端代码示例小程序端是用户直接触达的入口开发时重点要处理好三件事登录态保存、商品数据渲染、支付调起。核心页面建议先做好首页、商品列表、商品详情、购物车、确认订单、订单列表。6.1 登录流程封装小程序端登录逻辑不能简单用“后端登录后返回 token前端保存到本地”一句话带过。这里需要处理两个关键问题静默登录失败怎么办、Token 过期怎么办。很多刚接触小程序开发的同学会直接写一个wx.login然后调后端登录接口。实际上对于需要获取头像昵称的商城建议进入个人中心或下单时才弹出授权而首页和商品列表用静默登录就能完成。下面是最小可用的静默登录代码export function silentLogin() { return new Promise((resolve, reject) { uni.login({ provider: weixin, success: async loginRes { const { code } loginRes try { const res await request.post(/api/user/login, { code }) uni.setStorageSync(token, res.data.token) resolve(res.data.token) } catch (err) { reject(err) } }, fail: err reject(err) }) }) }6.2 首页商品列表渲染首页通常包含 Banner、金刚区图标、热卖商品瀑布流。在跨端框架中商品列表建议用自定义组件封装商品卡片组件可以在首页、搜索页、推荐位、订单相关页面复用。一个简单商品卡片组件的思路template view classgoods-card clickgoDetail image classgoods-image :srcgoods.mainImage modeaspectFill / view classgoods-info view classgoods-name{{ goods.name }}/view view classgoods-price{{ goods.price }}/view /view /view /template script setup const props defineProps({ goods: { type: Object, default: () ({}) } }) const goDetail () { uni.navigateTo({ url: /pages/goods/detail?id${props.goods.id} }) } /script这里需要注意modeaspectFill如果不设置网络图片在小程序里可能显示不出正常的比例尤其是户外装备这种需要展示完整外观的图片图片比例被拉伸会非常影响真实感。6.3 商品详情与 SKU 选择交互商品详情页的 SKU 选择是小程序商城体验的核心。后端返回 SKU 数组后前端需要根据规格维度把可选和不可选的选项区分出来。这里不做太复杂算法给一个常用处理思路把所有 SKU 的规格信息解析出来比如颜色、尺码两个维度。用户点击颜色时查看哪些尺码可选不可选的置灰。选完所有维度后匹配出唯一 SKU展示对应价格和图片。这样实现时需要把规格维度数据化。比如后端返回的 SKU 数组为[ { skuId: 1, specInfo: 黑色/L, price: 599, stock: 10 }, { skuId: 2, specInfo: 黑色/M, price: 599, stock: 0 }, { skuId: 3, specInfo: 蓝色/L, price: 599, stock: 5 } ]前端可以按specInfo拆分开得到颜色数组[黑色, 蓝色]、尺码数组[L, M]并把每个“颜色尺码”的组合映射到 SKU ID。这样选择交互并不难实现。6.4 调起微信支付当用户提交订单后后端会生成一笔待支付订单。此时小程序端需要拿到支付参数并调起微信支付。一个典型的支付调起代码const pay async orderId { const res await request.post(/api/pay/unifiedOrder, { orderId }) const payParams res.data uni.requestPayment({ provider: wxpay, timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: payParams.signType, paySign: payParams.paySign, success: () { uni.showToast({ title: 支付成功 }) uni.redirectTo({ url: /pages/order/detail?id orderId }) }, fail: err { // 用户取消支付或支付失败跳转到订单列表待支付 Tab uni.redirectTo({ url: /pages/order/list?status0 }) } }) }支付回调顺序建议后端先收到微信支付回调更新订单支付状态再通知前端跳转。但实际网络环境下回调可能稍有延迟前端拿到支付成功状态后可以先轮询订单接口确认后端已经更新了订单状态再跳转避免用户看到订单还是待支付。7. 项目启动与运行验证很多读者拿到项目后第一步不是改代码而是想知道怎么把它跑起来。下面按“后端、管理端、小程序端”三部分给出验证路径。7.1 后端启动启动 SpringBoot 项目前先确认Java 版本与项目要求一致如果是 SpringBoot3 系列一般需要 Java17 及以上。MySQL 已启动项目配置的数据库名、用户名、密码正确。如果代码里使用了 Redis确保 Redis 服务也在运行。启动时直接运行主类mvn spring-boot:run看到类似Started Application in xx seconds的日志就说明后端启动成功。接着可以验证一个公开接口curl http://localhost:8080/api/product/detail/1如果返回 JSON说明接口正常。7.2 Vue3 管理端启动与跨域问题在admin目录下安装依赖并启动npm install npm run devVite 默认端口一般是 5173。后端接口如果运行在 8080需要配置 Vite 代理解决跨域// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })配置好后管理端页面里的/api请求会自动转发到后端不会出现浏览器 CORS 报错。7.3 小程序端启动小程序端一般用 HBuilderX 或微信开发者工具打开。如果使用跨端框架需要先安装依赖然后编译到微信小程序。在 HBuilderX 中运行到微信开发者工具的常见步骤是在工具里导入小程序端源码目录或者通过 CLI 初始化工程。安装依赖执行npm run dev:mp-weixin或点击 HBuilderX 的运行按钮选择“运行到小程序模拟器 - 微信开发者工具”。保证微信开发者工具已开启服务端口。如果运行时提示“不是开发者”需要在微信开发者工具的“设置-安全设置”中打开服务端口同时使用有权限的微信账号扫码登录。另外小程序请求后端时本地开发环境中不能直接请求http://localhost。微信开发者工具里可以勾选“不校验合法域名”但在真机预览时需要在小程序后台配置 HTTPS 合法域名。如果你只是本地调试建议先把后端接口跑在一个局域网 IP然后在小程序请求封装里改成你的局域网地址方便手机和电脑联调。上线前再替换成正式的 HTTPS 域名。8. 常见问题与排查思路电商商城项目启动后问题往往不是集中在大功能上而是分布在环境、接口、数据一致性这些容易被忽视的环节。下面整理几个最常见的问题和排查路径。问题现象可能原因排查方式解决方案后端启动失败Java 版本不匹配或依赖冲突查看启动日志执行mvn dependency:tree看冲突按项目要求切换 JDK 版本统一 SpringBoot 相关依赖版本前端请求接口跨域管理端直接请求了不同域名或端口打开浏览器 Network 看响应头检查 Vite 代理配置用 dev server 代理后端开启 CORS 配置小程序图片加载失败图片域名未配置到合法域名或网络图片禁用了 HTTP在开发者工具中查看 Network 请求图片上传到对象存储并配置 HTTPS开发阶段可临时勾选不校验域名登录提示 code 无效微信code使用过一次就失效重复调用会被拒绝检查接口是否只调用一次wx.login每次登录重新调用wx.login获取新 code下单时库存不扣减事务未生效或异常被吞掉检查 Service 方法是否有Transactional是否被当前类内部调用保证入口方法由 Controller 调用Service 内部自调用事务不生效支付成功后订单仍是待支付微信支付回调未能正常通知后端查看后端回调日志检查回调地址是否可公网访问本地可用内网穿透工具收回调线上检查回调 URL 是否正确页面刷新后登录态丢失Token 未持久化查看前端是否有统一读取和写入 TokenVue3 管理端用 localStorage/sessionStorage小程序用 Storage 保存8.1 事务边界问题深入说明很多同学第一次做订单功能时会在OrderService内部再调用一个本地updateStock方法然后发现自己明明把updateStock也加了Transactional库存还是没扣订单却创建了。原因在于 Spring 事务默认基于 AOP 代理同类内部调用不会走代理。OrderServiceImpl里的方法调用另一个方法本质上是this调用没有进入代理逻辑所以内层方法上的Transactional不生效。解决办法有两种要么把库存扣减逻辑拆到另一个 Service 类中让OrderService注入该 Service要么在OrderServiceImpl中通过AopContext.currentProxy()获取代理对象再调用内部方法。推荐第一种职责也更清晰。8.2 图片上传的域名配置问题小程序网络图片、管理后台图片、后端存储图片看起来是三个前端文件路径实际上都指向同一个对象存储服务。你上传图片时如果返回的是http://链接在微信小程序里默认是不能显示的因为微信小程序要求所有网络资源都必须是 HTTPS。这里的处理路径是开发环境直接用开发者工具的“不校验合法域名”联调和线上推荐传到对象存储然后通过其自带 HTTPS 域名访问。如果你只是个人项目也可以用一些免费的图床但稳定性不好保证。总之不要在数据库表里直接保存本地磁盘路径D://xxx.jpg一旦换服务器全部图片都会失效。8.3 时间字段时区问题SpringBoot 返回 JSON 时LocalDateTime默认可能序列化为数组格式前端拿到后很难解析。处理方法是统一配置 Jacksonspring.jackson.date-formatyyyy-MM-dd HH:mm:ss spring.jackson.time-zoneGMT8如果项目中大量使用LocalDateTime配合JsonFormat(pattern yyyy-MM-dd HH:mm:ss)使用会更稳妥。小程序端拿到时间后可以用原生字符串直接展示不用额外引入日期处理库。9. 最佳实践与工程建议9.1 接口文档先行商城这种多端联调项目接口文档是最不能省的成本。后端写接口前先整理一份“接口路径、请求参数、返回结构”的表格或文档。只要前端、后端按同一份文档开发联调阶段至少能减少一半争执。常见的接口文档方案有 Swagger/OpenAPI也可以直接用 Apifox 之类的工具管理。建议在 SpringBoot 中集成 springdoc-openapi 等工具启动项目后自动生成接口文档页面前端开发照着文档调试效率比自己翻代码高很多。9.2 商品状态和 SKU 状态联动商品有上下架状态SKU 表也要考虑启用状态。不能只改商品主状态SKU 库存全部照常售卖。如果一个商品下架了用户端商品详情页仍然可以打开历史链接但应该显示“已下架”并禁止加购。这里更稳妥的方式是商品详情接口首先校验商品状态如果下架则直接返回提示而不是把详情数据照常给到前端。另外SKU 库存字段的更新要遵循最小更新原则只更新指定 SKU不能因为编辑了整个商品就把所有 SKU 的库存覆盖成同一个数。编辑页保存时建议把未修改的 SKU 原样提交而不是清空后重新赋值避免人为填错库存。9.3 订单状态机设计商户系统中订单状态不能随便改。比较规范的做法是定义一组状态常量0 待支付 1 已支付待发货 2 已发货 3 已完成 4 已取消 5 已退款后端每个更新订单状态的服务方法里只允许从某个特定状态流转到下一个状态。例如“发货”操作只允许从“已支付待发货”变为“已发货”。如果收到一个从“待支付”直接发货的请求应直接拒绝。这样能防止运营重复点击按钮或接口被异常调用后订单状态错乱。9.4 分页查询规范商品列表、订单列表、用户列表都需要分页。推荐统一使用 Page 对象接收pageNum和pageSize返回结构也固定为{ total: 100, records: [] }不建议用page1limit10这种给每个接口各自定义字段的风格。前后端统一定义分页协议后列表页的切换和刷新代码都可以抽象成通用组件。9.5 日志与可观测性支付、下单这类关键操作一定要打日志。日志里应记录请求用户 ID、订单号、操作类型、入参关键信息。例如下单日志[下单] userId1001, skuId2001, quantity2, orderNo202504100001但要注意日志不要记录完整手机号、身份证号、支付密钥等敏感信息。下单接口的日志尽量避免打印整个请求体特别是包含收货人手机号的请求体。可以用脱敏处理或者只打印 ID 类信息。9.6 前后端环境区分项目里一般需要三套环境配置本地开发、测试环境、生产环境。后端的application.yml可以按不同 profile 拆分application-dev.yml application-test.yml application-prod.ymlVue3 管理端和小程序端也要注意接口地址需要可配置。不要把http://192.168.x.x:8080硬编码在代码里。管理端可以用.env.development和.env.production区分环境变量小程序端可以把请求基地址抽取到一个config.js文件里打包时按平台或环境注入方便测试和发布时切换。只要环境切换做到配置化后面交付部署时会省大量时间。9.7 缓存使用优先级商城首页会重复请求分类和推荐位这一块可以做缓存。但要注意缓存更新不能只在“修改商品”时执行还要考虑上下架操作。一个简单有效的方案是后台任何商品变动都主动删除相关缓存 Key下次请求时重新从数据库加载。相比复杂的双写一致性方案“删缓存 延迟重建”对中小项目更友好。Redis 缓存时不建议把整个商品大对象直接缓存因为字段很多很杂更新频度也不一样。建议给首页聚合数据、Banner 列表、商品简要卡片这些可明确区隔的数据分别设置缓存。热卖商品列表可以缓存 5 到 10 分钟后台人工上架新商品后最坏延迟 10 分钟看到新商品通常可以接受。10. 项目扩展方向运动户外电商系统跑通第一版后后面的迭代方向可以看实际运营需要。第一个方向是营销能力。运动户外类目非常依赖活动运营比如“春季跑步节”“露营装备专场”。相应的系统需要增加优惠券、满减、限时秒杀、拼团等模块。这些模块和订单模块耦合比较高建议提前把订单金额计算逻辑抽成单独的OrderCalculator不要等到新增优惠规则时才发现所有下单代码散落在各个 Service 里。第二个方向是进销存与供应链。做实物电商无论如何绕不开库存管理。如果是自己发货还好如果是供应商代发模式还需要考虑采购单、入库单、盘点单。运动户外装备的 SKU 数量天然比普通服装类更多一套可控的库存管理系统比前端页面炫不炫重要得多。第三个方向是数据统计。当订单量上来后后台仪表盘需要展示销售趋势、热销商品 Top10、转化率等指标。第一版可以直接用 SQL 聚合统计但数据量大以后可能比较吃力。到那个阶段再考虑引入定时任务把统计数据同步到统计表或使用专门的分析数据库。先别急着在第一版上数据仓库和大数据组件因为初期最大的成本还是在业务链路本身。11. 写在最后的几点提醒这里不打算做一个标准化的“全文总结”只想给要动手做类似项目的同学留三个实际经验。第一核心链路优先跑通别一上来就铺满数十张表。一个能正常登录、浏览商品、下单、支付、后台发货的商城比一张庞大的数据库设计文档更能让你理解系统的真实痛点。最开始表结构多不是优势反而会让联调和改需求时不停调整 SQL。第二全栈项目要尤其注意数据类型一致性。后端金额使用BigDecimal前端处理金额时建议统一使用“分”为单位还是“元”为单位要在一个文档里写明白。很多支付对账的脏数据不是算法写错了而是端到端单位没统一。第三小程序和后台管理端是两套独立前端不要试图通过复制代码来同步功能。Vue3 管理端关注表格、弹窗、批量操作小程序端关注触摸交互、图片懒加载、弱网状态两者重心不同。如果以后团队人手多了可以再考虑用同一套组件库或设计系统来统一视觉规范但业务代码没法简单复用。如果你正计划把一个运动户外交易商城项目落地先用本文的思路把项目拆成“后端 API、管理端、小程序端”三个模块确定每个模块的分页协议、登录方案和订单状态流转然后再开始写代码。这样整个项目会清晰非常多。建议把文章里的代码和表结构先跑通一遍再根据自己实际类目做调整。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →