news 2026/10/6 9:51:24

基于SpringBoot+Vue的无人智慧超市全栈系统设计与实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot+Vue的无人智慧超市全栈系统设计与实现解析

1. 项目整体设计与技术选型解构

1.1 这套“无人智慧超市”到底在解决什么问题

先说个大白话的定位:这套系统是以无人零售场景为业务底座,把传统超市的收银、进销存、会员管理搬到线上,配合扫码进店、自助结算、库存预警这些“去人化”流程,做成一套前后端完全分离的Web管理系统。也就是说,用户侧有购物的小程序风格页面(这里我们用Vue实现Web端模拟),管理侧有运营后台,两端共用同一套后端接口,由SpringBoot统一提供RESTful API,MyBatis负责数据持久化,MySQL承载所有业务数据。

我之所以说这套项目“值得完整跟一遍”,是因为它把Java后端和Vue前端几乎所有的日常开发动作都覆盖了:建表、写Mapper、配事务、做鉴权、跨域处理、打包部署。你跟着做完,基本就把全栈开发的完整链路打通了一次。对于处在“会写CRUD但没做过完整系统”阶段的同学来说,这是一个能把知识串成线的绝佳练习;对需要交毕设、做课设的在校生来说,它更是一套可以直接二次开发的现成骨架。

这套系统的业务链路可以这样理解:顾客扫码或刷脸进店(这里用账号登录模拟)→ 浏览商品 → 加购 → 自助结算 → 订单生成 → 后台更新库存 → 运营端查看销售报表、管理商品上下架。整条链路里没有收银员,全靠系统自动处理。对应到技术实现上,前端需要处理购物车状态、结算流程、页面路由;后端需要处理订单事务、库存扣减、幂等校验——每一环都踩得到真实业务里的难点。

1.2 为什么偏偏是SpringBoot+Vue+MyBatis+MySQL这套组合

先说后端。SpringBoot能在2025年依然是Java Web项目的首选,靠的不是炫技,而是它把SSM(Spring+SpringMVC+MyBatis)时代最头疼的配置问题几乎清零了。你不需要再写一大堆XML去定义Bean,不需要手工装配数据源,一个spring-boot-starter-web加几行application.yml,一个能跑起来的Web服务就有了。对于无人超市这种需要快速开发、频繁迭代的中小型系统,用SpringBoot做后端是效率最高的选择,没有之一。

MyBatis在这套系统里的角色是“SQL掌控者”。它不像JPA那样帮你自动生成SQL,而是把SQL完全交到你手里,通过XML或注解精确控制每一条查询。在订单汇总、库存流水这类对SQL有精细要求的业务里,这种掌控感极其重要。比如统计某段时间的销售榜单,MyBatis里写个动态SQL,根据前端传过来的时间范围、商品分类灵活拼条件,代码可读性和执行效率都远好过JPA自动生成的一大坨。

Vue作为前端框架,核心优势是组件化和响应式。无人超市的购物车页面,用户每加一件商品,角标数字、合计金额、库存状态都要即时变化,Vue的响应式数据绑定让这些交互只需要维护好一个cartList数组,剩下的交给框架去更新DOM,开发体验非常顺滑。

MySQL则是这套组合里最稳的地基。它开源、免费、资料多,事务支持可靠,对无人超市这种读多写少、偶尔有批量导入导出需求的系统,性能完全够用。而且国内大多数中小型公司的生产环境就是MySQL,学它不会有“学了用不上”的问题。

最后说“前后端分离”。这个架构决策不是赶时髦,是实际业务逼出来的:无人超市的顾客端和管理端未来极可能一个是H5/小程序、一个是PC后台,两端需要复用同一套后端接口。后端只提供JSON数据,不关心谁在消费;前端专注于页面交互,想换皮肤、加功能都不影响后端。开发时前端用npm run dev起在8080端口,后端跑在8081,通过代理或CORS实现联调;上线时前端构建成静态文件交给Nginx或SpringBoot托管,分工非常清晰。

