资讯动态

Spring Boot+SSM+Vue实战:美容院美妆商城系统设计与部署

发布时间:2026/9/11 2:41:11 来源:尧图企业网站定制
在Java后端开发这个圈子里SSMSpring SpringMVC MyBatis几乎是每个初入行的开发人员都绕不过去的组合而Spring Boot的出现又让这个老组合焕发了第二春。至于前端Vue早已成为中小型管理系统的事实标准。今天我想聊的就是基于这三者做的一个美容院美妆化妆品商城管理系统。这类项目在毕业设计和私活里都非常常见但真正做完整、做规范的人不多我把自己实操过程中的完整思路、表结构设计、核心代码、以及踩过的坑整理出来希望对正在做类似项目的朋友有帮助。这个项目不是简单的增删改查demo它覆盖了商城系统最核心的闭环用户注册登录、商品浏览与搜索、购物车、下单支付模拟、后台订单管理、商品库存管理、会员等级与积分。无论你是准备拿它作为毕业设计、面试项目还是想快速搭建一个带商城的行业管理系统这篇内容都能给你一套可以直接落地的方案。1. 画清楚业务边界再动手写代码很多人拿到“美容院美妆商城”这种需求上来就建表、写接口做到一半发现逻辑混乱前后端对接时又推倒重来。我自己的经验是无论项目大小花半天时间把业务边界和用户角色梳理清楚比什么都值钱。1.1 这个系统到底需要几种用户角色美容院美妆商城和管理系统天然有三类使用者C端消费者、B端运营管理员、以及门店操作员美容师。在技术实现上这三类角色对应的是两套前端用户端商城 后台管理端和一套后端接口。我第一次做的时候图省事把后台管理和用户商城放在同一个Vue项目里用路由做区分结果权限控制写得很痛苦。后来重构改成两个独立的Vue应用共享同一套后端API结构清爽很多。你如果是在课程设计或面试场景里展示这个项目建议采用这种“双前端”结构这也是目前企业里主流的前后端分离方案。用户端核心流程是注册登录、浏览商品、加入购物车、提交订单、模拟支付、查看个人订单、积分签到。管理端核心流程是管理员登录、商品分类管理、商品上下架、订单发货与状态更新、用户管理、会员等级配置、数据看板。这里面最容易忽略的是“门店/美容师”这个角色实际上美业场景里经常需要前台为顾客直接下单所以后台管理端里可以保留一个“代客下单”的入口在代码里体现出来会显得业务思考很完整面试时也是加分项。1.2 为什么非要用SSMSpring Boot的组合可能有人会问既然用了Spring Boot为什么还要说SSM实际上Spring Boot并没有取代SSM它只是把原本Spring、SpringMVC、MyBatis繁琐的XML配置自动化了。你在Spring Boot项目里加入spring-boot-starter-web和mybatis-spring-boot-starter本质还是SSM那套逻辑在跑只是不用再手写一堆配置文件。在真实的项目选型里Spring Boot负责自动配置和项目骨架Spring和SpringMVC处理依赖注入、事务控制和请求分发MyBatis做持久层映射。这套组合最稳的版本搭配是Spring Boot 2.7.x MyBatis 3.5.x MyBatis Spring Boot Starter 2.x高版本Spring Boot3.x因为javax到jakarta的切换问题反而会给新手带来很多隐性的麻烦。我在项目实战中一直坚持一个原则能用稳定的老版本就不要追求最新版本除非新版本能直接解决你当前的技术痛点。Spring Boot 2.7是2.x系列的最后一个大版本坑最少资料最好查。Vue这边我用的是Vue 2 Element UI。现在虽然Vue 3 Element Plus已经普及了但Vue 2的生态成熟度依然很高市面上大量企业管理系统还在用Vue 2维护对快速出活来说Vue 2 Element UI的学习成本和组件丰富度是最平衡的。2. 数据库设计是商城系统的灵魂商城系统的表结构设计有很强的通用性我做了一个简单的ER规划用户表、商品分类表、商品表、购物车表、订单表、订单明细表、积分记录表、会员等级表、轮播图表、管理员表一共十张核心表。下面我挑几张关键的展开说这些都是踩过坑之后总结出来的经验。2.1 用户表不是简单的user表要预留会员扩展字段用户表除了常规的id、username、password、phone、avatar外还要加member_level_id和points两个字段。美妆商城和普通商城不同的地方在于它非常依赖会员体系和复购带动所以从一开始就要把会员等级和积分设计进用户表而不是后面再加。密码字段我强烈建议用BCrypt加密存储不要用MD5。MD5可以被彩虹表直接反查安全隐患很大。Spring Security的BCryptPasswordEncoder可以直接用即使不引入Spring Security全家桶单独引入spring-security-crypto这个依赖包也行。在Service层里做加密校验代码也不复杂。建表的时候还有一个很容易忽略的点金额字段不要用double一定要用decimal(10,2)避免浮点数精度问题。经验之谈订单金额计算如果用了double后面统计报表出现几毛钱的乱差排查起来极耗时。2.2 订单表的状态字段设计订单表是整个系统里最核心、逻辑最复杂的部分它承载了状态的流转。我设计的状态字段是order_status用tinyint类型含义如下0表示待付款1表示已付款待发货2表示已发货3表示已完成4表示已取消。另外加一个pay_status字段区分支付状态0未支付1已支付2已退款很多初学者容易搞混业务状态和支付状态这两个概念我用了两套状态并存的方式逻辑清晰很多。订单表还需要冗余一个total_amount和pay_amount。total_amount是商品原价总额pay_amount是减去优惠、积分抵扣后的实际应付金额这两个字段的值会在生成订单时一次性算好写入。这样做的好处是在订单历史列表中不需要再去关联明细表算价格性能更好也不容易被后面的数据变动影响。设计关联关系时还要记得外键的合理设置例如订单表关联用户表用user_id订单明细表关联订单表和商品表但不要加物理外键只建普通索引方便后续扩展和避免锁竞争。2.3 商品表与库存的边界处理商品表sku_id、product_name、category_id、main_image、detail_images、price、stock、sales、status。对于美妆类商品detail_images可以存多个图片URL用JSON数组格式或者逗号分隔字符串都可以我习惯用JSON数组前端遍历起来更顺手。库存是商城系统里最考验逻辑的字段。并发高的情况下直接用update product set stock stock - 1 where id ? and stock 0这种乐观锁式的SQL来做扣减避免超卖。在这个项目量级下这种写法够用且性能最佳没必要上Redis分布式锁。我自己封装了一个reduceStock方法在事务里先查库存再更新库存更新时带上stock 0条件影响行数为0就抛出库存不足异常。商品上下架用status字段标识1上架、0下架。管理端操作上下架只需要update这一个字段前端如果做了Redis缓存下架时还要注意同步清理缓存不然会出现商品已经下架但前端还能看到的情况。3. 后端核心实现Spring Boot整合SSM的实操细节后端我采用的是经典分层结构controller、service、mapper、entity、common。这个结构大家都很熟悉但真正写得规范的不多。我重点讲几个容易出问题的地方。3.1 application.yml里需要注意的配置项一个清爽的Spring Boot后端配置大概是这样的server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/beauty_mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.beauty.mall.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意几点数据库连接串里的serverTimezone必须设置否则会因为时区问题报错或者时间差8个小时。map-underscore-to-camel-case一定要开启这样数据库的create_time字段能自动映射到Java的createTime属性省去大量手写resultMap的功夫。log-impl配置为StdOutImpl是开发阶段的常备操作可以直接在控制台看到SQL语句和参数排查问题效率极高但生产环境要去掉这个配置否则日志量会很惊人。另外MyBatis的mapper.xml文件如果放在src/main/java目录下打包时会被Maven过滤掉导致启动时报Invalid bound statement (not found)。解决方式有两个把xml放在src/main/resources/mapper目录下或者在pom.xml的build标签里加上resources配置把src/main/java下的xml文件一并打包。我推荐第一种规范且省心。3.2 MyBatis分页查询与条件搜索后台管理端的商品列表、订单列表都逃不掉分页。手写LIMIT虽然简单但每次都要计算pageNum和pageSize的偏移量代码非常僵硬。我在项目里引入了PageHelper分页插件只需要一行PageHelper.startPage(pageNum, pageSize)紧跟其后的查询会自动拼接分页。返回结果用PageInfo包装能直接拿到总记录数、总页数、当前页等元数据。条件搜索是这类系统的高频需求。商品列表需要支持按商品名称模糊查询、按分类筛选、按价格区间筛选、按上下架状态筛选。我习惯在Mapper里用动态SQL拼接核心写法如下select idselectProductList resultTypecom.beauty.mall.entity.Product select * from product where if testproductName ! null and productName ! and product_name like concat(%, #{productName}, %) /if if testcategoryId ! null and category_id #{categoryId} /if if testminPrice ! null and price gt; #{minPrice} /if if testmaxPrice ! null and price lt; #{maxPrice} /if if teststatus ! null and status #{status} /if /where order by create_time desc /select动态SQL的where标签会自动去除第一个条件前面的and这一点很多新手不知道写了半天发现SQL拼接多了个and报错。这个写法在SSM时代就一直是这样Spring Boot整合后完全通用。3.3 登录鉴权JWT还是Session在我的实践中前后端分离的项目已经没什么理由再用Session了。Session依赖Cookie会面临跨域携带Cookie复杂、移动端不支持、分布式部署无法共享等一系列问题。我选择使用JWT做登录鉴权方案非常成熟。用户登录成功后后端生成一个JWT令牌返回给前端前端存储在localStorage里每次请求在Authorization头中带上这个令牌。后端通过拦截器统一校验令牌并解析出用户ID存入请求上下文。JWT生成我用的是jjwt库生成和解析代码都不复杂。关键点是设置过期时间我设置为2小时。管理员令牌和用户令牌可以共用一套生成机制只需在payload里放入一个role字段区分即可。拦截器校验逻辑核心如下public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) { return true; } // 从请求头获取token String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); } // 校验token try { Claims claims Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); UserContext.setCurrentUser(claims.get(userId, Integer.class)); return true; } catch (Exception e) { response.setStatus(401); return false; } }这里的UserContext是一个ThreadLocal封装在请求线程内保存当前登录用户ID业务逻辑里随时可以取出当前用户操作自己的数据比如给购物车加商品时确定归属用户。注意请求结束后要在afterCompletion里调用UserContext.clear()否则线程池复用时会发生数据串号的问题。3.4 购物车与下单的事务控制购物车表很简单id、user_id、product_id、product_count、checked、create_time、update_time。加购物车接口的逻辑是无则新增、有则数量加一。修改数量时校验库存是否充足。下单是整个系统中最考验事务的环节。我的下单流程是获取购物车中选中的商品列表遍历校验库存计算总金额插入订单主表批量插入订单明细表扣减库存清空购物车中已选商品。这四步必须保证原子性任何一个环节失败都要回滚。Spring Boot里加Transactional注解即可实现声明式事务默认遇到RuntimeException就会回滚。有个坑要注意如果在Service方法里自己try catch吞掉了异常事务不会回滚这是新手最容易踩的。正确做法是不在事务方法内catch异常或者在catch里手动抛出一个RuntimeException让事务管理器感知到。下面是我实测有效的下单核心逻辑Transactional(rollbackFor Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 1. 查询选中的购物车记录 ListCartItem cartItems cartMapper.selectCheckedItems(dto.getUserId()); if (cartItems.isEmpty()) { throw new BusinessException(请先勾选要结算的商品); } // 2. 生成订单号 String orderNo generateOrderNo(); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(dto.getUserId()); // 3. 遍历明细计算金额校验库存 BigDecimal totalAmount BigDecimal.ZERO; for (CartItem item : cartItems) { Product product productMapper.selectById(item.getProductId()); if (product null || product.getStatus() 0) { throw new BusinessException(商品不存在或已下架); } if (product.getStock() item.getProductCount()) { throw new BusinessException(商品[ product.getProductName() ]库存不足); } // 累计金额 BigDecimal itemAmount product.getPrice().multiply(new BigDecimal(item.getProductCount())); totalAmount totalAmount.add(itemAmount); // 插入订单明细 OrderItem orderItem new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(product.getId()); orderItem.setProductName(product.getProductName()); orderItem.setProductImage(product.getMainImage()); orderItem.setPrice(product.getPrice()); orderItem.setCount(item.getProductCount()); orderItem.setTotalAmount(itemAmount); orderItemMapper.insert(orderItem); // 扣减库存 int rows productMapper.reduceStock(product.getId(), item.getProductCount()); if (rows 0) { throw new BusinessException(商品[ product.getProductName() ]库存扣减失败); } } order.setTotalAmount(totalAmount); order.setPayAmount(totalAmount); order.setOrderStatus(0); order.setPayStatus(0); orderMapper.insert(order); // 4. 删除已购物车项 cartMapper.deleteCheckedItems(dto.getUserId()); return convertToVO(order); }3.5 美容院场景的特有逻辑会员价与积分抵扣美容院和普通电商最大的不同在于它是典型的会员制消费场景。我在设计时加入了会员等级表和积分抵扣功能。会员等级表设计了grade_name、discount折扣率、min_points升级所需积分三列。比如普通会员折扣1.0白银会员折扣0.95黄金会员折扣0.88。下单时根据用户当前会员等级按照商品原价乘以折扣率计算价格。积分抵扣是我后补的需求。因为美容院客单价高积分抵扣是刺激复购的关键。下单时用户可以输入要抵扣的积分数量我设计的规则是100积分抵扣1元且抵扣金额不能超过订单金额的20%。代码里需要注意的是积分抵扣金额的计算精度要使用BigDecimal不能直接用浮点数取整不然会多扣或少扣用户的积分。订单完成后系统再根据实付金额回赠积分形成完整的积分闭环。4. Vue前端搭建与前后端联调实战管理端和用户端我都用的Vue 2 Element UI下面说说联调过程中的核心细节。4.1 项目创建与目录结构创建项目时我推荐直接使用Vue CLISpring Boot前端开发中Vue2的常用版本是vue/cli 4.x或5.x。命令很简单vue create beauty-admin然后选择手动配置勾选Router和Vuex。等待依赖安装完成后再把Element UI装进来npm i element-ui -S。在main.js里全局注册Element UI后就可以直接使用el-table、el-form这些组件了。管理端的目录结构我会按模块分目录比如views下面分product、order、user、dashboard、login几个子目录每个目录下的vue文件负责一个页面的全部逻辑。这样做的好处是后期维护或者写多页面系统时可以快速定位。4.2 axios请求封装token统一处理前后端联调的第一个问题就是跨域和请求头的处理。我在utils/request.js里封装了一个axios实例统一设置baseURL、超时时间和请求拦截器。请求拦截器里从localStorage取出token并加到Authorization头中响应拦截器里处理401跳转登录页和403无权限提示。核心代码import axios from axios const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) // 请求拦截器自动携带token service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }, error { return Promise.reject(error) }) // 响应拦截器统一处理错误状态 service.interceptors.response.use(response { const res response.data if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token) location.href /login } return Promise.reject(new Error(res.message || 请求失败)) } return res }, error { return Promise.reject(error) }) export default service这样一个封装做完后页面里的请求代码会非常简洁比如商品列表请求可能只需要几行调用。一次性把统一的错误提示和登录过期跳转做好是联调阶段省时间的关键。4.3 开发环境的跨域代理配置跨域问题有两种解决方式后端开启CORS或者前端通过vue.config.js配置代理。我的经验是开发环境用前端代理最方便生产环境用Nginx反代统一入口后端甚至不需要开启CORS可以减少一层暴露面。vue.config.js里这样配置module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: } } } } }这样前端请求/api/product/list会被代理转发到后端/product/list绕过了浏览器的跨域限制。我在开发时请求地址直接写/api/xxx切换环境只需要改环境变量非常方便。如果你后端坚持开CORS注意是在Spring Boot的配置类里加CorsFilter或者使用CrossOrigin注解但生产环境建议关掉。4.4 商品列表展示与购物车状态管理用户商城的商品列表页面我分成搜索栏、分类筛选栏和商品卡片网格三部分。搜索时支持关键字模糊匹配分类筛选通过下拉框选择分类ID。商品卡片展示图片、名称、价格和库存状态点击进入详情页。购物车页面在Vuex中维护一个购物车列表的state并引入一个totalPrice的getter用于实时计算总价。添加商品、修改数量、删除商品、勾选商品这四个操作都同步调用后端接口成功后重新拉取购物车列表保证数据不是本地假逻辑。这里有一个实际踩过的坑修改商品数量时如果连续点击加减按钮可能会因为接口响应顺序问题传回来旧数据导致数量显示错乱。我的解决方案是在每次操作前禁用按钮等接口返回后再恢复或者在请求里带上一个递增的时间戳参数取最新一次请求的响应。后台管理端的商品管理页面更偏重表单和表格新增商品弹窗里需要实现图片上传、富文本详情编辑、价格和库存录入列表页用el-table展示数据操作列放“上架/下架”、“编辑”、“删除”按钮。图片上传我用的Element UI的el-upload组件action地址指向后端的/upload接口上传成功后把返回的URL填充到表单隐藏域里。4.5 路由权限控制后台管理端必须做路由权限控制否则任何人都可以通过URL直接访问管理页面。我的实现方式是在路由配置里给每个管理页面meta增加roles: [admin]标记然后在vue-router的全局前置守卫里判断当前用户角色。router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.path /login) { next() } else { if (!token) { next(/login) } else if (to.meta.roles !to.meta.roles.includes(role)) { next(/404) } else { next() } } })这个实现算是轻量级方案不需要动态注册路由适合固定菜单的管理系统。更复杂的场景比如不同管理员看到不同菜单的按钮级权限就需要后端返回权限标识数组前端动态渲染菜单和按钮当前项目量级暂时用不到。5. 部署上线与常见性能瓶颈项目开发完成后部署上线也是一门学问。尤其是前后端分离的两个项目要合理利用Nginx做静态资源服务与反向代理。5.1 前端打包与Nginx配置前端部署的第一步是构建npm run build生成dist目录。将dist目录拷贝到服务器上比如/app/beauty-web目录下。然后配置Nginx指向这个目录并将/api开头的请求反向代理到后端Java服务。我的Nginx配置核心片段server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /app/beauty-web; index index.html; try_files $uri $uri/ /index.html; } # 后端API反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传的图片访问 location /upload/ { alias /app/beauty-upload/; } }try_files $uri $uri/ /index.html这一行最容易被忽略。如果不配置用户在前端路由刷新页面时Nginx会尝试寻找对应的真实文件路径找不到就返回404导致刷新后页面空白。加上try_files后所有未命中的路由都会回退到index.html由Vue Router接管继续渲染正确页面。5.2 后端打包与内存优化后端打包用Maven命令mvn clean package -DskipTests生成jar包后上传服务器用nohup java -jar beauty-mall.jar log.txt 21 启动即可。生产环境我建议在启动参数里加上-Xms512m -Xmx1024m限制JVM堆内存避免占用过多服务器资源。如果做前后端分离部署Nginx与后端服务的配合就是整条链路的咽喉先把这一层搞通后续水平扩展也只是加一台服务器的事。如果并发量确实上来了优先加Redis做缓存。商品列表、分类信息、首页轮播图都是热点数据可以缓存在Redis里有效降低数据库压力。我在这个项目中给首页数据加了简单缓存接口响应时间从80ms降到了10ms以内提升明显。5.3 数据库连接池配置Spring Boot 2.x默认使用HikariCP连接池这也是目前性能最好的Java连接池之一。默认配置在大多数场景下够用但高并发时需要注意max-pool-size的调整。我的建议值是最小空闲连接数10最大连接数50连接超时时间30000ms。这个配置可以在application.yml中覆盖spring: datasource: hikari: minimum-idle: 10 maximum-pool-size: 50 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000配置连接池的核心思路是连接数不是越大越好每个连接都会占用数据库资源过大的连接池反而会拖垮数据库。50个连接在这个项目量级已非常充裕。6. 常见问题与排查技巧实录前后端整合的项目一旦出现问题排查链路比较长。我把自己开发过程中遇到的几个高频问题整理出来方便你少走弯路。6.1 跨域请求CORS错误前端请求后端时报“Access-Control-Allow-Origin”错误或者浏览器控制台出现“has been blocked by CORS policy”。这个问题大概率是因为前端没有走代理直接访问了后端地址。我在前面的Nginx配置里其实已经规避了这个问题。如果开发环境使用的还是直接调用后端地址的办法那就务必在后端配置CORS。我推荐的Spring Boot CORS配置是写一个WebMvcConfigurer配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这个配置类加上后前端的GET、POST、PUT、DELETE等请求都能通过预检。但要注意allowCredentials和allowedOriginPatterns不能同时用*否则在部分Spring Boot版本里会报错。实操中我有一个更省事的原则能用Nginx代理就用代理别在主项目里开CORS省得生产环境暴露接口风险。6.2 MyBatis映射文件被漏打包启动项目后调用任何Mapper方法都报Invalid bound statement (not found)但是代码看起来又没问题。这个问题十有八九是mapper.xml文件没有打包进classes目录。检查target/classes/mapper目录下有没有对应的xml文件如果没有说明打包时被忽略了。解决方式有两个把xml文件放在src/main/resources/mapper目录下这是最推荐的。或者如果你的项目结构要求xml和Mapper接口放同一个包那就在pom.xml里加上如下配置把xml文件也纳入打包范围build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build6.3 JWT过期后前端还在停留用户登录后过了一个小时再操作后端返回401但前端没有跳转登录页。原因是axios响应拦截器里处理401的代码没生效或者前后端对token过期的判断不一致。我的排查步骤是先看响应拦截器里是否捕获了HTTP 401状态码然后看后端返回的响应体结构。如果你后端拦截器统一返回{code:401}但HTTP状态码是200那么前端就要用res.code来判断而不是error.response.status。保持前后端约定一致是联调顺畅的前提。我的习惯是遇到未登录或token过期后端统一返回HTTP 401前端一律用error.response.status 401判断简单不二义。6.4 图片上传能访问但404开发环境图片上传到本机磁盘通过后端接口可以访问打包上线后就404了。大概率是因为Spring Boot默认无法直接访问本地磁盘的静态资源目录。我在Nginx里用alias配置了/upload/路径把上传目录映射为静态访问。Nginx中alias和root是有区别的root会把location的路径拼接到root路径后面alias则直接替换路径。我配置的location /upload/ { alias /app/beauty-upload/; }请求/upload/123.jpg会映射到/app/beauty-upload/123.jpg。如果你用root配置请求路径会变成/app/beauty-upload/upload/123.jpg这应该是个经典的配置陷阱了。6.5 Vue打包后白屏或路径错误前端打包后直接双击index.html打开是白屏检查控制台发现js、css的路径是绝对路径/。这是因为Vue CLI默认publicPath是/打包供服务器根路径访问没问题但如果你放在子目录或直接本地打开路径就错了。解决方式是在vue.config.js里把publicPath改为相对路径module.exports { publicPath: ./ }还有另一个情况打包部署后页面正常但刷新后404这个前面Nginx配置里已经提到了就是try_files没配。这两个问题在部署环节非常高频。6.6 冲突解决Maven依赖版本引起的启动异常有时候引入PageHelper或其他依赖后项目启动直接报错Error creating bean with name sqlSessionFactory。原因一般是MyBatis相关依赖版本不兼容。这个项目我最终确定的依赖版本组合是MyBatis Spring Boot Starter 2.2.2PageHelper Spring Boot Starter 1.4.2jjwt 0.9.1。这三个版本搭配Spring Boot 2.7.x跑得很稳没有冲突。再次强调新手一定不要一上来就用最新版依赖高版本Spring Boot 3.x系列中jakarta的命名空间变化会让大量老教程里的代码直接编译失败。做项目求稳是第一要务。写在最后的小心得做这类系统我发现最花时间的反而不是业务代码而是各种环境问题和联调问题。很多新手习惯踩到一个问题就百度一个解决完继续往下写没有形成自己的排查套路。我自己常用的排查顺序是看浏览器控制台Network请求状态码看后端控制台打印的SQL和异常栈然后根据错误信息归类找原因。这套思维模式在你做完一个完整项目后会根深蒂固比任何知识点都值钱。如果你也在做这个Spring Boot SSM Vue的商城系统建议先根据自己学校或公司的要求把表结构和接口定义好再来改代码。磨刀不误砍柴工这个项目本身是一个综合能力放大器同时把后端、前端、数据库、部署整个链路走通一遍你对Java开发的认识会上一个台阶。最后再分享一个小技巧开发阶段在后端配置了mybatis的日志输出log-impl: org.apache.ibatis.logging.stdout.StdOutImpl后前端一旦出问题先看后端SQL有没有执行再看SQL执行结果是否符合预期就能快速定位是后端逻辑问题还是前端传参问题。这一条经验能帮你省下大量的联调时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价