资讯动态

SpringBoot+Vue茶叶商城系统从数据库设计到部署完整实战解析

发布时间:2026/10/2 10:56:21 来源:尧图企业网站定制
作为一个带过不少毕业设计、也帮人救过无数次烂项目的从业者我对“049茶叶商城系统-springbootvue”这种带编号的标题特别熟悉。它频繁出现在各类资源平台和课程设计清单里足以说明一个问题SpringBoot加Vue的前后端分离商城系统已经是JavaWeb方向最成熟、最稳定的练手范式之一。不过项目拿到手只是第一步能不能真正讲清楚、改明白、跑得顺才是毕业答辩和面试时见真章的地方。这篇文章我不打算给你堆一堆泛泛而谈的架构图而是直接从实际开发的角度把这个茶叶商城从需求拆解、数据库设计、后端接口实现、前端页面联动到最后的联调部署和踩坑记录完完整整过一遍。我会把每一步为什么要这么做的逻辑讲透参数怎么算、表怎么建、代码怎么落都会给出可直接抄作业的参考。无论你是刚拿到项目编号、正在复现项目的学生还是想参考完整电商闭环的初级开发者这篇文章应该都能让你少走不少弯路。1. 项目整体拆解茶叶商城到底要做什么1.1 功能需求与业务模块划分商城系统听起来千篇一律但一旦把业务场景定为“茶叶”整个项目的气质就不一样了。茶叶这种商品有几个特点和普通图书、数码产品完全不同第一它有非常强的分类维度比如按茶类分有绿茶、红茶、乌龙茶、白茶、黑茶、普洱茶按产地又有西湖龙井、安溪铁观音、武夷岩茶这些地域属性第二商品详情需要展示的不仅是价格和库存还有产地、采摘年份、等级、净含量、工艺特色这些字段第三用户决策周期长很多人会反复浏览对比购物车的使用频率比买手机高得多。所以这个系统的功能模块可以从两条线来梳理。用户端前台注册登录、商品分类浏览、商品搜索、商品详情查看、加入购物车、结算下单、订单管理、收货地址管理。管理端后台数据看板、商品管理上架下架、库存修改、图片上传、订单处理发货、查看详情、用户管理、分类管理。从我个人的角度看如果一个商城项目连“商品-购物车-订单-支付状态流转”这条主链路都没跑通那不管前端页面做得多漂亮都是空中楼阁。很多网上下载的项目就是只做了增删改查购物车用的是纯前端localStorage下单接口只是往订单表里插一条记录这种项目拿去答辩是很容易被问住的。1.2 技术选型为什么是SpringBootVue先说后端。SpringBoot现在基本已经成了JavaWeb项目的默认起点它解决了传统SSH或者SpringMVC时代极其繁琐的XML配置问题。SpringBoot的核心是自动装配它通过spring.factories和EnableAutoConfiguration机制把SpringMVC、内嵌Tomcat、Jackson、数据源、MyBatis等一系列组件在你引入依赖的时候就帮你配置好了。你写一个RestController就能直接提供接口不需要像以前那样手写web.xml、spring-mvc.xml。对于商城系统这种典型CRUD加业务逻辑的项目SpringBoot的上手速度和可维护性都是最优解。再说前端。Vue这个框架在国内的流行程度不用多讲它的核心优势是组件化开发和响应式数据绑定。商城系统的页面无非就是商品列表、商品详情、购物车、订单确认这几类每个页面拆成若干个组件比如商品卡片组件、数量选择器组件、价格展示组件代码复用率会非常高。加上Element Plus或者Vant这套成熟组件库后台管理的表格、表单、弹窗、分页基本就是拿来即用能省掉大量写样式的时间。为什么强调“前后端分离”因为这种模式对毕业设计和中小型项目有特别实际的好处前端开发和后端开发可以分别调试前端跑在8080端口后端跑在8080端口通过代理转发请求互不干扰。打包部署时前端产出静态文件可以由Nginx直接托管后端打成一个独立的jar包运行。这种结构也符合企业里真实的开发流程——前端组和后端组分开协作。如果一开始就选JSP加SpringBoot的模板渲染模式那等于把自己锁死在一个不太有前景的技术路线上改起来也费劲。2. 数据库设计商城系统的核心地基2.1 核心表结构与字段设计我见过太多项目代码写得热热闹闹一打开数据库表结构就露馅了。商城系统的表设计其实有相对固定的范式关键是每个表该有哪些字段、字段类型怎么选、状态字段用什么整型来定义这些需要一开始就定清楚。下面是这个茶叶商城项目里最核心的几张表我直接给出可以落地的设计参考。用户表t_user字段包括id主键自增、username用户名唯一、password密码BCrypt加密后的字符串、nickname昵称、phone手机号、avatar头像URL、role角色0表示普通用户1表示管理员、status状态0禁用1正常、create_time创建时间。这里有个容易被忽视的点密码字段长度至少要60位以上因为我用的BCrypt加密生成的哈希字符串长度是60当初有人图省事设置成varchar(32)存都存不进去。分类表t_category字段id、name分类名、parent_id父分类ID顶级分类为0、sort_order排序权重。茶叶的分类是层级结构比如“绿茶”下面还可以分“龙井”、“碧螺春”用parent_id做自关联是最简单实用的方案不需要引入复杂的树结构。商品表t_tea这是整个系统的核心表我重点展开一下。字段包含id、category_id所属分类、name商品名、subtitle副标题简短的卖点描述、main_image主图URL、price价格decimal(10,2)、original_price原价用于划线展示、stock库存int、sales销量int、origin产地、grade等级、net_weight净含量、create_time、update_time、status商品状态0下架1上架。关于价格字段最重要的一个教训是不要用float或者double一定要用decimal。浮点数在Java和MySQL里面做精度计算会有误差商城这种涉及钱的场景一个分币差都不能有decimal(10,2)是最基本的自我要求。购物车表t_cart字段id、user_id、tea_id、quantity数量、checked是否勾选0否1是、create_time、update_time。很多教程里会把购物车设计成不需要checked字段但我强烈建议加上。因为订单结算页需要用户勾选部分商品下单如果你不在购物车里存勾选状态就要靠前端来记忆刷新页面状态就丢了体验很差。订单主表t_order和订单明细表t_order_item是订单体系的两个核心。主表用来存订单的整体信息字段id、order_no订单编号全局唯一、user_id、total_price订单总金额、status订单状态用整型表示状态机、receiver_name、receiver_phone、receiver_address这三个字段是把收货地址做了快照防止用户修改地址后影响历史订单、remark用户备注、create_time、pay_time、delivery_time、finish_time。明细表存的是订单里每一个商品项的快照字段id、order_id、tea_id、tea_name、tea_image、price下单时的单价快照、quantity。这里我特别强调一下“快照”的概念。你可能觉得明细表里已经有tea_id了直接关联商品表查询不就行了为什么还要冗余存一份tea_name和tea_image原因是商品信息是可能变的管理员改了商品名称、换了图片甚至下架商品如果订单明细还去关联实时数据那用户看到的订单历史就会变得面目全非。做电商系统的第一原则就是订单一旦生成所有和交易相关的信息都必须以当时的快照为准。最后是收货地址表t_address字段id、user_id、receiver_name、receiver_phone、province、city、district、detail_address、is_default是否默认地址。默认地址用0/1标记一个用户最多只能有一个默认地址这个逻辑建议在代码里控制而不是完全依赖数据库唯一约束。2.2 订单状态机与库存扣减方案订单状态是整个商城系统里最容易被问出问题的地方。很多同学的订单表只有一个status字段代码里直接写死数字 0是待付款1是待发货2是待收货3是已完成4是已取消。这么写能跑但不够严谨。我更建议你脑子里有一个清晰的“订单状态机”概念订单从创建到完结中间经过哪些合法流转路径哪些状态之间可以互相跳转哪些是终态。在一个典型B2C商城里状态流转是这样的用户提交订单订单初始为待付款用户完成支付变为待发货商家后台发货变为待收货用户确认收货变为已完成待付款状态下用户可以取消订单变为已取消超时未支付也可能被系统自动取消。这里面有几个关键点已取消是终态不能再变回待付款已完成后不能直接回到待发货只能走售后流程本系统如果不做售后就可以不设计这个路径。在实际代码里我会写一个状态变更的方法根据当前状态和动作判断是否允许下一步流转不允许就直接抛出业务异常而不是简单的setStatus到一个新值。库存扣减是并发场景下最容易出问题的环节。商城系统最常见的库存扣减策略有两种一种是下单减库存一种是支付减库存。下单减库存的逻辑是用户提交订单的同时把库存扣掉这样能最大程度避免超卖但如果用户一直不支付库存就被占住了所以需要配合订单超时自动关闭机制。支付减库存则是等到用户付款成功才扣库存用户体验好但对并发的要求更高一旦很多人同时下单最后只有付款的人才能拿到货库存校验要做得特别严格。对于毕业设计和中小型项目我推荐下单减库存而且要在事务里执行。具体到代码层面更新库存的SQL不要写成先查询再更新而是直接用条件更新UPDATE t_tea SET stock stock - #{quantity} WHERE id #{teaId} AND stock #{quantity}这条SQL利用数据库的行锁和条件判断在更新的瞬间就完成了库存校验。如果影响行数为0说明库存不足直接抛出异常回滚事务。这就是最简单的乐观锁思路应付商城系统这种量级完全够了。如果你非要引入分布式锁或者Redis预减库存那就是把一个本来简单的事搞复杂了项目规模配不上这种架构。3. 后端核心逻辑实现从登录鉴权到下单流程3.1 JWT登录鉴权与拦截器配置前后端分离项目里登录鉴权是一个绕不开的课题。Session那套方案在分离架构下不好使因为前端和后端可能不在同一个域名下Cookie的跨域问题、CSRF攻击风险都是麻烦。现在的主流做法是JWTJSON Web Token认证。JWT的本质是把用户的身份信息加密签名后放在token里服务器不需要存储会话状态每次请求时把token放在请求头里带上后端解码验证即可。SpringBoot里整合JWT非常简单用io.jsonwebtoken这个库就能完成token的生成和解析。登录接口的逻辑是前端传来用户名和密码后端根据用户名查出用户记录用BCrypt去比对密码哈希匹配成功就把用户的id、用户名、角色封装成一个Claims用密钥签名生成一个有效期为两小时或者一天看需求的token返回给前端。public String generateToken(User user) { MapString, Object claims new HashMap(); claims.put(userId, user.getId()); claims.put(username, user.getUsername()); claims.put(role, user.getRole()); Date now new Date(); Date expireDate new Date(now.getTime() EXPIRE_TIME); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }有了token的生成还需要配合拦截器去校验后续所有需要登录的接口。这里我用的是SpringMVC的HandlerInterceptor重写preHandle方法从请求头里取出token解析成功就放行把userId放进request attribute里供后续Controller使用解析失败直接返回401并写一个JSON响应体说明原因。然后在WebMvcConfigurer配置类里注册这个拦截器只有登录接口、注册接口和商品查询接口等少数几个白名单接口可以免登录访问。之所以放在拦截器而不是过滤器是因为拦截器能访问Handler对象配合注解可以做出很灵活的权限控制比如后面加一个RequireAdmin注解就能区分管理员接口和普通用户接口。3.2 商品图片上传与MinIO对象存储商品图片上传是电商系统里最基础也最容易出问题的功能之一。最简单的方案是把图片保存到本地磁盘然后通过SpringBoot的静态资源映射让图片可以被URL访问。具体操作是在配置类里添加一个方法Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir); }这样前端把图片传到/upload目录就能通过http://服务器地址:8080/upload/图片名.jpg访问了。这个方案优点是简单缺点是图片会占用服务器磁盘空间而且如果项目用Docker部署容器一旦重建上传的图片就会丢失。所以从工程化的角度我更推荐用MinIO来做对象存储。MinIO是一个开源的、兼容S3协议的对象存储服务安装方式很简单一条Docker命令就能跑起来docker run -p 9000:9000 -p 9001:9001 --name minio \ -v /data/minio:/data \ -e MINIO_ROOT_USERadmin \ -e MINIO_ROOT_PASSWORDadmin123456 \ minio/minio server /data --console-address :9001901端口是管理控制台9000端口是API接口。在SpringBoot里整合MinIO需要引入io.minio:minio依赖然后写一个配置类把endpoint、accessKey、secretKey、bucketName配置到application.yml里再写一个MinioService封装上传方法public String uploadFile(MultipartFile file) throws Exception { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String objectName UUID.randomUUID().toString().replace(-, ) suffix; PutObjectArgs args PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build(); minioClient.putObject(args); return endpoint / bucketName / objectName; }这里有个细节需要注意MinIO默认的桶访问权限是私有的外部通过URL是访问不到对象的会出现图片403。所以创建桶之后要手动设置桶的访问策略为public或者在控制台里配置一条*只读策略。这个坑我踩过一次当初上传接口返回成功前端拿回来的URL却怎么都打不开排查了半天才发现是桶访问策略的问题。3.3 下单流程与事务管理下单是整个商城系统业务逻辑最密集的一个接口涉及购物车校验、库存扣减、订单创建、明细插入等多个数据操作任何一个步骤失败都必须全部回滚。这里就是Spring声明式事务Transactional的典型使用场景。下单接口的逻辑步骤大概是这样的前端把购物车里勾选商品的id列表传过来后端先查出这些购物车记录校验是否都属于当前用户再从购物车表查询对应的商品信息逐条检查商品状态为上架状态且库存充足然后累加计算总金额接下来插入订单主表记录生成一个全局唯一的订单编号再遍历插入订单明细表最后把购物车里已下单的商品删掉批量扣减对应商品的库存。整套逻辑只要中间任何一步抛错事务就会把前面所有操作全部回滚不会出现订单建了但库存没扣这种事。订单编号的生成很多人喜欢用数据库自增id来当订单号这是不可取的因为自增id直接暴露了你的订单量而且拼接上用户id可信度很低。我用的生成策略是日期时间加随机数yyyyMMddHHmmss加四位随机数再拼一个两位数的随机后缀基本能保证同一秒内的订单不重复。如果你要求更高可以再引入Snowflake算法生成分布式ID但对这个量级的项目来说时间戳加随机数已经足够了。Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, ListLong cartIds) { // 1. 校验购物车 ListCart carts cartMapper.selectBatchIds(cartIds); if (carts.stream().anyMatch(c - !c.getUserId().equals(userId))) { throw new BusinessException(购物车数据异常); } // 2. 校验商品状态与库存计算总价 BigDecimal totalPrice BigDecimal.ZERO; for (Cart cart : carts) { Tea tea teaMapper.selectById(cart.getTeaId()); if (tea null || tea.getStatus() ! 1) { throw new BusinessException(商品 cart.getTeaId() 已下架); } if (tea.getStock() cart.getQuantity()) { throw new BusinessException(商品[ tea.getName() ]库存不足); } totalPrice totalPrice.add(tea.getPrice().multiply(new BigDecimal(cart.getQuantity()))); } // 3. 创建订单主表 // 4. 创建订单明细 // 5. 删除购物车记录 // 6. 扣减库存 // 7. 返回订单信息 return orderVO; }这里我要额外讲一个容易被忽略的事务失效场景Transactional注解默认只在方法抛RuntimeException时回滚如果你catch住了异常没有往外抛事务就不会回滚。所以我写业务代码时有个习惯除非需要捕获异常做特殊处理比如把异常信息返回给前端否则绝不吞异常。而且要注意rollbackFor Exception.class必须显式指定因为默认值只回滚运行时异常检查异常抛出去是不会回滚的。4. 前端实现Vue3Element Plus搭建商城界面4.1 工程结构、路由设计与动态路由前端工程我推荐用Vite来构建Vue3配合Vite的启动速度确实比webpack时代的Vue CLI快了一个量级。创建一个新项目的命令非常简单npm create vitelatest tea-shop-front创建完项目后第一件事是安装必要的依赖vue-router路由、pinia状态管理Vuex的替代方案、axiosHTTP请求、element-plus组件库。然后我们要规划工程目录假如说你的项目里有用户端和管理端两套界面就按模块拆分views/user放首页、分类页、商品详情、购物车、订单列表views/admin放数据看板、商品管理、订单管理、用户管理api目录下按业务模块拆分接口定义components放通用组件router放路由配置stores放Pinia的状态定义。路由设计是前端项目的骨架。用户端我采用嵌套路由加布局组件的模式最外层的Layout.vue包含顶部导航栏和底部信息栏子路由通过children组合const routes [ { path: /, component: () import(../layouts/UserLayout.vue), children: [ { path: , name: Home, component: () import(../views/user/Home.vue) }, { path: category/:id, name: Category, component: () import(../views/user/Category.vue) }, { path: tea/:id, name: TeaDetail, component: () import(../views/user/TeaDetail.vue) }, { path: cart, name: Cart, component: () import(../views/user/Cart.vue) }, { path: orders, name: Orders, component: () import(../views/user/Orders.vue) } ] }, { path: /login, name: Login, component: () import(../views/user/Login.vue) } ]这里所有组件都用了() import这种动态导入写法也就是路由懒加载。好处是首屏只加载首页需要的代码其他页面在路由切换时再按需加载能显著提升首页打开速度。对于商城这种页面数量多、图片体积大的项目这个优化几乎是必须的。关于登录权限和动态路由我的做法是在路由守卫router.beforeEach里检查目标路由是否需要登录权限需要的话就判断Pinia里有没有token没有就跳转到登录页并带上redirect回跳参数。管理端的路由不直接写在静态路由表里而是在用户登录后根据后端返回的role字段动态添加。如果role是管理员就通过router.addRoute把管理端那几条路由加进去否则访问管理端页面时直接拦截跳转。这个方案虽然比直接把所有路由写好再靠页面内判断复杂一点但更贴近真实项目的权限控制思路。4.2 Axios请求封装与统一响应处理前端和后端交互的每一处都在用Axios发请求如果每个页面都写一遍完整的请求逻辑代码会非常冗余。所以我在api/request.js里封装一个axios实例统一处理baseURL、请求超时时间、请求拦截器、响应拦截器这些逻辑。const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[token] 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 error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )这个封装的细节很多值得说道的关键点。第一我在请求头里放的是token这个自定义字段所以后端拦截器取请求头时要保持一致。第二后端返回的统一格式是{ code, message, data }响应拦截器里如果code不是200就直接弹出错误消息并reject业务代码里就不用到处写try catch和if判断了。第三当后端返回401状态码时说明token过期了这时候自动清除本地token并跳回登录页。这比让每个页面自己判断要省事得多。接口定义这块按模块拆分文件管理每个模块一个文件比如user.js里放登录、注册、获取用户信息相关的接口tea.js里放商品列表、详情、搜索的接口cart.js里放购物车的增删改查接口。每个接口导出的是一个函数函数内部调用封装的request实例。4.3 商品详情页、购物车与订单交互细节商品详情页是用户端最复杂的页面之一因为它要同时处理商品图片轮播、价格展示、库存状态、数量选择、加入购物车、立即购买等一堆交互。我的建议是把页面组件拆细轮播图单独做成ImageCarousel.vue组件、数量选择器做成QuantityInput.vue组件这样代码可读性高也方便复用。购物车页面的核心逻辑是数据联动。购物车里每一项都有勾选状态页面底部展示的是已勾选商品的总价。这个时候我建议用一个computed计算属性来实时计算总价而不是在每次点击勾选时手动去更新price。Vue的双向绑定和计算属性的特性天然适合这种场景只要勾选状态变了总价自动跟着变不需要写一堆事件监听。结算跳转到订单确认页时前端会把勾选的购物车id列表通过路由参数或者Pinia传过去订单确认页通过后端查询接口拿到这些商品的最新信息再次展示商品列表和总价。这里有一个交互上的细节订单确认页展示的价格应该以后端返回的最新价格为准而不是直接复用购物车里存的价格因为购物车里存的价格可能已经过时了比如管理员刚刚调了价。虽然购物车表里可以冗余存一个价格但真正结算时必须以接口实时查询的为准。5. 前后端联调、打包与部署实录5.1 开发环境的跨域问题与代理方案前后端分离开发时前端跑在本地5173端口Vite默认后端跑在8080端口浏览器直接从前端页面发起http://localhost:8080/api/xxx的请求时会因为跨域规则被浏览器拦截。解决这个问题最省事的方案不是在SpringBoot里打开CORS而是在Vite的配置文件vite.config.js里配置开发代理export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } })这样设置后前端请求/api/teas会被Vite开发服务器代理转发到http://localhost:8080/teas浏览器看到的请求是同源的自然不会触发跨域。注意rewrite这里把/api前缀去掉了所以后端接口的路由就不需要统一加/api前缀前后端的路径设计通过代理层做了一层解耦。如果你后端确实想保留/api前缀就把rewrite去掉代理转发时保持路径不变。为什么要用代理而不是直接在后端配置跨域因为CORS配置在开发时虽然能用但在生产环境里前端静态文件如果由Nginx托管后端接口如果不在同一个域名下还是绕不开跨域。与其在生产环境用CORS硬扛不如开发和生产都用同一种方案——通过反向代理把前后端统一到同一个访问源下。这才是企业里常见的最佳实践。5.2 打包部署Nginx托管前端加反向代理开发环境跑通不代表项目就完成了最终要能部署到服务器上给别人访问这才是完整的项目交付。前后端分离项目的部署方案有两种一种是把前端打包后的静态文件直接放进SpringBoot的src/main/resources/static目录让SpringBoot同时提供页面和接口服务这种方式最简单打一个jar包就完事但失去了前后端分离的意义每次前端代码有更新都得重新构建整个jar包另一种是前端构建产物交给Nginx后端单独跑jar包Nginx worker进程只托管静态资源并把/api/下的请求代理转发到Java服务。我强烈推荐第二种方案。先在前端项目根目录执行npm run build生成dist目录然后把dist里的内容复制到Nginx的html/tea-shop目录。Nginx的配置大概是这样的server { listen 80; server_name tea.example.com; root /usr/share/nginx/html/tea-shop; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有两个关键配置第一个是location /的try_files $uri $uri/ /index.html因为Vue Router默认是history模式前端路由切换时URL路径会变化比如访问/cart但服务器上其实没有cart这个目录Nginx通过try_files把这个请求回退到index.html让前端路由自己去渲染对应页面。如果不写这行配置用户刷新购物车页面就会出现404。第二个是location /api/的proxy_pass http://localhost:8080/注意这里proxy_pass的地址后面有一个斜杠它的含义是把/api/前缀去掉再转发给Java服务。如果忘了加斜杠后端接口收到的请求路径里会带上/api前缀和Controller的RequestMapping对不上直接404。后端打包就更简单了。mvn clean package打出的jar包直接java -jar tea-shop.jar就能启动。如果想让部署更规范一点写一个Dockerfile做成镜像也是顺手的事情FROM openjdk:17-alpine WORKDIR /app COPY target/tea-shop.jar tea-shop.jar EXPOSE 8080 ENTRYPOINT [java, -jar, tea-shop.jar]构建镜像的代码就这么几行关键是要理解FROM openjdk:17-alpine这个基镜像一定要跟你本地开发用的JDK版本一致如果你的pom.xml里配置的是Java 8却用了JDK 17的镜像来跑大概率会报UnsupportedClassVersionError错误。5.3 常见问题与排查技巧实录项目开发和部署过程中我整理了一份高频问题的速查表每一条都是实际踩过或者帮别人排查过的坑。第一个高频问题是图片上传后404。会造成这种情况的原因通常有两种用本地存储方案时忘记在配置类里添加静态资源映射SpringBoot默认不对外暴露本地非public目录的文件用MinIO方案时桶的访问策略还是私有状态上传的Object外部不可见。排查思路就是先确认上传接口返回的URL能不能直接访问不能访问再按照两种方案分别检查对应配置。第二个高频问题是前端请求接口时一直报跨域错误即使加了代理配置也不行。这个大概率是代理配置没生效比如修改了vite.config.js没有重启开发服务器或者proxy的target地址写错了端口。记住一个要点Vite的配置文件修改后必须重启才生效热更新不会重新加载配置文件。还有一个容易被忽视的点如果前端请求的是完整路径http://localhost:8080/api/login而不是/api/login那么代理规则里的/api匹配不到请求会直接发到前端服务器。第三个高频问题是时间字段返回格式不对。后端返回Date类型默认序列化出来是时间戳或者yyyy-MM-ddTHH:mm:ss这种带T的格式前端展示不友好。解决办法是在application.yml里配置JackSon的全局格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8注意时区一定要设置成GMT8因为MySQL连接串里的serverTimezoneAsia/Shanghai和JackSon的时区都要一致否则会出现日期比实际时间少了8小时的问题。第四个高频问题是部署到Linux服务器后前端页面能打开但点击登录按钮没反应、控制台报404。这个场景就是前面提到的Nginx try_files和proxy_pass配置问题检查点有三个静态资源路径对不对、try_files回退有没有配置、代理转发的路径有没有多带前缀。6. 一些可以继续扩展的方向如果你拿到手的是一个功能较基础的版本或者你想在答辩时有一些加分项我建议可以从这几个方向做扩展。第一个是订单超时自动取消用SpringBoot的Scheduled定时任务每分钟扫描一次超过30分钟未支付的订单把状态改为已取消并恢复库存。这里要小心并发问题定时任务用条件更新SQL去恢复库存就不会超卖。第二个是简单的数据统计比如根据订单数据计算每天的销售额、热门商品排行用SQL的GROUP BY和DATE_FORMAT函数就能实现再配合ECharts做一个折线图或者柱状图答辩展示的效果会非常直观。第三个是接入支付回调微信支付和支付宝的沙箱环境都支持个人开发者测试虽然打通支付链路有点繁琐但一旦做出来整个项目的完整度会提升一个档次。想做这些扩展也要注意不要过度设计比如为了追求“高并发”去引入Redis缓存商品列表、用RabbitMQ做异步下单这些技术在面试里能聊但在这个体量的项目里实际上是一种负担。你想想本地跑的一个案例项目商品量级是几百条QPS能不能过百都不好说引入微服务架构只会增加排错难度。把核心链路的代码写得干净、规范把状态流转和事务边界讲清楚比堆砌一堆用不上的中间件要加分得多。在项目里部署Edge One 或 考虑前端技术选型时如果你愿意去折腾这些基础设施会花掉大量不必要的精力。更推荐的做法是先把单体应用打磨扎实。从我个人带项目的经验看最后验收时老师最认可的是那种“逻辑闭环完整、代码有清晰职责划分、能讲明白每一个设计决策”的项目。这几个方面前面讲的表设计和事务处理就是分量最重的部分。如果你手里正拿着“049茶叶商城系统”这个项目在复现我给你的建议是先别急着跑起来而是把源码目录结构和数据库脚本先通读一遍对照我上面讲的核心表结构和接口分层把项目的骨架在脑子里建起来。然后从后端的商品模块开始一个个业务模块过再切换到前端的页面和接口调用把数据流串起来。最后再动手部署遇到问题对照排查表处理。这样走完一遍你对项目的理解深度和你同学那种只知道点运行按钮的状态是完全不同的两个层次。

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

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

免费获取报价 →
↑