1.3 模块划分:用一张“业务地图”看穿整个系统

接手任何项目,我习惯先画业务地图,搞清楚有哪几个端、哪几类用户、哪些核心模块,再动手写代码。这套无人超市系统,从用户角色出发可以拆成三个主要模块:

  • 顾客端(Web/H5方向):注册登录、商品浏览、分类筛选、购物车管理、订单结算、订单历史查看。
  • 管理后台(运营方向):商品管理(上下架、库存修改、价格调整)、分类管理、订单管理(发货/退款/状态流转)、会员管理、销售统计报表。
  • 系统公共模块:统一认证鉴权(JWT Token)、全局异常处理、跨域配置、文件上传(商品图片)、定时任务(可选,比如自动下架库存为零的商品)。

数据库表我建议这样设计,后面实现起来最顺手:

数据表核心字段说明
userid, username, password, balance, create_time顾客/管理员账号,可用role字段区分
categoryid, name, sort商品分类
productid, category_id, name, price, stock, image, status商品主表,status控制上下架
cartid, user_id, product_id, quantity购物车(也可以前端维护,但后端存一份更稳)
ordersid, order_no, user_id, total_amount, status, create_time订单主表,status记录待支付/已支付/已取消等
order_itemid, order_id, product_id, product_name, price, quantity订单快照明细,防止商品后来改名/改价影响历史订单
stock_logid, product_id, change_count, type, create_time库存流水,方便追溯

这个表结构不是随便拍的,有几个设计点是实战中容易忽略的:order_item里为什么要把product_name和price冗余存一份?因为商品信息是可变的,今天叫“可口可乐”,明天可能改叫“可乐330ml”,如果订单详情去关联实时商品表,历史订单显示就乱了。stock_log表则是为了排查库存异常准备的,谁在什么时间改了多少库存,一查便知,这在无人超市的补货场景里非常实用。

2. 后端核心实现:从搭建到接口落地的全流程

2.1 SpringBoot项目骨架搭建与基础配置

后端我选择用SpringBoot 2.7.x版本,为什么不追最新?因为2.7版本对应Spring Cloud Alibaba等生态的兼容性最成熟,网上资料也最丰富,踩坑时容易找到解决方案。JDK用1.8,这是大多数公司生产环境的标配,虽然JDK17都出很久了,但1.8在MyBatis、老项目依赖的兼容性上依然是最省心的。

项目结构我建议按功能分包,而不是按技术分层:

com.smartmart ├── controller // 控制器,只做参数接收和结果封装 ├── service // 业务逻辑层,事务控制在这里 ├── mapper // MyBatis的Mapper接口 ├── entity // 数据库实体类 ├── dto // 前端交互的数据传输对象 ├── vo // 视图对象,比如购物车VO、订单VO ├── config // 配置类:跨域、拦截器、WebMvcConfig ├── common // 公共类:统一返回结果、异常处理、常量 ├── util // 工具类:JWT工具、日期工具 └── SmartMartApplication.java

这种按业务模块组织包的方式,在项目变大之后比controller/service/dao三层平铺更容易维护——毕竟真实的开发是按业务功能推进的,不是按技术层推进的。

application.yml里几个关键配置我直接给你,省得自己查:

server: port: 8081 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_mart?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl logging: level: com.smartmart.mapper: debug

注意serverTimezone=Asia/Shanghai,这个不加,MySQL 8.x的驱动会报时区错误,这是我会在“常见问题”里强调的。map-underscore-to-camel-case必须设为true,否则数据库的create_time字段映射不到Java的createTime属性上。log-impl是MyBatis打印SQL的配置,开了它你才能在控制台看到每一条执行的SQL和参数,这对调试太重要了。

2.2 MyBatis的XML映射与动态SQL实战

MyBatis用XML写Mapper是我个人推荐的风格——虽然注解写起来快,但一旦SQL复杂起来(多表关联、动态条件),注解的可读性会断崖式下跌,而且没法复用SQL片段。在这个无人超市系统里,商品列表的查询就是个典型场景:前端要支持按分类筛选、按价格排序、按关键词模糊搜索,页面可能还带分页。

