ARTICLE DETAIL

资讯详情

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

Spring Boot宠物商城网站设计与实现:从数据库设计到订单状态机实战

Spring Boot宠物商城网站设计与实现:从数据库设计到订单状态机实战 最近在技术群里看到好几个朋友在问同一个问题用Spring Boot做一个宠物商城网站功能到底应该怎么设计数据库怎么建踩坑怎么避免。这个问题其实很有代表性——基于Spring Boot的宠物商城网站设计与实现这类题目看起来是个标准的电商项目但宠物商品和普通商品比如衣服、数码产品有本质区别。很多人拿着这个题目就开始写代码写到购物车发现库存逻辑对不上写到订单又发现状态流转混乱最后只能推倒重来。我前前后后参与过好几个电商类项目的设计与开发也帮人改过不少这类毕设和实训项目的代码。今天不写那种教科书式的项目介绍直接把整套宠物商城网站的设计思路、核心代码结构、数据库设计细节和实际踩过的坑整理出来。内容围绕Spring Boot展开覆盖从需求拆解到部署上线的完整链路适合正在做毕业设计的学生也适合想拿真实业务场景练手的初级开发者。你既可以把它当作完整项目的参考骨架也可以只挑里面关系型数据库设计、订单状态机、权限认证这些模块来复用。1. 把宠物商城四个字拆成一份可落地的功能清单很多人在拿到题目之后的第一反应是商城嘛不就是商品列表加购物车加订单。如果你这么想后面大概率要返工。宠物商城这个命题里真正决定项目质量的是宠物两个字——它带来的业务复杂度远超普通商品。1.1 商城类系统绕不开的基础模块先说说所有商城都有的部分这部分是骨架没有它项目不成立用户模块注册、登录手机号验证码或用户名密码、个人信息维护、收货地址管理。商品模块分类浏览、商品列表含分页和条件筛选、商品详情多图展示、价格、库存状态。购物车模块加入购物车、修改数量、选中结算、删除。订单模块订单创建、订单列表按状态筛选、订单详情、取消订单、确认收货。管理后台商品上下架、分类维护、订单处理发货、用户管理、基础数据统计。这些模块在功能上互相依赖但在代码层面建议完全解耦。我见过不少设计方案把商品和订单揉在一个Service里看起来省事实际维护起来非常痛苦。建议按照标准的单体分层结构来做Controller只做参数校验和路由转发Service只做业务逻辑和事务管理Mapper/DAO层只做数据持久化。这个习惯越早养成越好。1.2 宠物商品独有的业务场景这才是这个项目的分水岭。宠物作为商品有几个普通商品没有的特点第一一物一码。你卖一只布偶猫库存数量就是1不能像卖手机一样设置库存为999。这就意味着商品表的库存字段不能单纯做加减法还需要一个锁定状态——有人下单但未支付时这只宠物不能同时被另一个人下单。第二信息密度极高。买一只狗或者一只猫用户需要看的东西远不止品牌、型号、价格。它需要看出生日期、疫苗记录、驱虫记录、健康状态描述、父母信息甚至血统证书。这些信息不是两张表就能搞定的需要专门设计健康档案表与基因/血统信息表。第三交易周期长。普通电商下单后基本就是发货、收货、完成宠物交易可能涉及定金 尾款这种场景或者用户在购买前会反复咨询甚至还要视频连线看宠物状态。如果你在订单模块里拆了定金单和尾款单会让数据对账变得很复杂建议直接用一个订单主表加一个交易流水表来处理支付明细而不是拆成多个订单。第四售后逻辑特殊。普通商品七天无理由退货宠物不行它有活体运输风险、健康周期等因素。所以订单状态里建议增加售后审核这类环节而不是简单地在完成态下加上退货。1.3 功能优先级先走通主链路再谈锦上添花结合以上分析我给这类项目做的功能优先级排序是这样的优先级功能模块说明P0用户登录、商品列表/详情、购物车、订单创建、模拟支付、后台发货主交易链路没它项目不成立P1宠物健康档案展示、分类筛选、订单状态流转、后台商品上下架体现宠物业务特色也是评分/答辩重点P2地址管理、优惠券、搜索历史、数据统计图表、图片批量上传加分项属于体验优化P3WebSocket在线问诊、视频看宠、Spring Boot Admin监控、定时优惠活动扩展亮点时间充裕再做你如果真的想把这个项目做成能拿出来说的作品P0和P1必须做得非常扎实P2选两个做起来就行P3属于锦上添花但答辩效果极好。后面我会详细展开P0和P1的关键实现。2. 技术选型为什么Spring Boot是这类项目的最优解我知道肯定有人会在技术群里争论为什么不用FastAPI为什么不用Node.js。技术选型这事脱离场景谈优劣都是耍流氓。针对宠物商城网站设计与实现这个题目Spring Boot就是最合理的选择而且没有之一。原因有三点我逐个说清楚。2.1 Spring Boot解决了这类项目的核心痛点商城类网站的本质是什么是业务逻辑复杂、数据关系密集、需要稳定事务支撑的管理系统。用户在浏览商品、加购、下单、支付这一整套流程里任何一步出现数据不一致都会引发严重问题。Spring Boot最大的优势是把Spring生态那套成熟的事务管理、依赖注入、面向切面编程能力用极简的配置方式提供出来。你只需要加一个Transactional注解就能保证扣库存创建订单生成支付流水这三步操作要么全部成功、要么全部回滚。换成FastAPI或者Node.js你得自己去实现分布式事务或者手工补偿逻辑复杂度完全不是一个级别。对于做设计类项目的人来说把精力省下来去打磨业务细节比折腾底层事务框架要有价值得多。另一个原因是生态成熟度。Spring Boot有极其完善的第三方集成方案Spring Security做权限、MyBatis-Plus做数据持久化、Redis做缓存、Spring Boot Admin做监控、WebSocket做实时通信全部都有成熟稳定的Starter配置非常统一。这些能力对宠物商城来说几乎全是刚需。2.2 版本选择别盲目追求最新版在帮人检查代码的过程中我发现很多人一上来就选最新的Spring Boot 3.x然后折腾JDK 17的兼容性又踩一遍Jakarta命名空间迁移的坑。我的建议非常明确使用Spring Boot 2.6.x JDK 8/11 MyBatis-Plus 3.5.x。这个组合是经过大量生产环境验证的文档齐全网上遇到问题时能搜到的解决方案也最多。2.6.x相比2.3.x加入了路径匹配策略的调整默认禁止了*匹配所有路径如果从老项目迁移需要留意spring.mvc.pathmatch.matching-strategy配置。如果你确实想用Spring Boot 3.x那就要做好两个心理准备第一JDK必须17以上第二原来的javax.*包名全部要改成jakarta.*。这不是不能做但对一个以设计和实现为核心目标的项目来说完全没有必要把时间花在环境适配上面。2.3 ORM选型与配套中间件数据库访问层建议直接用MyBatis-Plus。理由很简单单表CRUD它帮你写完了复杂查询你还能手写XML控制SQL。宠物商城的查询场景非常杂——前端列表要根据品种、年龄、价格区间、是否已售等多个条件动态组合用MyBatis-Plus的LambdaQueryWrapper可以非常优雅地动态拼接条件而不需要写一堆if判断去拼接字符串SQL。中间件方面Redis在这个项目里是有实际价值的商品详情页的访问热点集中把商品基本信息缓存到Redis可以显著降低数据库压力购物车也可以用Redis实现以用户ID作为key商品ID作为field用Hash结构存储性能远好于关系型数据库表。不过要注意缓存一定要设置过期时间和主动更新策略否则商品价格改了用户看到的还是旧数据。另外一个容易被忽略的配套工具是Spring Boot Admin它能把项目的运行状态内存、线程、健康检查、请求映射可视化展示出来。在项目的答辩环节你打开监控面板展示平稳运行的指标比说一百句系统稳定都有说服力。3. 数据库设计宠物商品的信息密度比普通商品高一个量级数据库设计是这类项目的灵魂也是很多人卡壳最久的地方。我见过太多人把宠物商城设计得像一个简单的二手交易平台——一张商品表一个价格字段一个库存字段然后就没有然后了。这种设计表面上看能跑实际上完全体现不出宠物商城的业务内涵。3.1 核心表结构设计我实际推荐的表结构如下去掉了一些非核心字段保留主干用户表t_userid、username、passwordBCrypt加密、phone、avatar、create_time。宠物分类表t_pet_categoryid、category_name猫/狗/水族/小宠、parent_id支持二级分类比如猫下面分英短/美短/布偶、sort_order排序权重。宠物商品表t_pet这个表是核心字段需要认真设计。除了常规的id、category_id、title、cover_image、price、original_price、banner_images、status上架/下架/已售、create_time、update_time之外建议加上这几个关键字段pet_type区分是活体宠物还是宠物用品因为商城经常混卖宠物猫粮、玩具、笼子等周边商品。gender公/母。买猫买狗的人普遍在意性别。birth_date出生日期用于计算年龄展示2个月零10天这种效果。inventory_status库存状态字段0未锁定/1已锁定用于一物一码的宠物商品。view_count浏览量用于人气宠物推荐。宠物档案表t_pet_profile关联t_pet表的主键ID字段包括vaccine_record疫苗记录用JSON数组存储比如[第一针猫三联,第二针猫三联,狂犬疫苗]、deworm_record驱虫记录文本描述、health_status健康状态描述活泼好动/轻微软便等、certificate_no血统证编号没有可留空、parent_info父母信息JSON格式存储父母品种和照片链接。购物车表t_cartid、user_id、pet_id、quantity、checked是否选中结算、create_time。订单主表t_orderid、order_no唯一订单号、user_id、total_amount、pay_amount实付金额、pay_type支付方式、status订单状态、receiver_name、receiver_phone、receiver_address、remark买家留言、pay_time、ship_time、finish_time、cancel_time、create_time。订单明细表t_order_itemid、order_id、pet_id、pet_title、pet_image、price下单时快照价格、quantity。明细表必须冗余商品名称和图片因为商品可能后续被修改或者下架但订单历史必须保持当时的信息快照。支付流水表t_pay_transactionid、order_no、transaction_no模拟支付平台流水号、amount、status、callback_time模拟支付回调时间。3.2 宠物档案表为什么不用JSON字段一把梭有人可能会说疫苗记录、驱虫记录、父母信息直接用JSON字段存在t_pet表里不就行了技术上确实可以但我不建议这么做。原因在于管理后台有独立的档案编辑页面疫苗记录可能要支持逐条新增、删除、修改还要记录每次操作的时间。如果把JSON存成一个整体字符串每次修改都得把整个JSON读出来、反序列化、改完再序列化写回去。逻辑复杂不说还容易出现并发覆盖的问题。拆成独立的关联表或者分包成子表后续无论是展示、统计比如所有打过狂犬疫苗的宠物还是扩展都会灵活很多。如果你是抱着简化设计的心态那退一步的方案是t_pet_profile主表 t_pet_vaccine子表。主表存健康状态和证书信息子表专门存疫苗记录每条记录包含名称和接种时间。这个设计既不复杂又能支撑答辩时被追问如何查询所有疫苗齐全的宠物这类问题。3.3 订单与库存的关联设计——宠物商城最容易翻车的地方普通商品的库存扣减逻辑是下单时UPDATE t_sku SET stock stock - 1 WHERE id ? AND stock 0用乐观锁保证不超卖。宠物商品因为一物一码的天然属性处理方式完全不同。我的方案是给宠物商品表加一个inventory_status字段同时配合订单状态做三段式锁定用户创建订单时将inventory_status从0改为1并在订单表记录锁定时间。如果用户超过30分钟未支付定时任务自动将订单置为取消状态同时把inventory_status改回0。用户支付成功后订单进入待发货状态宠物继续保持锁定。后台发货后订单进入已发货状态此时才允许把宠物商品标记为已售status字段改为2inventory_status可以保留锁定状态到订单完成。这个设计避免了扣库存和订单状态不一致的问题。你在微服务架构里可以把它做成独立的库存服务但对于单体应用来说一张表加一个状态字段完全够用。4. 核心交易链路从商品浏览到订单支付的完整实现功能清单和数据模型确定之后接下来就是最核心的交易链路实现。这一段我按照前端用户实际操作的顺序来讲。4.1 商品查询动态条件筛选与缓存策略宠物商城首页和列表页通常需要支持多条件组合筛选。用户在页面上可能同时选择猫、2-4个月、预算2000-5000、已打第一针疫苗这些条件。如果用传统的SQL拼接代码会非常冗长且难以维护。MyBatis-Plus的LambdaQueryWrapper非常适合这个场景。核心代码逻辑如下public PagePet queryPetList(PetQueryDTO dto) { LambdaQueryWrapperPet wrapper new LambdaQueryWrapper(); // 按分类筛选 wrapper.eq(dto.getCategoryId() ! null, Pet::getCategoryId, dto.getCategoryId()); // 按性别筛选 wrapper.eq(StringUtils.hasText(dto.getGender()), Pet::getGender, dto.getGender()); // 按价格区间筛选 wrapper.between(dto.getMinPrice() ! null dto.getMaxPrice() ! null, Pet::getPrice, dto.getMinPrice(), dto.getMaxPrice()); // 按年龄筛选根据出生日期动态计算 if (dto.getMinMonth() ! null || dto.getMaxMonth() ! null) { LocalDate now LocalDate.now(); if (dto.getMaxMonth() ! null) { wrapper.ge(Pet::getBirthDate, now.minusMonths(dto.getMaxMonth())); } if (dto.getMinMonth() ! null) { wrapper.le(Pet::getBirthDate, now.minusMonths(dto.getMinMonth())); } } // 只查上架状态的商品 wrapper.eq(Pet::getStatus, PetStatus.ON_SALE.getCode()); wrapper.orderByDesc(Pet::getCreateTime); return petMapper.selectPage(new Page(dto.getPageNum(), dto.getPageSize()), wrapper); }这里面有一个非常关键的细节年龄筛选不要存年龄字段而是存birth_date然后动态换算。因为宠物的年龄是实时变化的存龄字段需要定时更新早晚会出数据不一致的问题。用出生日期动态计算是唯一正确的做法。商品详情页的数据建议做两级缓存。一级是Rediskey设计为pet:detail:{id}value存储JSON序列化后的商品详情包含宠物档案二级是本地应用缓存Caffeine。读取顺序本地缓存 → Redis → 数据库数据库查询回来后反填到缓存。缓存过期时间设为30分钟比较合适既不会让数据过于陈旧也不会频繁过期导致缓存穿透。后台编辑商品信息时同步删除对应的Redis缓存。4.2 购物车数据库表方案与Redis方案的取舍购物车的实现有两种主流方案我来说说各自的适用场景。数据库表实现设计t_cart表用户加购、勾选、删除都走CRUD。优点是对账清晰重启数据不丢缺点是每一次加购都要写数据库压力较大。Redis Hash实现用cart:{userId}作为keypetId作为field商品数量等信息作为value。优点是性能极高、代码简单缺点是需要处理缓存过期和持久化问题。宠物商城这个场景我推荐数据库表方案。原因非常务实宠物商城不是高并发秒杀系统用户加购频率很低数据库完全抗得住而且订单创建时需要根据购物车记录联动查询商品信息和库存状态走数据库天然具备事务一致性。Redis方案在毕业设计答辩现场还会面临redis宕机怎么办的灵魂拷问数据库表方案没有这种问题。购物车的核心操作里有一个容易被忽略的校验加入购物车时必须校验商品的status是否为上架状态、inventory_status是否未被锁定。否则用户把一只已经被人下单但未支付的宠物加进购物车结算时才发现不能提交订单体验非常差。我的做法是在加入购物车的Service方法里直接查一次商品表不满足条件直接抛业务异常由全局异常处理器转为友好提示返回给前端。4.3 订单创建事务、状态机与幂等性订单创建是交易链路里最复杂的一步涉及用户校验、购物车数据读取、商品状态校验、库存锁定、订单主表和明细表写入、购物车清理。这一整串操作必须放在同一个事务里任何一步失败都整体回滚。我建议的Service层核心代码结构如下Transactional(rollbackFor Exception.class) public OrderCreateResult createOrder(OrderCreateDTO dto) { // 1. 校验用户地址信息 UserAddress address addressService.getValidAddress(dto.getAddressId(), dto.getUserId()); // 2. 读取购物车中勾选的商品 ListCartItem checkedItems cartService.getCheckedItems(dto.getUserId()); if (checkedItems.isEmpty()) { throw new BizException(请先选择要结算的商品); } // 3. 校验商品状态并计算总价 ListOrderItem orderItems new ArrayList(); BigDecimal totalAmount BigDecimal.ZERO; for (CartItem item : checkedItems) { Pet pet petService.getById(item.getPetId()); if (pet null || pet.getStatus() ! PetStatus.ON_SALE.getCode()) { throw new BizException(商品已下架 pet.getTitle()); } if (pet.getInventoryStatus() 1) { throw new BizException(该宠物已被其他用户锁定请重新选择); } // 4. 锁定宠物库存 petService.lockInventory(pet.getId()); // 组装订单明细快照 OrderItem orderItem buildOrderItem(pet, item.getQuantity()); orderItems.add(orderItem); totalAmount totalAmount.add(orderItem.getPrice().multiply( BigDecimal.valueOf(orderItem.getQuantity()))); } // 5. 生成订单主表 Order order createOrderMaster(dto.getUserId(), dto.getAddressId(), totalAmount, orderItems); // 6. 生成订单编号格式时间戳 用户ID 随机数 order.setOrderNo(generateOrderNo(dto.getUserId())); // 7. 清理购物车已结算项 cartService.removeCheckedItems(dto.getUserId()); return new OrderCreateResult(order.getOrderNo(), order.getTotalAmount()); }这段代码里有几个值得展开讲的关键点幂等性用户可能因为网络原因重复提交同一个创建订单请求导致生成两笔相同内容的订单。处理方案有两种一是前端在提交后立刻禁用按钮这是体验层的兜底二是后端在订单表增加一个source_token唯一字段前端提交时生成一个UUID放入请求头数据库层面保证唯一约束重复请求直接报重复异常。订单号生成不要用数据库自增ID当订单号那个会暴露平台的订单量而且长度不够随机。推荐格式年月日时分秒 用户ID后四位 随机四位数字。例如2025060710381500012345。如果遇到并发加随机四位后基本不会冲突即使冲突数据库捕获唯一索引异常后重试即可。金额计算所有涉及金额的字段都用BigDecimal严禁使用double或float。宠物商城可能会出现定金 尾款这类精确金额计算场景用浮点数会出现0.10.20.30000000000000004这种经典问题直接导致对账不平。4.4 订单状态流转与模拟支付订单状态设计我推荐用枚举类维护代码里禁止散落魔法数字public enum OrderStatus { UNPAID(0, 待支付), PAID(1, 待发货), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELED(-1, 已取消), REFUNDING(4, 售后中), REFUNDED(5, 已退款); private final int code; private final String desc; // getter... }合法的状态流转路径UNPAID→PAID用户支付成功回调后更新。UNPAID→CANCELED用户主动取消或定时任务30分钟未支付自动取消。PAID→SHIPPED后台管理员发货需要填入物流单号。SHIPPED→COMPLETED用户确认收货或发货后7天系统自动确认。PAID/SHIPPED→REFUNDING→REFUNDED用户发起售后简化版。实现层面对非法的状态流转直接用状态机校验或者在Service方法内检查if (order.getStatus() ! OrderStatus.UNPAID.getCode())抛异常即可。不要过度设计状态机框架这个项目的复杂度用简单的if判断完全够。关于支付模块我的建议是对接一个模拟支付页面不要接入真实第三方支付。原因很简单真实支付需要商户资质而且涉及资金合规问题。模拟支付的实现路径是用户点击支付→前端跳转到模拟支付页显示应付金额→用户点击确认支付→后端调用模拟支付Service生成一笔t_pay_transaction流水将订单状态从UNPAID改为PAID同时更新支付时间。如果你想让答辩更有亮点可以模拟真实支付的回调逻辑支付成功后通过Spring的事件发布机制ApplicationEventPublisher触发一个异步任务模拟支付平台回调更新订单并推送用户通知。这样既体现了对真实支付流程的理解又不需要接入任何外部系统。5. 会员体系、后台管理与权限控制一个完整的商城网站必须有前后台两套逻辑。前台面向用户后台面向管理员。如果前后台混在一个入口权限会很难控制。我的方案是拆成两个模块pet-admin管理后台和pet-web用户端商城但共享同一个数据库和Service层。5.1 基于JWT的登录认证用户端的登录认证我推荐用JWT而非传统的Session。原因很简单Session需要服务端存储JWT是无状态的非常适合前后端分离的部署场景。JWT令牌的生成使用jjwt库核心用法如下// 生成token有效期设置为2小时 long expiration 2 * 60 * 60 * 1000; String token Jwts.builder() .setSubject(userId.toString()) .claim(username, user.getUsername()) .claim(role, USER) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expiration)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();这里有一个非常容易踩的坑jjwt 0.9.1和0.11.5的API差别非常大。0.9.1版本用Jwts.builder().signWith(SignatureAlgorithm.HS256, secretKey)而0.11.5版本要求Jwts.builder().signWith(secretKey)且密钥长度必须大于256位。很多人在整合Spring Security时看到报错WeakKeyException就是因为密钥长度不满足要求。解决方法是生成一个Base64编码后的32字节以上密钥或者用Keys.secretKeyFor(SignatureAlgorithm.HS256)自动生成。这里我实际推荐使用0.11.5版本因为0.9.1已经停止维护存在安全漏洞。认证流程是用户登录成功后返回token前端存储在localStorage或内存变量中每次请求在Authorization请求头携带Bearer token。后端写一个JwtAuthenticationFilter继承OncePerRequestFilter拦截所有/api/**请求解析token后把用户信息放入SecurityContextHolder。密码存储一律使用BCryptPasswordEncoder绝不存明文。5.2 管理后台分类管理与商品上下架后台管理端的核心是让运营人员能维护整个商品目录。这里我讲讲分类管理的最佳实践。分类表我设计了parent_id支持自关联前台展示时需要把二级分类组合成树形结构。推荐的实现方式是查询所有分类后在内存中组装成树而不是在SQL里做递归查询。数据量不大时内存组装性能极高且代码清晰。public ListCategoryTreeNode buildCategoryTree() { ListPetCategory allCategories categoryMapper.selectList(null); MapLong, ListPetCategory categoryMap allCategories.stream() .collect(Collectors.groupingBy(PetCategory::getParentId)); // 根节点 pid 为 0 return buildChildren(0L, categoryMap); }商品上下架功能存在一个业务陷阱商品下架时用户购物车里已经添加了该宠物怎么办如果直接允许下架用户结算时会因为商品不存在而报错如果禁止下架运营又无法管理。已售状态的宠物商品必须自动下架这是通过后台发货操作联动实现的。而下架操作时建议同时清理该商品在Redis中的缓存且在下架校验中提示该商品已被N个用户加入购物车是否确认下架让运营做出判断。5.3 订单提醒、定时任务与基础统计后台订单管理的核心痛点是待发货订单漏发货。这里我引入了一个简单实用的定时任务方案用Spring自带的Scheduled注解每5分钟扫描一次超过2小时仍未发货的已支付订单通过站内信或者短信平台可以对接阿里云短信也可以直接对接一个模拟的日志通知提醒管理员。Component public class OrderRemindTask { Scheduled(cron 0 */5 * * * ?) public void remindPendingShipOrders() { ListOrder pendingOrders orderMapper.selectList( new LambdaQueryWrapperOrder() .eq(Order::getStatus, OrderStatus.PAID.getCode()) .lt(Order::getPayTime, LocalDateTime.now().minusHours(2))); pendingOrders.forEach(order - { // 发送提醒通知异步处理不要在定时任务里做耗时操作 asyncNotifier.notifyAdmin(有订单超过2小时未发货 order.getOrderNo()); }); } }定时任务还有一个关键业务处理超过30分钟未支付的订单并释放宠物库存。这个功能必须在前台抢购场景下才有意义。我建议使用Scheduled每1分钟执行一次扫描订单表里status0且create_time小于当前时间30分钟的订单将其置为取消状态并将对应宠物的inventory_status改为0。基础统计方面不需要引入复杂的报表工具。直接在后台首页用几个卡片展示今日新增用户数、今日订单数、今日销售额、待发货订单数再配合一个近7日销售额折线图前端使用ECharts绘制后端提供聚合查询接口就足够了。这类聚合查询不需要实时计算可以每天晚上跑一个定时任务把统计结果写入统计表后台直接查统计表响应速度会非常快。6. 部署上线与踩坑记录项目开发完成不等于项目结束部署上线才是真正检验代码质量的时刻。这一节我来分享整个过程中最容易踩的坑以及对应的解决方案。6.1 环境准备与打包部署先说基础环境。生产服务器建议使用2核4G的云服务器安装JDK 8或11、MySQL 5.7或8.0、Redis 6.x、Nginx。打包命令很简单mvn clean package -DskipTests打包后会在target/目录下生成pet-mall-0.0.1-SNAPSHOT.jar。启动命令建议使用nohup配合-Xms和-Xmx参数避免JVM动态申请堆内存导致性能抖动nohup java -jar -Xms512m -Xmx512m -Dspring.profiles.activeprod \ pet-mall-0.0.1-SNAPSHOT.jar /app/logs/pet-mall.log 21 这里说一下application.yml的Profile设计我习惯拆成三个文件application.yml公共配置比如应用名、端口。application-dev.yml开发环境配置数据库地址本地日志级别DEBUG打印SQL。application-prod.yml生产环境配置数据库地址云服务器日志级别INFO关闭SQL打印。生产环境务必开启spring.jpa.show-sqlfalse或者mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl关闭SQL打印否则日志文件会在一夜之间暴涨到几个GB。这个坑我真实踩过血泪教训。6.2 我实际遇到过的三个典型问题问题一MyBatis-Plus分页插件不生效症状是selectPage返回的数据条数正确但total始终为0或者分页根本没生效。原因是MyBatis-Plus 3.5.x版本必须显式配置分页插件在配置类里加入Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }如果你用老版本的MyBatis-Plus3.4.x之前配置的是PaginationInterceptor类名不一样直接迁移会编译报错。另外注意DbType一定要和数据库对应否则生成的方言SQL可能不兼容。问题二JSON序列化导致LocalDateTime格式变成一串数字宠物健康档案里存了疫苗接种时间的LocalDateTime字段未做序列化处理时前端拿到的数据是类似于birthTime: 1716800000000的时间戳数字。解决方案是在application.yml配置全局的JackSon序列化规则spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8同时给实体类的LocalDateTime字段加上JsonFormat(pattern yyyy-MM-dd HH:mm:ss)注解。注意time-zone必须设置为GMT8否则传回前端的时间会差8个小时。问题三文件上传大小限制宠物商城需要上传多张商品图片如果前端上传大图会直接报MaxUploadSizeExceededException。Spring Boot默认上传文件大小上限只有1MB。需要手动调整spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB图片存储方面不建议把图片直接存到数据库BLOB字段更推荐的方式是上传到服务器本地目录或对象存储服务数据库只存访问URL。本地存储需要配置一个静态资源映射把/images/**映射到服务器磁盘目录Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/images/**) .addResourceHandler(file:/app/upload/); } }6.3 这个项目后续可以怎样扩展如果你做完以上内容还有余力我建议从三个方向做扩展这三个方向在答辩或者面试时都能成为加分话题。第一个是WebSocket实时通信。宠物商城非常适合在线看宠这个场景。买家在下单之前想视频看猫的状态运营在后台发起一个视频会话前端WebSocket就能接收到会话链接。Spring Boot集成WebSocket并不复杂配置一个WebSocketConfigurer注册WebSocketHandler即可核心要点是握手阶段通过token完成身份认证以及心跳检测维持连接。第二个是对接Spring Boot Admin做实时监控。在pom.xml引入spring-boot-admin-starter-server和spring-boot-admin-starter-client就能以可视化的方式看到项目的堆内存、线程状态、HTTP接口耗时等信息。这些数据在演示系统健壮性时非常有说服力。第三个是第三方接口集成。如果你希望项目更像一个真实可运营的系统可以考虑对接真实的物流查询API比如快递鸟、快递100实现订单物流轨迹展示或者对接阿里云短信服务发送发货通知。这个扩展的核心在于学会第三方接口外置的设计思路——将外部接口调用封装成独立的Service通过接口定义隔离外部依赖这样即使第三方服务不可用也不会阻塞主业务流程。做这类项目最大的体会是一个看起来简单的商城网站真正深入进去会发现每一个环节都有设计决策要做。Spring Boot的价值在于它帮你把框架层的复杂度降到最低让你能够把精力集中在真正的业务难点上——这正是我从这个项目里收获最大的地方。先把主交易链路做扎实再逐层叠加亮点功能你会发现这个题目能给到你的成长远比想象中多。
返回列表