尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Vue+UniApp构建新零售移动电商系统实战指南

SpringBoot+Vue+UniApp构建新零售移动电商系统实战指南 简介基于Java(SpringBoot)、Vue(Element UI)与UniApp构建的新零售移动电商系统完整源码包面向Java全栈开发者、电商项目学习者及需要二次开发的团队。项目采用前后端分离架构Web管理端使用VueElement UI移动端基于UniApp跨平台框架标准RESTful接口、Redis队列与事件机制支撑高并发场景权限可精细到按钮级并内置ECharts数据统计、表单拖拽配置及数据导出能力。压缩包共2000个文件涵盖922个Java源码、304个Vue组件、207个JavaScript脚本以及XML配置、SQL脚本、样式与图标资源等整体约28.46MB目录结构完整含详细代码注释与系统手册便于快速上手和二次开发。已有2183人学习下载适合作为电商中后台与移动端一体化开发的参考基线可直接用于学习研究或商业项目改造。1. 新零售移动电商系统的技术底座SpringBoot Vue UniApp 各司其职新零售移动电商系统本质上要同时解决三头问题C 端用户在小程序/H5/App 上逛店下单运营人员在后台维护商品、处理订单以及两端共享同一套业务规则与数据。把这三头压在一套代码里最常见的落地组合就是 Java(SpringBoot) 提供接口能力Vue(Element UI) 搭运营后台UniApp 做用户端跨端打包。这篇内容围绕这套组合讲清楚工程结构、典型业务闭环和部署时的关键参数。适合正在选型的技术负责人也适合需要快速搭出可演示电商项目的后端或前端工程师。2. SpringBoot 端搭建最小可运行的电商服务骨架2.1 先用一个 Maven 工程把基础依赖列清楚后端不急着堆微服务。新零售系统在起步阶段一个 SpringBoot 单体工程加拆好的模块包结构比一上来就拆订单服务、用户服务更容易维护和排查问题。我一般会用 SpringBoot 2.7.x 配 MyBatis-Plus 做数据访问、Redis 做缓存与登录态鉴权采用 JWT文件存储先走本地磁盘后续需要再换 OSS。对应的 pom.xml 关键依赖如下。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency /dependencies依赖版本建议固定到具体数字。SpringBoot 版本选的太高会连带要求 JDK 版本升级很多团队本地开发环境还停在 JDK 8强行上 SpringBoot 3 反而增加无意义的成本。MyBatis-Plus 3.5.5 对 SpringBoot 2.x 兼容良好字段自动填充、分页插件都能直接使用。2.2 包结构划分把管理端与用户端的接口路径分开后端代码的包结构决定了后续多人协作时会不会频繁冲突。常见的做法是在 controller 层区分admin与app两个子包分别对应 Vue 后台管理端和 UniApp 移动端service 层不区分端因为订单创建、商品查询这类核心业务是两端共用的。一个建议的结构是。com.retail.mall ├── common # 统一返回、异常处理、常量 ├── config # Redis、MyBatis-Plus、拦截器配置 ├── controller │ ├── admin # 后台管理端接口 │ └── app # 移动端接口 ├── service # 业务逻辑 ├── mapper # MyBatis-Plus Mapper ├── entity # 数据库实体 └── dto # 入参与出参对象路径前缀最好一并约定后台接口统一/admin/**移动端接口统一/app/**。这样一个拦截器就能按前缀区分校验逻辑。/admin/**校验管理员 JWT/app/**校验用户 JWT公开接口放在/pub/**不做任何鉴权。后续不管 UniApp 还是 Vue 端联调看接口路径就知道该带什么 token排查 401 问题也省事。2.3 用 JWT Redis 控制登录态而不是裸 Token移动端电商系统里用户登录、下单、支付都要识别身份同时我们还要能随时把一个被顶号的用户踢下线。纯 JWT 做不到主动失效所以我一般用 JWT 保存 uid 和随机会话号再把会话号作为 Redis key 的一部分存一份value 是用户基础信息 JSON。校验时先看 Redis 是否存在存在才认为 Token 有效。public class AuthInterceptor implements HandlerInterceptor { Autowired private StringRedisTemplate stringRedisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { response.setStatus(401); return false; } String jwt token.substring(7); Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); String uid claims.get(uid).toString(); String sessionKey login:token: uid; if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(sessionKey))) { response.setStatus(401); return false; } request.setAttribute(uid, uid); return true; } }这段代码体现了一个重要细节Redis 的 key 里带着 uid而不是整段 token。这样当我们修改用户密码、后台禁用账号时只需要删除这个 key就能让该用户所有端立即失效。Token 本身的过期时间可以设置到 7 天Redis 过期时间也设置成 7 天两者保持一致避免出现 JWT 有效但 Redis 已过期的不一致状态。2.4 商品列表接口与分页参数设计新零售系统里商品列表是访问量最大的接口首页、分类页、搜索页都会用到。分页参数要统一设计pageNum从 1 开始pageSize单页数量不超过 100排序字段与排序方向由前端传入但要在后端做白名单校验防止随意传危险字段。代码示例如下。public PageResultProductVO productList(ProductQuery query) { PageProduct page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getCategoryId()), Product::getCategoryId, query.getCategoryId()) .eq(query.getOnSale() ! null, Product::getOnSale, query.getOnSale()) .orderByAsc(Product::getSortOrder) .orderByDesc(Product::getCreateTime); PageProduct result productMapper.selectPage(page, wrapper); // 转换为 ProductVO填充销量与价格区间 return PageResult.of(result); }查询接口有几个常见坑。第一个是orderByDesc与orderByAsc叠加时实际生成的 SQL 排序字段顺序就是代码调用顺序要让固定排序字段在前、时间排序在后这样分页数据才稳定第二个是 MyBatis-Plus 的逻辑删除配置要打开否则下架或删除的商品还会出现在列表里第三个是 Page 对象查询后result.getTotal()是数据库总条数如果数据量过大需要配合 Redis 缓存总条数减少 count 查询压力。3. Element UI 搭建运营后台的商品管理与订单查询3.1 Vue 工程结构与 Element UI 按需加载运营后台最核心的模块是商品管理和订单查询前端用 Vue 2 Element UI 依然是非常稳的选择。Vue 3 配合 Element Plus 能做组合式 API但如果你接手的项目里已经有一批基于 Vue 2 的组件直接升级成本比较高。我建议按“能不能平滑替换”来决定而不是追新版本。Element UI 的全部组件包体积较大首屏加载慢需要用babel-plugin-component做按需引入。以按钮和表格为例在main.js中按需引入。import Vue from vue; import { Button, Table, TableColumn, Dialog, Form, FormItem, Input, Select, Option, Pagination, Message } from element-ui; Vue.use(Button); Vue.use(Table); Vue.use(TableColumn); // 其他组件按同样方式引入 Vue.prototype.$message Message;按需引入之后还要确认babel.config.js里有对应的插件配置不然样式会丢。实际项目里容易漏掉Pagination和Dialog导致订单列表翻不了页、商品编辑弹窗没有样式这类问题查起来很浪费时间。3.2 商品分类树与表格联动商品管理页左侧是分类树右侧是商品表格。分类树用el-tree右侧表格用el-table两个组件之间通过当前选中的分类 id 联动。分类树在选择节点时需要区分“叶子节点”和“父节点”不然会出现选了父分类也带出所有子分类商品的情况。el-tree :datacategoryTree :props{ label: name, children: children } node-keyid highlight-current node-clickhandleCategoryClick /handleCategoryClick的逻辑是如果 selected node 下面还有 children就把查询条件里的 categoryId 设成空只按父级模糊匹配如果是叶子节点就精确匹配该分类 id 下的商品。这个操作看似简单但因为分类层级一般为三级前端展示要展开到默认两层树节点刷新后要保留选中状态。3.3 订单查询页的筛选条件与状态流转订单查询是新零售后台最重的页面。筛选条件至少包含订单号、用户手机号、订单状态、下单时间段这些条件要组合查询。表格列一般展示订单号、用户信息、商品摘要、实付金额、订单状态、下单时间、操作按钮。订单状态用数字存数据库前端用字典映射成中文状态流转按钮根据当前状态显示不同操作。下面是订单查询页面的核心代码框架。el-form :inlinetrue :modelqueryForm el-form-item label订单号 el-input v-modelqueryForm.orderNo placeholder请输入订单号 clearable / /el-form-item el-form-item label订单状态 el-select v-modelqueryForm.status clearable placeholder全部 el-option label待付款 :value1 / el-option label已付款 :value2 / el-option label已发货 :value3 / el-option label已取消 :value4 / /el-select /el-form-item el-form-item label下单时间 el-date-picker v-modelqueryForm.timeRange typedatetimerange value-formatyyyy-MM-dd HH:mm:ss start-placeholder开始时间 end-placeholder结束时间 / /el-form-item el-form-item el-button typeprimary clickloadOrderList查询/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form查询时间范围时后端接口接收两个参数startTime和endTime前端在提交前要把this.queryForm.timeRange拆开传参。时间段跨度过大时数据库create_time上的索引不一定能用得上需要采用“查询开始时间与结束时间在同一月内”或“超过 3 个月必须选到具体日期”的前端校验。loadOrderList方法里要用对象展开避免直接修改 queryForm 导致 watch 反复触发。订单状态流转的按钮逻辑也要细心处理比如待付款订单可以“关闭”已付款订单可以“发货”已发货订单可以“完成”。每个操作调用不同的后端接口操作前要有二次确认弹窗确认框建议用this.$confirm不要用浏览器的原生 confirm。用户在意的是操作结果反馈用$message.success提示并在回调中重新加载列表比默默刷新体验更好。4. UniApp 开发新零售用户端从商品展示到购物车结算4.1 页面结构一个仓库跑小程序、H5、App 三端用户端需要同时覆盖微信小程序、微信公众号 H5、以及 iOS/Android App 时UniApp 是省成本的路子。页面逻辑用 Vue 写一套样式尽量用 flex 布局平台差异通过条件编译处理。工程创建时选择uni-app模板默认支持h5、mp-weixin、app三个平台。页面目录结构按业务划分商城首页、分类页、购物车、个人中心、订单列表是五大金刚。pages ├── index # 首页 ├── category # 分类 ├── cart # 购物车 ├── user # 个人中心 ├── order │ ├── list # 订单列表 │ └── detail # 订单详情 ├── goods │ ├── detail # 商品详情 │ └── search # 搜索页面间跳转用 uni 自带的 API比如uni.navigateTo和uni.switchTab。注意 tabBar 页面只能用uni.switchTab而普通页面用uni.navigateTo。新手一上来在首页点“购物车”如果用的 navigateTo会碰到“页面不存在”的报错或者干脆没反应。定位这类问题先检查 tabBar 配置里有没有注册对应页面。4.2 首页数据加载与触底分页以及 H5 定位问题首页的推荐商品列表通常采用触底分页加载。用onReachBottom生命周期监听触底每次滚动到底就请求下一页数据直到hasMore为 false。请求是在onLoad时发起的而不是在onShow里因为首页从详情页返回时每次重新刷新列表会让用户觉得浏览不连贯。触底分页的加载要注意loading锁避免快速滚动时重复请求同一个下一页。methods: { loadRecommend() { if (this.loading || !this.hasMore) return; this.loading true; request({ url: /app/goods/recommend, data: { pageNum: this.pageNum, pageSize: 10 } }).then((res) { const list res.data.records; this.goodsList.push(...list); this.hasMore res.data.total this.goodsList.length; this.pageNum; }).finally(() { this.loading false; }); } }在 H5 端还有一类高频问题首页要做定位根据定位展示最近的门店。微信公众号内嵌页面获取定位需要调用微信 JS-SDK 的getLocation接口而这一步的难点在于签名。签名需要的timestamp、nonceStr和签名 URL 必须是当前页面地址但公众号内嵌页面用了 Vue Router 的 history 模式时URL 变化时签名常常会失效。常见的做法是只在首页进入时拉取一次签名并缓存同时保证后端拿到的 URL 是window.location.href.split(#)[0]去掉 hash否则签名会不一致。4.3 购物车与服务端同步而不是只存本地购物车是移动端最容易把数据搞乱的地方。只把购物车存到本地换设备就没法同步全部走服务端前端每次增删都要等接口返回。常见的设计是本地保存购物车列表每次操作后同步一次服务端进入购物车页时把服务端数据拉下来合并。合并规则以时间戳为准本地有且服务端没有的条目直接上传两边都有的一条以数量多者为准。购物车列表的数据项至少包括skuId、spuId、数量、选中状态、商品标题、单价、图片、库存上限。勾选状态要存储到本地因为结算时需要知道哪些商品被选上。function syncCart(cartList) { const syncData cartList.map((item) ({ skuId: item.skuId, quantity: item.quantity, selected: item.selected })); request({ url: /app/cart/sync, method: POST, data: { items: syncData } }); }同步接口的设计要注意幂等性。即同一份购物车数据重复提交服务端最终结果应该一致。用skuId作为业务键后端在同步时按照“存在则更新数量不存在则新增”的逻辑处理而不是先删除再插入。否则接口超时重试时商品数量和勾选状态会被重置。4.4 商品详情的 SKU 选择与库存校验UniApp 的商品详情页通常在页面底部放一块“选择规格”弹窗弹窗里展示 SKU 组合。前端拿到商品详情后要生成 SKU 规格树比如颜色、尺码、版本三个维度一组合可能几十个 SKU。用户每次点一个规格值时都要计算当前组合是否有效无效要置灰。function isSkuAvailable(specs, currentSelection) { // currentSelection 为已选项例如 { color: 黑, size: L } const availableSkus skuList.filter((sku) { return Object.keys(currentSelection).every((key) { return !currentSelection[key] || sku.specs[key] currentSelection[key]; }); }); return availableSkus.some((sku) sku.stock 0); }这个计算函数依赖服务端返回的skuList其中每个 sku 包含specs对象、stock、price、skuId字段。如果库存是 Redis 缓存要看前端展示的库存是否实时实时库存最好由详情接口返回不单独增加请求次数。前端置灰逻辑只做展示真正的库存校验一定要在提交订单接口里再查一次防止用户连点或者本地数据过期导致超卖。4.5 提交订单与模拟支付流程购物车勾选商品后进入结算页结算页确认收货地址、支付方式、商品明细最后把订单提交到后端。下单接口需要传入地址 id、从购物车选中的 sku 列表、备注、支付方式等。服务端生成订单后返回订单号前端带着订单号调用支付接口。在开发阶段通常不会真正接入微信支付或支付宝支付而是先让后端提供一个模拟支付接口前端点击“立即支付”后调用该接口后端直接把订单状态改为已付款这样能先把整个下单流程打通。5. 从本地开发到部署上线的几个关键配置与打包要点新零售系统的联调和上线阶段工作重心从写业务代码转移到处理环境和不同端的打包细节上。生产中常见的问题可以按端来梳理。前端部署是纯静态资源后端是打好的 Jar 包两者相对独立但用户端在微信小程序里打开时还涉及域名白名单和业务域名校验需要提前在公众号和小程序后台配置合法域名。移动端 H5 打包后要部署在 HTTPS 域名下且不能用 ip 端口方式访问否则微信里没法正常发请求。后端部署时SpringBoot 的配置文件要按环境拆分比如application-dev.yml、application-prod.yml生产环境的数据库密码和 Redis 密码不能明文写在配置文件里。常见做法是通过环境变量注入例如用jasypt对密码加密再用环境变量传入解密密钥这样即使代码仓库泄露生产密码也不会直接暴露。新零售系统上线时最好先给管理后台加一层访问控制比如限制内网 IP 访问部署到云服务器时用安全组只放行必要端口。最后订单数据要每天做一次全量备份因为新零售场景里订单表增长很快不做冷备万一误删数据会很被动。本文还有配套的精品资源点击获取
返回列表