<select id="selectProductPage" resultType="com.smartmart.entity.Product"> SELECT id, category_id, name, price, stock, image, status, create_time FROM product <where> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY ${sortField} ${sortOrder} LIMIT #{offset}, #{pageSize} </select>

这段SQL里有几个细节值得展开。<where>标签是MyBatis的动态SQL利器,它会自动处理第一个条件前面的AND,避免你写“WHERE 1=1”这种让人诟病的写法。${sortField}用的是字符串替换而不是参数占位,这是因为ORDER BY子句不能预编译绑定参数,但要小心${}注入,所以sortField一定要做白名单校验,只允许传入白名单里的字段名,这个我会在后端代码里加上校验逻辑。

另一个核心SQL是库存扣减,这里必须用乐观锁或条件更新来防止超卖。无人超市的高并发场景不多,但“同一商品同时被多个人下单”的理论冲突还是要防:

<update id="deductStock"> UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock &gt;= #{quantity} </update>

这一步是关键中的关键:更新前必须先判断当前库存是否足够减,并且把“判断和扣减”合并到一条SQL里,这样数据库层面的行锁天然帮我们挡住了超卖。如果返回的影响行数为0,说明库存不足,业务层再去抛异常、回滚事务。很多初级开发者喜欢先SELECT stock判断再用UPDATE去减,这两条语句之间存在时间窗口,并发下一定会出问题,实战中千万别这么写。

2.3 JWT鉴权与拦截器设计

前后端分离项目里,Session方案天然不合用——前端不在同一个域,Cookie跨域麻烦,而且移动端根本不吃Session这一套。所以这里选JWT做无状态鉴权,流程是:用户登录成功后,后端签发一个Token返回给前端,前端存到localStorage里,每次请求头带上Authorization: Bearer <token>,后端通过拦截器解析并校验。

JWT工具类核心代码如下:

