ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MyBatis商城管理系统全栈开发实战:从架构设计到部署排错

SpringBoot+Vue+MyBatis商城管理系统全栈开发实战:从架构设计到部署排错 做这个“SpringBoot Vue 的米家商城 / abo 管理系统”项目前前后后折腾了小一个月。项目本身不算特别复杂但把用户端商城和后台管理端揉在一起又要保证代码能跑、能演示、能答辩确实有不少需要注意的细节。这篇文章我就以自己的实操记录为主线把整个项目从选型、设计到具体实现、部署排错的过程完整梳理一遍用的技术栈就是标题里那套SpringBoot Vue MyBatis MySQL。先说清楚这个项目是干什么的。它本质上是一个“小米商城”风格的电商系统包含两个端面向普通用户的商城页面浏览商品、加购物车、下单以及面向运营/管理员的后台系统商品管理、订单管理、用户管理、数据统计。很多同学在简历里写“商城项目”但往往只做了前台展示后台管理是空壳答辩时一问就露馅。这个项目的好处在于前后台完整且“abo”这种项目命名方式在老课程设计、毕业设计里边很常见基本上等同于“一个完整可运行的 Java Web 全栈项目”的代名词。不管你是准备拿它做毕业设计、课程设计还是单纯想练手 SpringBoot Vue 的全栈开发这篇文章里提到的设计思路、代码坑点、环境配置和部署方式都可以直接参考。下面从项目整体架构开始聊。1. 项目整体设计与技术选型思路1.1 这个商城系统到底需要做什么很多新手拿到需求就急着建表这是最要命的。先梳理清楚业务边界既然是“米家商城” “abo 管理系统”那必然包含两类角色、两种操作场景。用户端商城页面需要做的事很明确用户注册、登录一般还要有 token 校验来维持会话状态首页商品展示按分类、按关键字搜索商品商品详情页展示商品图片、价格、库存、规格购物车操作加入、修改数量、删除、批量选中结算订单流程提交订单、生成订单号、模拟支付、查看订单列表和详情个人中心修改个人信息、查看收货地址。后台管理系统相对偏“管理管理员登录不同管理员可能分配不同角色权限这是“abo”命名里常带的点即基于角色控制的管理系统商品管理添加、修改、上下架、库存调整、商品图片上传订单管理查看订单、发货、退款处理、订单状态流转用户管理查看用户列表、禁用启用账号数据看板展示销售统计、用户增长等简单图表。把需求拆开写清楚后才能确定数据库表和接口要设计到什么粒度。我在做的时候刚好还看到热词里有一堆关于“vue 播放 m3u8”“vue 播放欢乐谷 m.3u8”的搜索这说明很多同学想着在商城商品详情里加视频展示。这个点完全可行后面我单独在 Vue 前端那节说播放的实现方式。1.2 为什么选了 SpringBoot Vue MyBatis MySQL这套组合在今天几乎成了 Java 全栈入门项目的“标准配置”不是没有道理。后端用 SpringBoot好处是约定优于配置一个 main 方法就能启动整个 Web 服务。相比传统的 SSMSpring SpringMVC MyBatisSpringBoot 把大量 XML 配置简化成了自动配置和注解省掉的不只是时间还有新手最容易搞错的配置项。对商城这种中等规模项目来说SpringBoot 的性能、生态和学习资料都足够。MyBatis 相比 JPA 更受国内企业青睐尤其是 SQL 可控这一点。商城系统里复杂查询很多——多表联查商品、订单状态统计、分组聚合这些用 MyBatis 写 XML SQL 非常直观出了问题也可以直接把 SQL 拿到数据库工具里跑一下排查。JPA 在这类场景下反而容易让人一头雾水。前端选 Vue 而不用 React原因很简单Vue 中文文档齐全、上手曲线平缓、模板语法容易理解而且生态里的 Element UI / Element Plus 有现成的后台管理组件库堆积木式开发效率极高。MySQL 就不多解释了开源、轻量、足够稳和 SpringBoot 整合资料遍地都是。这套组合还有一个实际好处招聘市场上 Java 后端岗位的 JD 里SpringBoot MySQL 几乎是必写项Vue 作为加分项能让你在简历筛选阶段多留一会儿。1.3 系统整体结构前端站点 管理后台 后端服务我把项目拆成了三个目录方便分端开发。mijia-mall ├── frontend-mall # 用户端 Vue 项目 ├── frontend-admin # 管理后台 Vue 项目 └── backend # SpringBoot 后端项目为什么用户端和管理后台的前端要分开成两个项目而不是合并成一个因为这两部分的页面风格差异很大用户端讲究展示效果后台讲究表格表单效率。如果硬塞在同一个 Vue 项目里路由配置会变得臃肿权限控制也更麻烦。两个前端项目共享同一个后端接口是更清晰的方案。后端项目按常规分层来com.example.mijia ├── controller # 接口层 ├── service # 业务逻辑层 ├── dao/mapper # MyBatis 数据访问层 ├── entity/domain # 实体类 ├── config # 配置类跨域、拦截器、异常处理 ├── common # 公共返回对象、工具类、常量Controller 层只做参数接收和结果包装Service 层写业务逻辑Mapper 层只负责 SQL 操作这种分层能让你在后期排查问题时快速定位。压测或者答辩时别人问“订单库存扣减这段逻辑在哪”你直接翻 Service 就能找到。2. 核心模块拆解与数据库表设计2.1 商品、订单、购物车三大模块的需求边界商城类项目核心永远是三个字商品、订单、库存。做得好的系统这三者之间的关系是环环相扣的。商品模块是最基础的模块。商品要有分类分类可以有多级但在课程设计级别里建议只做一级分类避免给自己挖坑。商品本身包含名称、图片、价格、原价、库存、销量、上下架状态、详情描述、创建时间。订单模块是业务最重的模块。一张订单至少需要拆成订单主表和订单明细表。主表存订单号、用户 ID、总金额、支付状态、订单状态、收货地址快照、创建时间明细表存每个商品的名称快照、单价快照、数量、小计金额。为什么要存“快照”而不是关联商品表去查因为商品价格会变用户下单后查看自己的历史订单看到的价格应该是下单那一刻的价格。这是电商设计的常识但很多新手项目里没有这个意识被老师一问就答不上来。购物车模块相对简单一张表记录用户 ID、商品 ID、数量、选中状态即可。但要注意购物车表里如果只存商品 ID展示购物车列表时需要联查商品表拿到最新价格和图片、库存信息接口返回的 DTO 需要做一次组装这个在 MyBatis 里用关联查询处理很顺手。2.2 后台管理端abo的表设计注意事项很多课程设计里“abo 管理系统”会带上权限管理功能所以用户表要给一个 role 字段比如 0 表示普通用户1 表示管理员。在后台管理端登录时通过 role 来判断是否允许访问管理接口。这里有个关键设计管理端的接口不能只靠前端路由隐藏后端必须做权限校验。你不能让一个普通用户知道了接口地址就通过后台接口把商品价格改了。最简单的做法是自定义一个拦截器拦截所有/admin/**的请求校验请求头里是否携带 token 并且 token 对应的用户 role 是否为管理员。这块代码量不大但能体现你的安全意识答辩时是加分项。数据库的整体表设计我一般是按这个顺序建的user用户表category商品分类表product商品表cart_item购物车表order订单主表注意 order 是 MySQL 的保留字建表时最好改成orders或加反引号我直接用了orders表名order_item订单明细表address收货地址表2.3 MyBatis 映射文件里的联表查询怎么写商城项目的查询场景十有八九要连表。比如前台商品列表按分类查询要查出分类名称购物车列表要查出商品的图片、价格、库存。MyBatis 里我习惯用 XML 文件来管理 SQL而不是注解。因为 XML 里可以写分页 SQL、动态 SQL、结果映射比注解更灵活而且后期 SQL 优化时不用重新编译 Java 代码。一个代表性的商品列表查询 XML 写法是这样的select idselectProductWithCategory resultTypemap SELECT p.id, p.name, p.price, p.main_image, p.stock, p.sales, p.status, c.name AS category_name FROM product p LEFT JOIN category c ON p.category_id c.id where if testcategoryId ! null AND p.category_id #{categoryId} /if if testkeyword ! null and keyword ! AND p.name LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null AND p.status #{status} /if /where ORDER BY p.create_time DESC /select注意LIKE CONCAT(%, #{keyword}, %)的写法不要直接写LIKE %${keyword}%前者会用预编译参数防止 SQL 注入后者是字符串拼接存在注入风险。这个细节在面试里经常会被问到做项目时也要养成习惯。分页我用的 PageHelper这一块的细节后面单独说。3. 后端开发实操SpringBoot MyBatis 关键实现复盘3.1 项目初始化与依赖配置项目初始化两个办法一是用 IDEA 的 Spring Initializr 直接生成二是去 Spring 官网 start.spring.io 下载压缩包。目前最新稳定版 SpringBoot 2.7.x 对课程设计项目已经完全够用如果不想折腾 JDK 版本兼容用 2.7.x 搭配 JDK 8 是最稳妥的方案。最新的 SpringBoot 3.x 要求 JDK 17对很多本地只有一个 JDK8 环境的同学来说反而不方便。pom.xml里的关键依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependency这里有两个容易踩的坑。第一个坑MyBatis-spring-boot-starter 的版本不是随便选的2.3.x 对应 SpringBoot 2.x如果用 SpringBoot 3.x 必须用 mybatis-spring-boot-starter 3.x不然启动直接报错。第二个坑MySQL 8.0 以上的驱动类名是com.mysql.cj.jdbc.Driver8.0 以下才是com.mysql.jdbc.Driver自己写配置文件时容易搞混。3.2 配置文件里的隐藏细节在application.yml里我最开始吃过一个亏数据库连接 URL 没加时区参数运行时报错Cannot create PoolableConnectionFactory。最终的连接配置是spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mijia_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: yourpassword jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.mijia.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case这个配置强烈建议打开。数据库字段create_time可以自动映射到实体类的createTime省去一堆resultMap映射代码。很多新手不用这个配置然后发现查出来的对象 createTime 是 null折腾半天。log-impl设置为StdOutImpl可以在控制台打印执行的 SQL 和参数极大方便调试。生产环境再关掉本地开发坚决要开。当你发现查询结果不符合预期第一件事就是看控制台打出来的 SQL 是不是你想象中那条。3.3 MyBatis 一级缓存和二级缓存到底要不要开热词里许多人搜“mybatis 缓存”。这个项目里我的选择是默认不开二级缓存保持 MyBatis 的一级缓存。原因很简单商城系统里商品库存、价格的实时性要求很高开二级缓存很容易出现数据不一致。解释一下区别面试常问。MyBatis 的一级缓存是 SqlSession 级别的同一个 SqlSession 中执行两条相同的 SQL第二次直接走缓存不查数据库。一级缓存默认开启作用范围极小基本不会出问题。MyBatis 的二级缓存是 namespace 级别的同一个 Mapper 的查询结果会被缓存到 JVM 内存里供多个 SqlSession 共享看似能提升性能但比如你更新了商品库存如果缓存没有及时清空用户看到的库存数可能是旧的这在商城项目里不可接受。真正需要缓存的数据更稳妥的做法是用 Redis 做缓存并且主动设置过期时间。课程设计项目中不建议引入 Redis因为会让复杂度上升一个级别。兜底的方案是如果你用了二级缓存所有增删改操作对应的 Mapper 里必须配置刷新缓存属性。这个坑我踩过商品上架后后台改了价格前台依然显示旧价格排查了很久才发现是缓存没刷掉。3.4 分页插件 PageHelper 的实际用法热词里很多人搜“mybatis 的分页插件的用法”。我这里直接给出在 SpringBoot 项目里的标准操作。导入依赖之后在你的 Service 层调用public PageResultProductVO getProductPage(Integer pageNum, Integer pageSize, Integer categoryId, String keyword) { PageHelper.startPage(pageNum, pageSize); ListProductVO list productMapper.selectProductWithCategory(categoryId, keyword); PageInfoProductVO pageInfo new PageInfo(list); return new PageResult(pageInfo.getTotal(), pageInfo.getList()); }PageHelper.startPage的作用是从这行代码开始拦截下一条 SQL自动拼接LIMIT语句。有一个很重要的注意事项startPage后面必须紧跟着你要分页的那条 Mapper 查询如果中间还调用了别的查询分页就会作用到错误的 SQL 上。比如你先查了商品分类列表再查商品主列表startPage 放在查分类之前就会完蛋。另一个容易忽略的点是分页插件会把 COUNT 查询自动跑一遍如果主查询 SQL 本身性能不好COUNT 查询也会慢。所以商品列表页的 SQL 尽量别写得过于复杂索引建到位保持 COUNT 简单。3.5 事务、全局异常与上传文件的 XSS 处理先谈事务。下单这个操作典型的场景是生成订单记录、扣减库存、清空购物车。这三步必须在一个事务里否则中间某一环执行失败会造成“订单生成了但库存扣了”这种脏数据。在 SpringBoot 里加一个Transactional注解即可。但注意事务默认只在运行时异常RuntimeException时回滚如果抛出的是受检异常Exception不会回滚。所以如果你在 Service 里捕获了所有异常并返回一个错误结果对象事务是不会触发的。我的习惯是Transactional(rollbackFor Exception.class) public Order createOrder(CreateOrderRequest request) { // 1. 校验库存 // 2. 生成订单主表记录 // 3. 生成订单明细 // 4. 扣减库存 // 5. 清空购物车 }热词里还有个“springboot项目全局过滤器处理上传pdf文件时xss攻击”。这是安全相关的点。商城后台的商品介绍如果支持富文本用户可能上传带有恶意脚本的内容。虽然这个项目的使用场景是课程设计但你也要知道全局过滤器可以拦截所有请求清洗请求参数中的script标签等特殊字符。实现方式是实现 Filter 接口对HttpServletRequestWrapper做参数重写。这块我不建议新手强行深入知道这个概念并在答辩时能讲出来就赢了。全局异常处理也是一个临场表现加分项。我用RestControllerAdviceExceptionHandler统一捕获业务异常、参数校验异常、未知异常返回统一格式的 JSON。这样即使后端报错前端拿到的也是可读的错误信息而不是一堆异常堆栈。4. 前端开发实操Vue 两端项目的搭建与核心页面实现4.1 Vue 环境配置与项目创建热词里很多“vue安装及环境配置”“vue安装依赖”的搜索说明新手在环境中卡住的不少。我先说我个人的标准流程。Node.js 版本是一个大坑。Vue 3 Vite 需要 Node 16 以上Vue 2 Vue CLI 4 适合 Node 14。如果你电脑上之前装过旧版 Node建议先清干净再装 LTS 版本。确认命令node -v npm -v然后创建项目。用户端我用 Vue 3 Vitenpm create vitelatest frontend-mall -- --template vue cd frontend-mall npm install npm install vue-router4 pinia axios element-plus npm run dev管理端相对保守我用了 Vue 2 Element UI。理由很奇怪但真实管理端的 Table 组件 Element UI 用得太熟练了开发速度快。如果你从头开始我建议两个端统一用 Vue 3 Element Plus不用为了省事而分裂版本。热词里出现的“electron 主渲染进程 ipc 通信 和vue有关系吗”这里捎带说一句Electron 的 IPC 通信属于桌面端开发范畴和 Vue 本身没有直接关系Vue 只负责页面渲染层。你搜索这个话题多半是想把商城做成桌面应用那是在 Vue 项目外面再套一个 Electron 壳前后端的代码基本不用改。这个思路可以做成品提升但不是必要的。4.2 路由、状态管理与拦截器设计Vue 项目的核心骨架是路由和状态。用户端我定义了这几条路由/首页/product/:id商品详情/cart购物车/order/confirm订单确认/order/list订单列表/login登录/register注册管理端的路由走后台布局嵌套路由/admin/login管理员登录/admin/dashboard数据面板/admin/product/list商品列表/admin/product/edit添加/编辑商品/admin/order/list订单列表/admin/user/list用户列表状态管理方面用户端和管理端都要存登录后的用户信息、token。我用 PiniaVue 3和 VuexVue 2分别实现核心是同一套逻辑登录成功后把 token 存 localStorage页面刷新后从 localStorage 恢复状态。最重要的前端拦截器是 Axios 请求拦截和响应拦截。请求拦截器给每个请求头加上Authorization: Bearer token这样后端拦截器才能识别用户身份。响应拦截器统一处理业务状态码比如 401 时直接跳转登录页。http.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) http.interceptors.response.use( response { const res response.data if (res.code ! 200) { if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message || Error)) } return res }, error { if (error.response error.response.status 401) { router.push(/login) } return Promise.reject(error) } )这种集中式拦截处理好处是后端返回任何错误前端都能统一弹提示而不是在每一处接口调用里写一遍重复的 if 判断。4.3 商品列表、购物车、订单页的核心交互逻辑商品列表页和购物车页面是商城前端的门面。商品列表我用卡片布局点击进入详情。商品数据通过 Axios 请求后端/api/product/page获取分页加载点击分类切换时重新请求。购物车页面的关键逻辑是全选、单选的计算方式。我用一个 computed 来维护“已选商品”的派生状态计算总价时遍历购物车列表筛选出勾选项累加price * quantity。订单确认页要处理流程切换购物车勾选商品 - 点击结算 - 跳转订单确认页 - 填写/选择地址 - 提交订单 - 模拟支付成功 - 跳转订单列表。这个流程涉及多个页面之间的状态传递最简单可靠的方式是路由 query 传参传购物车项 ID 的集合订单确认页再根据这些 ID 从后端查最新商品信息。不要用全局 store 保存临时数据因为刷新页面会导致 store 被清空体验很差。商品详情页如果加视频播放m3u8 的播放方案我上面已经预告了。m3u8 不是一种视频文件格式而是 HLS 协议的索引文件。Vue 3 里推荐用 hls.jsnpm install hls.js然后封装一个简单的播放组件template video refvideoRef controls autoplay muted/video /template script setup import Hls from hls.js import { onMounted, ref } from vue const videoRef ref(null) const props defineProps({ src: String }) onMounted(() { if (Hls.isSupported() props.src) { const hls new Hls() hls.loadSource(props.src) hls.attachMedia(videoRef.value) } }) /script这个组件直接用在商品详情页传入商品带货视频的 m3u8 地址就能播放。如果本地没有视频资源可以用 ffmpeg 把 mp4 转成 m3u8 切片后面部署篇章我也提一下。4.4 前后端联调跨域和本地代理问题开发环境最烦人的就是跨域。后端开发的地址是http://localhost:8080前端是http://localhost:5173端口不同就会触发浏览器的同源策略。两个解决方案。后端方案写一个全局 CORS 配置类Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }前端方案在 Vite 配置里加代理前端请求/api开头时自动转发到http://localhost:8080// vite.config.js export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })两种方案选一种即可同时我推荐你把后端接口统一都加/api前缀这样代理就只拦截/api开头的路径避免误伤静态资源请求。联调阶段字段名不一致、时间格式不对、返回结构不统一是最常见的沟通矛盾点所以接口返回统一用ResultT封装public class ResultT { private Integer code; private String message; private T data; }所有 Controller 都返回Result.success(data)或Result.error(msg)前端拦截器看到 code 不为 200 就统一处理错误思路清晰联调效率高出一大截。5. MySQL 环境配置与数据库连接问题排查5.1 MySQL 安装与字符集设置热词里“mysql安装教程”“linux安装mysql”出现频率极高。Windows 上安装 MySQL 8.0 比较无脑官网下了 zip 解压后用管理员命令行执行mysqld --initialize-insecure mysqld install net start mysql mysql -u root注意执行mysqld --initialize-insecure之后root 默认没有密码首次登录后立刻设置ALTER USER rootlocalhost IDENTIFIED BY 你的密码;Linux 上安装无非是 apt 或 yum 安装 mysql-server但注意默认配置文件在/etc/mysql/mysql.conf.d/mysqld.cnf需要确认bind-address和字符集配置。实际开发和部署时数据库字符集统一用utf8mb4排序规则用utf8mb4_unicode_ci。utf8mb4和utf8的区别是前者能存 emoji 和 4 字节字符。米家商城里商品描述如果你让用户填写一些带 emoji 的文字utf8 会直接报错utf8mb4 就好好的。建库语句示范CREATE DATABASE mijia_mall DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;5.2 数据库连接池配置SpringBoot 的默认连接池是 HikariCP不需要额外加配置就能工作。但生产环境要注意几个参数。我习惯在 application.yml 里显式配置spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000很多人在高并发下会遇到Connection is not available, request timed out这类报错通常就是连接池连接被耗尽。商城场景下并发量虽然不高但连接泄漏的问题要提前规避。最典型的连接泄漏原因是查询结果没有正常关闭或者事务包裹里包含了一些耗时的外部调用。排查连接泄漏的办法是在配置里打开 HikariCP 的泄漏检测spring: datasource: hikari: leak-detection-threshold: 60000这样一旦有连接持有超过 60 秒未归还日志里会输出告警。5.3 常见的 MySQL 连接报错与解决方法把我在这个项目里真实遇到的几个错误列成表方便你对照排查报错信息常见原因解决办法Access denied for user rootlocalhost密码错误或 user 表权限问题检查密码确认是用 root 还是新建账号SSL connection error: ...MySQL 8.0 默认开启 SSL连接串未处理URL 加useSSLfalsePublic Key Retrieval is not allowedMySQL 8.0 配合非 SSL 连接的认证问题URL 加allowPublicKeyRetrievaltruecom.mysql.cj.exceptions.InvalidConnectionAttributeException连接串没有指定 serverTimezoneURL 加serverTimezoneAsia/ShanghaiERROR 2002 (HY000): Cant connect to local MySQL server through socket /tmp/mysql.sockLinux 上 mysql 服务未启动或 Socket 路径不对systemctl start mysql或确认 socket 路径Unknown database mijia_mall建库失败或库名写错确保建库成功并检查库名大小写那个ERROR 2002的报错尤其常见尤其是在 Linux 服务器上部署的同学。/tmp/mysql.sock这个文件是 MySQL 客户端和服务器进程通信的通道如果服务没有启动Socket 文件就不存在。按顺序核查先看服务状态再看是否使用了错误的连接方式最后确认权限。还有一个诡异的“数据库突然连不上”问题原因是连接池里的连接到了max-lifetime之后被服务端断开了而客户端不知道还在用旧连接。用 Hikari 时把max-lifetime设置得比 MySQL 默认的wait_timeout短一些就可以规避这个坑。6. 项目部署、源码解析与常见面试考点6.1 前后端打包部署的全流程开发完了之后总得让项目跑在服务器上才能演示。打包部署这块我吃过不少亏把完整流程记录一下。前端打完包是纯静态文件npm run build以 Vite 项目为例构建产物在dist/目录。把这个目录里的文件拷贝到 Nginx 的 html 目录下再配置 Nginx 将/api请求反向代理给后端 SpringBoot 服务server { listen 80; server_name your-domain; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files $uri $uri/ /index.html;这一行。Vue 路由默认是 history 模式如果少了这行你直接刷新/product/2这个页面会报 404因为 Nginx 找不到对应的真实文件。加上这行后所有刷新请求都会重定向到 index.html再由 Vue 路由解析地址。后端打包mvn clean package -DskipTests生成target/*.jar然后通过java -jar xxx.jar启动。如果服务器内存不大可以加 JVM 参数限制内存java -jar mijia-mall.jar --spring.profiles.activeprod部署时记得把application.yml里的数据库连接、上传文件保存路径等配置改成服务器环境对应的值。如果你图省事也可以用 Docker 把前后端分别容器化但这就会使项目复杂度上来了。课程设计要求不高的情况下Nginx Java 进程这种传统方案已经足够稳定。6.2 SpringBoot jar 反编译拿到别人的源码怎么分析热搜词里有“怎么将springboot jar反编译成项目”这个场景很实际很多同学拿到的是别人打包好的 jar想把它还原成能看懂、能修改的源码项目。推荐的工具是 IDEA 自带的 Java DecompilerFernflower或者 CFR。IDEA 打开一个 jar 文件时通常能看到反编译出来的 class 源码但这种方法适合单文件查看。想整体还原我一般用cfrjava -jar cfr.jar mijia-mall.jar --outputdir ./src反编译出来的目录结构大体还原了包结构Controller、Service、Mapper 这些包都还在。但要注意XML 文件如果被打进了 BOOT-INF/classes也会一并解出来实体类里的注释、一些常量值比如数据库密码都会被还原成明文。这提醒了两件事第一你自己打 jar 发布前确保数据库密码等敏感信息不要硬编码在配置里至少要用环境变量替换第二分析别人项目时先看application.yml或者bootstrap.yml再看 Controller基本就能搞清楚项目有哪些接口然后顺藤摸瓜看 Service 实现。从 jar 逆向成“可运行的 IDEA 项目”还需要自己补 pom.xml 或 build.gradle因为反编译出来的只是 class 和资源构建文件不会自动生成。你需要参考项目里用的依赖重新搭一个 Maven 工程然后把反编译代码拷进去。这个过程很容易积攒依赖冲突的问题多数情况是重新手写一遍比逆向改造更省力。6.3 SpringBoot、MyBatis 面试高频问题这个项目做完之后如果你要去面试 Java 开发岗有几个知识点是大概率会被问到的。热词里已经有“springboot面试题”“mybatis面试题”了我直接把和这个项目相关的常见题目和回答思路放这里一、SpringBoot 自动配置原理怎么理解回答思路SpringBootApplication包含EnableAutoConfigurationSpringBoot 会去读取spring.factories中配置的自动配置类按条件注解ConditionalOnClass、ConditionalOnMissingBean等判断是否生效。比如DataSourceAutoConfiguration会在 classpath 里有DataSource相关类时自动创建连接池配置。面试官一般还想听到你举一个实际例子这时你就说“项目里 SpringBoot 自动帮我配置了默认数据源 HikariCP”这就把事情讲活了。二、MyBatis 里 #{} 和 ${} 的区别这个属于送分题。#{}走预编译能防止 SQL 注入${}是字符串拼接可能被注入。但${}在分页、排序字段动态替换时有需求。在商城项目里排序字段如果可配置建议用白名单校验后再拼入 SQL绝对不要直接由用户输入。三、SpringBoot 的 Controller、Service、Mapper 请求流转过程这是概念题也是实测题。请求从浏览器发出Nginx/前端代理转发到 SpringBoot经过 DispatcherServlet 分发到对应 ControllerController 调 ServiceService 调 MapperMapper 执行 SQL 返回结果反向逐层封装最终返回 JSON 给前端。这个流程能讲清楚基本就证明你是真的写过项目的人。四、如何解决后端查询的性能瓶颈这个在商城项目里可以结合商品列表分页讲。先看 SQL 执行计划有没有走索引商品表的 category_id、status 字段要有索引再看有没有 N1 查询问题比如循环查数据库的问题实在难搞的 SQL 还可以用缓存解决但在本项目里索引和分页优化已经足够。五、Vue 项目的权限控制如何实现前端路由守卫 后端拦截器双重控制。前端 router.beforeEach 判断是否存在 token 和角色信息没有则跳登录页后端给需要权限的接口配置拦截器校验 token 和角色。6.4 从源码到apkvscodevue 做手机软件是怎么回事热搜词里还有一条“vscode vue 怎么制作手机软件”说实话看到这个问题我估计提问者是把“Vue 写的网页”和“手机 App”搞混了。Vue 本身做的是 Web 页面要变成手机 App 一般有三条路第一条路把 Vue 项目打包成移动端 H5 页面部署到服务器上用户通过浏览器访问这是最简单的移动端适配方案。第二条路用 uni-app 重写页面它是 Vue 语法可以编译到 iOS/Android 原生 App。第三条路用 Capacitor 或 Cordova 给 Vue 项目套壳生成 APK。相比 Electron 桌面端移动端套壳本质是一个 WebView 容器加载你的 H5 页面。如果这个米家商城项目想“变成手机软件”最省事的方式就是第三条路用 Capacitornpm install capacitor/core npm install capacitor/cli npx cap init npx cap add android npm run build npx cap copy android然后用 Android Studio 打开生成的android目录直接就能打包 APK。后端接口地址要写你的公网地址或者用内网穿透在局域网联调。但对于课程设计来说我不建议在“变成 App”上花太多精力容易陷进原生构建工具链的大坑。最后分享一点实操体会这个项目前前后后改了几版我个人最大的感受是商城系统看起来千篇一律做起来细节才是最耗时的。比如“下单扣库存”时要考虑商品下架/库存不足的处理“购物车结算”要考虑商品价格变动“订单列表”要考虑状态筛选和分页每一块都是真实电商业务里的核心问题哪怕是一个课程级别的系统也值得用严谨的态度去实现。另外建议你在开发时给自己加一个小目标不要只做到“能跑”要能做到“能给别人讲懂”。把每一张表的字段设计理由、每个接口的事务边界、每个前端页面交互的数据流向都在心里过一遍这类项目才真正变成了你的项目。如果你基于这篇文章搭好了环境、跑通了页面后面再遇到“ES 检索”“Redis 缓存”“支付回调”这些进阶概念时你至少已经有了稳固的落地场景去承载它们。
返回列表