ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

基于SpringBoot+Vue的无人智慧超市全栈系统设计与实现解析 1. 项目整体设计与技术选型解构1.1 这套“无人智慧超市”到底在解决什么问题先说个大白话的定位这套系统是以无人零售场景为业务底座把传统超市的收银、进销存、会员管理搬到线上配合扫码进店、自助结算、库存预警这些“去人化”流程做成一套前后端完全分离的Web管理系统。也就是说用户侧有购物的小程序风格页面这里我们用Vue实现Web端模拟管理侧有运营后台两端共用同一套后端接口由SpringBoot统一提供RESTful APIMyBatis负责数据持久化MySQL承载所有业务数据。我之所以说这套项目“值得完整跟一遍”是因为它把Java后端和Vue前端几乎所有的日常开发动作都覆盖了建表、写Mapper、配事务、做鉴权、跨域处理、打包部署。你跟着做完基本就把全栈开发的完整链路打通了一次。对于处在“会写CRUD但没做过完整系统”阶段的同学来说这是一个能把知识串成线的绝佳练习对需要交毕设、做课设的在校生来说它更是一套可以直接二次开发的现成骨架。这套系统的业务链路可以这样理解顾客扫码或刷脸进店这里用账号登录模拟→ 浏览商品 → 加购 → 自助结算 → 订单生成 → 后台更新库存 → 运营端查看销售报表、管理商品上下架。整条链路里没有收银员全靠系统自动处理。对应到技术实现上前端需要处理购物车状态、结算流程、页面路由后端需要处理订单事务、库存扣减、幂等校验——每一环都踩得到真实业务里的难点。1.2 为什么偏偏是SpringBootVueMyBatisMySQL这套组合先说后端。SpringBoot能在2025年依然是Java Web项目的首选靠的不是炫技而是它把SSMSpringSpringMVCMyBatis时代最头疼的配置问题几乎清零了。你不需要再写一大堆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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 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注意serverTimezoneAsia/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 idselectProductPage resultTypecom.smartmart.entity.Product SELECT id, category_id, name, price, stock, image, status, create_time FROM product where if testcategoryId ! null AND category_id #{categoryId} /if if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null AND status #{status} /if /where ORDER BY ${sortField} ${sortOrder} LIMIT #{offset}, #{pageSize} /select这段SQL里有几个细节值得展开。where标签是MyBatis的动态SQL利器它会自动处理第一个条件前面的AND避免你写“WHERE 11”这种让人诟病的写法。${sortField}用的是字符串替换而不是参数占位这是因为ORDER BY子句不能预编译绑定参数但要小心${}注入所以sortField一定要做白名单校验只允许传入白名单里的字段名这个我会在后端代码里加上校验逻辑。另一个核心SQL是库存扣减这里必须用乐观锁或条件更新来防止超卖。无人超市的高并发场景不多但“同一商品同时被多个人下单”的理论冲突还是要防update iddeductStock 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捕获异常后往响应流里写JSONresponse.setStatus(401); response.setContentType(application/json;charsetUTF-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的业务唯一约束或者更优雅的做法是前端生成一个requestIdUUID后端在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 vitelatest smart-mart-web -- --template vue cd smart-mart-web npm install npm install axios vue-router4 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.vue3.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 requestbaseURL设为/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如果用createWebHistoryhistory模式刷新/cart页面时Nginx找不到真实的/cart文件会返回404必须把所有路由都重写到index.html由前端路由接管。4.3 高频报错与排查技巧速查表这里把我在部署这个项目的过程中遇到的坑列成一个表每一行都是实测过的报错现象根本原因解决方案启动报Access denied for user rootlocalhostMySQL密码错误或账号权限不足检查application.yml里的用户名密码执行ALTER USER rootlocalhost IDENTIFIED BY 新密码;连接报Public Key Retrieval is not allowedMySQL 8.0的caching_sha2_password插件问题JDBC URL加allowPublicKeyRetrievaltrue启动报The server time zone value Öйú±ê׼ʱ¼ä时区未配置JDBC URL加serverTimezoneAsia/Shanghai中文乱码数据库/表/连接三个层级编码不一致统一为utf8mb4连接参数加characterEncodingutf8MyBatis报Invalid bound statement (not found)Mapper接口和XML不匹配检查XML的namespace是否等于接口全限定名检查mapper-locations路径前端请求404后端有路径代理路径问题检查Vite代理的rewrite是否正确或者后端是否加了context-path前端请求跨域报错开发用了直连而非代理或后端CORS未配置开发环境用Vite代理生产环境Nginx处理跨域端口被占用8080被其他程序占用netstat -ano | findstr 8080找到PIDtaskkill /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: 30000maximum-pool-size不是越大越好MySQL服务端的并发连接数是有限制的一般业务系统配20到30就够了配太大反而因为连接竞争拖垮数据库。关于MyBatis缓存还有一个容易踩的坑。MyBatis默认开启一级缓存SqlSession级别在高并发场景下如果有脏数据写入一级缓存可能返回过期数据。所以我在系统中把一级缓存的默认行为保持在SESSION级别这样在小项目里性能最优但如果将来扩展成微服务、多实例部署就要小心缓存各实例不同步的问题那时候改关闭MyBatis的二级缓存或引入Redis集中缓存才是正解。5. 从这套系统出发的下一步演进方向每次做完一套这种全栈系统我都会习惯性地回头梳理这个项目还能往哪些方向走哪些模块是容易扩展的。这个习惯能帮你从一个“会写CRUD”的人慢慢变成“会设计系统”的人。无人超市这个场景其实天然适合做数据挖掘现在的订单表、商品表、库存流水表就是现成的数据源。你可以统计出“哪些商品经常被一起购买”在结算页做个“搭配推荐”可以按小时维度统计销售热度发现每天下午5点到7点是饮料类的高峰期提醒运营在这段时间补货。“无人”带来的好处就是所有行为都留下了数据痕迹这些数据是实体超市根本没有的宝贵资源。技术层面的重点演进方向一个是接口层引入Redis缓存商品列表、热门推荐这些读多写少的数据直接缓存起来吞吐量会有一个量级的提升。另一个是把订单模块改成异步化下单成功后丢进消息队列由消费者异步扣库存、发通知这样用户结算时不需要等待全部链路走完体验会更好。当然这会引入消息中间件系统复杂度会上升一个台阶但这是从“可运行”走向“可支撑业务”的必经之路。我再多嘴一句和这套系统直接相关的实务问题如果这个项目是要拿去当毕设或作品集建议你至少往两个方向深挖一个小功能。比如做数据可视化——把订单数据按天聚合用ECharts画个销售趋势图这在前端展示上非常加分的或者加一个Excel导入商品的功能用EasyExcel写一个导入接口这会让评委觉得你不仅会CRUD还懂实际业务里高频使用的效率工具。别堆功能挑一个做深比做十个半吊子功能有用得多。对我个人而言写这套系统最大的收获不是“学会了SpringBoot的注解怎么写”而是理解了“为什么每个模块要这样设计”——这是只看文档学不来、只有自己动手做完整项目才能沉淀下来的东西。如果你正在学Java全栈我建议你花一到两周时间认真把这套系统从头到尾自己敲一遍、部署一遍、踩一遍坑这个过程值回票价。
返回列表