public class JwtUtil { private static final String SECRET = "smart-mart-secret-key-2025"; private static final long EXPIRE = 24 * 60 * 60 * 1000L; // 24小时 public static String createToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }

拦截器的逻辑是:放行登录/注册接口,其余接口一律校验Token,解析失败或过期直接返回401。这里要说一个很多人忽略的问题——Token过期后前端拿到的应该是“未授权”的JSON响应而不是页面跳转,因为前端是SPA,路由跳转要由前端处理。所以我会定义一个AuthInterceptor,捕获异常后往响应流里写JSON:

response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write(JSON.toJSONString(Result.error(401, "Token无效或已过期")));

这个细节做好,前后端联调时你会少掉很多头发。前端Axios响应拦截器里遇到401就清理本地Token并跳转登录页,整个鉴权闭环就通了。

2.4 订单创建的分布式事务与幂等设计

无人超市的“下单”是最能考验后端功底的一环。先看事务:创建订单这个操作至少要往三张表写数据——orders主表、order_item明细表、product表的库存扣减,可能还有stock_log流水。任何一个步骤失败,前面的都不能留。所以这个Service方法必须加@Transactional注解:

@Override @Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 1. 生成订单号:时间戳+用户ID+随机数 String orderNo = generateOrderNo(dto.getUserId()); // 2. 插入订单主表 Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); order.setTotalAmount(calculateTotal(dto.getItems())); order.setStatus(0); // 待支付 orderMapper.insert(order); // 3. 遍历购物车项:扣库存 + 写明细 for (OrderItemDTO item : dto.getItems()) { int affected = productMapper.deductStock(item.getProductId(), item.getQuantity()); if (affected == 0) { throw new BusinessException("商品[" + item.getProductName() + "]库存不足"); } orderItemMapper.insert(buildOrderItem(order.getId(), item)); } // 4. 清空购物车 cartMapper.deleteByUserId(dto.getUserId()); return buildOrderVO(order); }

rollbackFor = Exception.class很关键,默认情况下Spring事务只对RuntimeException回滚,如果你在事务方法里抛的是自定义的BusinessException但它不是RuntimeException的子类,事务不会回滚,数据就脏了。踩过这个坑的人都知道有多痛。

再说“幂等”:如果用户点了一下“支付”按钮没反应,又点了一下,前端重复请求,后端就创建了两笔一模一样的订单——这是绝对不能发生的。解决办法是在订单表加一个user_id + create_time的业务唯一约束,或者更优雅的做法是前端生成一个requestId(UUID),后端在Redis里用SETNX命令做防重:

boolean firstRequest = redisTemplate.opsForValue() .setIfAbsent("order:request:" + dto.getRequestId(), "1", 10, TimeUnit.MINUTES); if (!firstRequest) { throw new BusinessException("请勿重复提交"); }

这个设计在支付场景里尤其重要,因为支付回调也涉及幂等。无人超市的自助结算如果不做这个,顾客支付时手机卡顿重试一次就下了两单,售后问题会直接爆炸。

3. 前端Vue设计与交互实现要点

3.1 从零初始化Vue项目与工程化配置

前端这块我用Vue 3 + Vite + Element Plus这套组合。Vue 3的Composition API用起来比Options API顺手太多,逻辑复用能力完全不是一个级别——购物车里加购、删减、清空这些操作封装成useCart()组合式函数,组件里几行就调完,代码量直接砍半。

Vite相比Webpack,启动速度和热更新体验是“用过就回不去”的,日常开发启动只要一两秒。初始化工程:

npm create vite@latest smart-mart-web -- --template vue cd smart-mart-web npm install npm install axios vue-router@4 pinia element-plus @element-plus/icons-vue

这里我特意用Pinia做状态管理而不是Vuex,因为Pinia是Vue3官方推荐的新一代状态库,API更简洁,对TypeScript的支持更好。购物车状态就可以放Pinia里,跨页面共享不会丢。

工程结构上,我习惯这样组织:

src ├── api // 每个模块的接口请求文件 │ ├── product.js │ ├── order.js │ └── user.js ├── router // 路由配置 ├── stores // Pinia状态仓库 │ ├── cart.js │ └── user.js ├── views // 页面组件 │ ├── Home.vue │ ├── Cart.vue │ └── Order.vue ├── components // 公共组件 ├── utils // axios封装、工具函数 └── App.vue

3.2 Vue Router的路由设计与登录守卫

无人超市的前端需要这几个页面:商品首页、购物车、结算页、订单列表、管理后台的商品管理/订单管理/数据看板。路由配置里要注意两个点:一是管理后台的路由需要懒加载,避免首屏一次性加载所有后台代码拖慢速度;二是需要加全局前置守卫做登录校验。

const routes = [ { path: '/', component: () => import('@/views/Home.vue') }, { path: '/cart', component: () => import('@/views/Cart.vue') }, { path: '/login', component: () => import('@/views/Login.vue') }, { path: '/admin', component: () => import('@/layouts/AdminLayout.vue'), meta: { requiresAuth: true, role: 'ADMIN' }, children: [ { path: 'products', component: () => import('@/views/admin/ProductManage.vue') }, { path: 'orders', component: () => import('@/views/admin/OrderManage.vue') }, { path: 'dashboard', component: () => import('@/views/admin/Dashboard.vue') } ] } ] router.beforeEach((to, from, next) => { const userStore = useUserStore() if (to.meta.requiresAuth && !userStore.token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } })

这个全局守卫配合后端的JWT拦截器前后呼应,前端管体验(没登录先跳去登录页),后端管安全(每个接口都校验Token)。双保险在实战中是标准配置,不要只依赖一端。

3.3 Axios封装与跨域联调的关键配置

Axios如果不封装,项目里会出现大批重复代码,每个页面都要写catch、写错误提示。我会封装一个request.js,统一处理baseURL、超时时间、请求头携带Token、响应拦截器:

import axios from 'axios' import { ElMessage } from 'element-plus' import { useUserStore } from '@/stores/user' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动带Token request.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${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.data }, error => { if (error.response?.status === 401) { const userStore = useUserStore() userStore.logout() router.push('/login') ElMessage.error('登录已过期,请重新登录') } else { ElMessage.error(error.message || '网络异常') } return Promise.reject(error) } ) export default request

baseURL设为/api,然后用Vite的代理配置把请求转发到后端的8081端口,这样最干净,不会出现跨域CORS问题。vite.config.js配置如下:

export default defineConfig({ server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8081', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } } })

要用代理而不是直接写http://localhost:8081的原因是:浏览器跨域限制只认域名+端口,直接用8081请求会被CORS拦截;代理则是在Vite开发服务器层把请求转出去,浏览器看到的始终是同源的3000端口,完美绕开跨域问题。虽然后端也能配CORS,但在开发阶段用代理更轻量,而且把环境相关的配置收口在前端工程里,团队协作时后端不用为前端的环境差异做适配。

3.4 商品列表与购物车交互的实现细节

商品列表页是整个前端最核心的页面,涉及请求后端、加载状态、分类切换、关键词搜索、加入购物车。Vue3里用onMounted生命周期发请求获取数据,用ref管理响应式状态。这里有个细节:分类切换时,商品列表要显示加载动画,避免用户反复点击旧分类还没有更新的数据闪一下。解决方案很简单:切换分类时先把productList清空、loading设为true,数据返回后再赋值。

购物车交互是无人超市体验的重点。我的做法是:购物车放Pinia,页面只负责展示,加减商品、计算合计都在store里完成。核心代码如下:

export const useCartStore = defineStore('cart', { state: () => ({ items: [], // [{ productId, name, price, quantity, image }] selectedIds: [] }), getters: { totalPrice: (state) => { return state.items .filter(item => state.selectedIds.includes(item.productId)) .reduce((sum, item) => sum + item.price * item.quantity, 0) .toFixed(2) }, totalCount: (state) => { return state.items.reduce((sum, item) => sum + item.quantity, 0) } }, actions: { addItem(product) { const existing = this.items.find(item => item.productId === product.id) if (existing) { existing.quantity++ } else { this.items.push({ productId: product.id, name: product.name, price: product.price, quantity: 1, image: product.image }) } }, removeItem(productId) { this.items = this.items.filter(item => item.productId !== productId) }, clear() { this.items = [] } } })

购物车加购的数量校验不能只在后端做,前端也要限制最多不能超过商品库存数,否则用户加购100件,提交后端时返回“库存不足”,体验很差。前端在商品卡片上直接拿到stock字段,加购按钮判断quantity >= stock时就禁用。

4. 部署上线与常见问题排查实录

4.1 本地环境搭建:MySQL安装与数据库初始化

很多同学项目跑不起来,不是因为代码问题,而是环境没装对。这里把MySQL这块说得细一点。我推荐装MySQL 8.0。Windows上安装的坑主要在服务初始化和密码策略:如果装的时候选了不支持空密码,后续登录就麻烦。我的做法是安装时直接选“Use Legacy Password Authentication”,避免8.0默认加密插件带来的兼容问题(老的Navicat连不上就是这个问题)。

装好之后创建一个干净的库:

CREATE DATABASE smart_mart DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

注意一定是utf8mb4,不是utf8。因为utf8在MySQL里最多存3字节,像手机号里的隐藏字符、特殊emoji这类4字节字符直接报错,utf8mb4才是完整的Unicode支持。

然后执行项目里的schema.sql建表,再执行data.sql插入测试数据。我用的是Spring Boot的sql.init机制,在application.yml里配:

spring: sql: init: mode: always schema-locations: classpath:db/schema.sql >server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/smart-mart-web; index index.html; location /api { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } }

try_files $uri $uri/ /index.html这行是因为Vue Router如果用createWebHistory(history模式),刷新/cart页面时Nginx找不到真实的/cart文件会返回404,必须把所有路由都重写到index.html,由前端路由接管。

4.3 高频报错与排查技巧速查表

这里把我在部署这个项目的过程中遇到的坑列成一个表,每一行都是实测过的:

报错现象根本原因解决方案
启动报Access denied for user 'root'@'localhost'MySQL密码错误或账号权限不足检查application.yml里的用户名密码;执行ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码';
连接报Public Key Retrieval is not allowedMySQL 8.0的caching_sha2_password插件问题JDBC URL加allowPublicKeyRetrieval=true
启动报The server time zone value 'Öйú±ê׼ʱ¼ä'时区未配置JDBC URL加serverTimezone=Asia/Shanghai
中文乱码数据库/表/连接三个层级编码不一致统一为utf8mb4,连接参数加characterEncoding=utf8
MyBatis报Invalid bound statement (not found)Mapper接口和XML不匹配检查XML的namespace是否等于接口全限定名;检查mapper-locations路径
前端请求404,后端有路径代理路径问题检查Vite代理的rewrite是否正确,或者后端是否加了context-path
前端请求跨域报错开发用了直连而非代理,或后端CORS未配置开发环境用Vite代理;生产环境Nginx处理跨域
端口被占用8080被其他程序占用netstat -ano | findstr 8080找到PID,taskkill /PID <pid> /F
刷新页面404Vue Router history模式未配置Nginx加try_files $uri $uri/ /index.html;
打包后前端接口请求地址不对请求路径被硬编码了localhost:8081统一用/api相对路径,部署环境用Nginx代理或环境变量控制baseURL

还有一个高频问题,就是SpringBoot版本太高导致依赖不兼容。很多初学者一上来就用SpringBoot 3.x,结果发现MyBatis的starter、一些老代码里的javax包全部换成jakarta了,各种报错无从下手。对这个项目,我建议直接用2.7.x,等把整套系统跑通了再去升级3.x。这类问题看起来是版本兼容细节,实际会让新手浪费大量时间,先跑通再升级是最高效的路径。

4.4 关于数据库连接池与线程池的调优心得

最后分享一个我在联调阶段发现的性能问题。系统默认的HikariCP连接池配置在写并发比较大时会出现“Connection is not available, request timed out after 30000ms”的报错,原因是默认池大小是10,但一次性插入多个订单明细时,每张表操作都要从池里拿一个连接——虽然MyBatis的事务内连接是复用的,但并发多个请求时就可能把连接池打满。

我的调整是:

spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000

maximum-pool-size不是越大越好,MySQL服务端的并发连接数是有限制的,一般业务系统配20到30就够了,配太大反而因为连接竞争拖垮数据库。

关于MyBatis缓存还有一个容易踩的坑。MyBatis默认开启一级缓存(SqlSession级别),在高并发场景下如果有脏数据写入,一级缓存可能返回过期数据。所以我在系统中把一级缓存的默认行为保持在SESSION级别,这样在小项目里性能最优;但如果将来扩展成微服务、多实例部署,就要小心缓存各实例不同步的问题,那时候改关闭MyBatis的二级缓存或引入Redis集中缓存才是正解。

5. 从这套系统出发的下一步演进方向

每次做完一套这种全栈系统,我都会习惯性地回头梳理:这个项目还能往哪些方向走,哪些模块是容易扩展的。这个习惯能帮你从一个“会写CRUD”的人慢慢变成“会设计系统”的人。

无人超市这个场景其实天然适合做数据挖掘,现在的订单表、商品表、库存流水表就是现成的数据源。你可以统计出“哪些商品经常被一起购买”,在结算页做个“搭配推荐”;可以按小时维度统计销售热度,发现每天下午5点到7点是饮料类的高峰期,提醒运营在这段时间补货。“无人”带来的好处就是所有行为都留下了数据痕迹,这些数据是实体超市根本没有的宝贵资源。

技术层面的重点演进方向,一个是接口层引入Redis缓存,商品列表、热门推荐这些读多写少的数据直接缓存起来,吞吐量会有一个量级的提升。另一个是把订单模块改成异步化:下单成功后丢进消息队列,由消费者异步扣库存、发通知,这样用户结算时不需要等待全部链路走完,体验会更好。当然这会引入消息中间件,系统复杂度会上升一个台阶,但这是从“可运行”走向“可支撑业务”的必经之路。

我再多嘴一句和这套系统直接相关的实务问题:如果这个项目是要拿去当毕设或作品集,建议你至少往两个方向深挖一个小功能。比如做数据可视化——把订单数据按天聚合,用ECharts画个销售趋势图,这在前端展示上非常加分的;或者加一个Excel导入商品的功能,用EasyExcel写一个导入接口,这会让评委觉得你不仅会CRUD,还懂实际业务里高频使用的效率工具。别堆功能,挑一个做深,比做十个半吊子功能有用得多。

对我个人而言,写这套系统最大的收获不是“学会了SpringBoot的注解怎么写”,而是理解了“为什么每个模块要这样设计”——这是只看文档学不来、只有自己动手做完整项目才能沉淀下来的东西。如果你正在学Java全栈,我建议你花一到两周时间,认真把这套系统从头到尾自己敲一遍、部署一遍、踩一遍坑,这个过程值回票价。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 9:50:08

OpenShell深度配置指南:从安装到多机同步的完整实践

1. 从"OpenShell"这个名字说起&#xff1a;它到底是个什么东西第一次看到"OpenShell"这个词&#xff0c;很多人会下意识地把它和"Shell"脚本、命令行终端联系起来。这个直觉不算错&#xff0c;但也不完全对。OpenShell在技术圈里其实指向一个非常…

作者头像 李华
网站建设 2026/10/6 9:49:17

Python自动化脚本实战:从文件整理到Excel报表与定时任务

你有没有过这样的早晨&#xff1a;打开电脑&#xff0c;先把上周的测试报告从十几个文件夹里拖出来&#xff0c;重命名成规范格式&#xff0c;再手工汇总到一张Excel表里&#xff0c;顺便把下载目录里乱七八糟的安装包按类型归置好。等这一套做完&#xff0c;半小时已经过去了&…

作者头像 李华
网站建设 2026/10/6 9:48:19

Agent-Reach 实战:CLI 工具链整合与高并发 Agent 调度调优

1. 项目缘起与核心定位Agent-Reach 这个名字第一次出现在我视野里的时候&#xff0c;我正被一堆零散的 AI Agent 工具链折腾得够呛。那段时间我在同时维护三套不同架构的 Agent 项目&#xff0c;一套基于 Python 的 LangChain 生态&#xff0c;一套是团队内部用 Rust 重写的轻量…

作者头像 李华
网站建设 2026/10/6 9:48:08

OpenShell 开始菜单替换工具:安装配置与深度定制指南

1. 从零认识 OpenShell&#xff1a;它到底解决什么问题第一次听到 OpenShell 这个名字&#xff0c;很多人会下意识以为它又是一个新的命令行工具或者某个操作系统的外壳程序。实际上&#xff0c;OpenShell 是一个面向 Windows 平台的开始菜单替代与增强工具&#xff0c;它的核心…

作者头像 李华
网站建设 2026/10/6 9:47:57

Set集合全解析:从数学原理到编程、数据库与配置实战

刚看到"Set 集合系列"这个题目的时候&#xff0c;我脑子里蹦出来的不是某一种单一技术&#xff0c;而是一连串画面&#xff1a;数学课上那个画着圈圈交叠的韦恩图&#xff0c;Java 里天天见的 HashSet&#xff0c;MongoDB 里安静躺着数据的 collection&#xff0c;还…

作者头像 李华
网站建设 2026/10/6 9:47:12

Agent-Reach CLI工具集:AI Agent工程化与并发管理实践

1. 项目缘起与核心定位 Agent-Reach 这个名字第一次出现在我视野里的时候&#xff0c;我正被一堆零散的 AI Agent 工具链折腾得够呛。那段时间我在同时维护三个不同技术栈的智能体项目&#xff0c;一个基于 Python 的 LangChain 做知识问答&#xff0c;一个用 Rust 写的高频任务…

作者头像 李华