简介:本资源是一套完整的基于Spring Boot开发的生鲜电商交易系统课程设计项目源码,面向Java初学者与高校计算机专业学生,解决生鲜商品在线展示、多角色协同管理及订单流转等核心业务场景。压缩包共912个文件,涵盖171个Java后端逻辑类、164个JavaScript前端交互脚本、162个SVG图标资源、61个HTML页面模板及59个Vue组件,辅以CSS样式、SQL建表语句与配置文件(yml/properties),整体大小为51.82MB,结构清晰,前后端分离明确。已有115人学习下载,资源包含管理员、商家、普通用户三端完整功能模块:管理员可进行生鲜分类、仓库、出库及广告全生命周期管理;商家支持商品上架、订单处理与库存操作;用户端实现注册登录、购物车、收藏、评论与地址管理等电商基础能力。预览可见bat启动脚本、Vue页面备份文件及标准Maven工程结构,开箱即用,适合作为Java Web综合实训或毕业设计参考范例。 看到这个项目标题,我第一反应是:这不就是把普通商城系统换个商品分类再卖一遍吗?真正上手去做才发现,生鲜交易系统里面的坑,比想象中多不少。这个项目以Spring Boot作为技术底座,覆盖了商品、购物车、订单、库存、支付、配送、营销这些电商核心链路,同时因为类目是生鲜,又额外承载了保质期管理、损耗控制、同城履约这类特殊需求。如果你正打算拿Spring Boot做一个实战项目,或者课程设计、毕业设计的题目已经锁定了类似方向,这篇内容应该能帮你少走很多弯路——我会把整个系统的设计思路、核心实现、部署方式以及我踩过的坑,从头到尾梳理一遍。
1. 生鲜交易系统的需求到底是什么
1.1 生鲜类目和普通电商的差异
刚开始我确实低估了“生鲜”这两个字的重量。普通电商卖衣服、卖数码产品,库存单位就是“件”,发快递可以等两三天,售后问题相对简单。生鲜不一样,它有几个很明显的特征:第一,商品有保质期,同样的商品,不同批次的生产日期不同,卖出去的货要能追踪批次;第二,损耗率极高,库存和实际可售数量之间经常有偏差,采购入库、门店调拨、顾客退款都会影响库存;第三,配送时效要求苛刻,通常要求当日达或者次日达,下单之后往往需要系统自动匹配最近的门店或前置仓;第四,很多生鲜商品是按重量卖的,比如“500g±50g”,而订单结算又要按实际重量来,这就导致价格模型比普通电商复杂。
所以生鲜交易系统不能简单套用商城模板。我在做设计的时候,把核心需求拆成了四块:商品中心要支持多单位换算和批次属性,订单中心要处理预售、整单退款和部分退款,库存中心要做可售库存和实际库存分离,履约中心要按区域和门店配送。这四块做好,系统骨架基本就立住了。
1.2 技术选型背后的取舍
既然标题已经锁定了Spring Boot,技术栈其实没有太多悬念,但选型还是要讲道理。对于这类业务复杂度中等偏上的系统,Spring Boot是当前Java后端最稳的选择,它内置了Tomcat,简化了依赖管理和配置,让你能把更多精力放在业务代码上。数据库方面,我选的是MySQL 8.0,因为生鲜交易涉及大量事务操作,订单、库存、资金这种数据强一致性的要求,用MySQL加InnoDB事务比NoSQL更踏实。缓存用Redis,主要处理热点商品、购物车、验证码和分布式锁。前端配合Vue做前后端分离,接口通过RESTful风格提供。
这里多说一句选型时的取舍。很多人纠结要不要上微服务,把订单、商品、用户全拆开,再用Spring Cloud Alibaba那一套。我个人的建议是:如果项目规模到不了百万级用户,单体应用配合模块化分包,开发效率反而最高,排查问题也更简单。Spring Boot的单体架构不是落后,而是对绝大多数毕业设计和中小型项目来说,足够用且好维护。微服务带来的是部署复杂度和分布式事务成本,这个代价在初期是完全没必要的。Spring Boot和Spring Cloud的区别,面试的时候讲讲可以,真正落到代码上,先做一个漂亮的单体比什么都强。
2. 搭建Spring Boot工程,版本和依赖是第一道坎
2.1 Spring Boot版本到底怎么选
做这个项目时,我第一个踩的坑就是Spring Boot版本。新项目刚创建,IDEA默认拉到的是3.x的最新版,但Spring Boot 3.x要求Java 17或更高,很多机房机器的JDK还停留在1.8,一部署就报错。如果你的开发环境和部署环境都受控,那用3.x没问题;但如果要兼容老机器、老同事的习惯,Spring Boot 2.7.18其实更稳,它是2.x系列最后一个版本,该修的Bug都修完了,稳定性很高。
版本选择这件事,我列一个简单的对照表:
| 场景 | 推荐版本 | JDK要求 | 说明 |
|---|---|---|---|
| 新项目全面现代化 | 3.2.x / 3.3.x | JDK 17+ | 长期支持,适合新环境 |
| 兼容老系统、JDK 8 | 2.7.18 | JDK 8+ | 稳定收尾版,依赖生态成熟 |
| 学习源码、调试机制 | 源码随意 | 按版本匹配 | 建议从2.x读起,更直观 |
通过这个表格可以看出,版本的坑往往不是框架本身的坑,而是环境匹配的坑。IDEA新建项目的时候,如果没有对应版本选项,可以先去Spring Initializr官网生成项目包再导入,或者检查一下IDEA的Spring Boot插件版本是不是太旧。另外,Spring Boot版本一旦确定,相关依赖尽量用BOM统一管理,不要混着试,版本太高和太低都会出现诡异的问题。
2.2 pom.xml和组织结构怎么搭
工程创建之后,pom.xml是第一步。基础依赖至少要包含spring-boot-starter-web、spring-boot-starter-data-jpa或mybatis-spring-boot-starter、spring-boot-starter-data-redis、mysql-connector-j,以及lombok、validation。有人喜欢用JPA,有人喜欢用MyBatis Plus,我自己的习惯是MyBatis Plus,因为在单表CRUD和分页查询上确实省事,复杂SQL又能自己写XML控制。
目录结构我推荐按模块分包,而不是按技术职责分包。简单说,就是不要搞一堆controller、service、mapper的顶层包,而是按功能域拆成user、product、order、cart、inventory、delivery、payment这种包,每个包下面再放controller、service、mapper、entity、dto。这样做的最大好处是,改订单功能的时候你只需要打开order包,所有相关文件都在一个文件夹里,不用满项目跑。Spring Boot对项目结构没有硬性要求,但优秀的代码组织方式能让你后期维护省下大量时间。
配置文件我用的是application.yml加多环境profile的方式:
spring: profiles: active: dev --- spring: config: activate: on-profile: dev datasource: url: jdbc:mysql://localhost:3306/fresh_market?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456生产环境和开发环境分开,部署的时候通过启动参数--spring.profiles.active=prod切换,不要在代码里硬编码环境。这个习惯越早养成越好,不然每次上线都要改一遍连接串,迟早出事故。
2.3 常用注解在业务中的正确姿势
Spring Boot的常用注解,面试时那些都是要背的,但真正写代码时,有几个注解最容易用错。@RestController和@Controller的区别,很多新手会搞混,@RestController就是@Controller加@ResponseBody,如果用的是@Controller,方法返回值会被当成视图名称去解析,接口直接返回String时就会出现奇怪的路径跳转问题。@Service、@Repository、@Component三个注解功能上没有本质区别,都是把Bean交给Spring容器管理,但语义上要区分清楚:业务层用@Service,数据访问层用@Repository,工具类、配置类用@Component。@Autowired和@Resource的区别也值得注意,@Autowired按类型注入,@Resource按名称注入,当容器里存在多个同类型Bean时,@Autowired要搭配@Qualifier指定名称。
这里有一个比较容易被忽略的坑:@Transactional注解在同类内部方法调用时会失效。比如你在GoodsService里有个方法A调用了同类的方法B,B上面标了@Transactional,B的事务不会生效,因为事务代理只对从外部进入Service类的方法生效,内部调用走的是this引用,绕过了代理。解决方式很简单,把B抽取到另一个Service里,或者用AopContext.currentProxy()。这个坑在所有Spring项目里都很常见,生鲜系统的库存扣减和订单创建恰恰是强事务场景,用错了会造成库存不一致,而且是那种很难排查的逻辑错误。
3. 核心业务:从商品到订单再到履约
3.1 生鲜商品模型与库存设计
生鲜商品模型第一件事是SKU设计。普通商城一个SKU就是“颜色+尺码”,生鲜则是“规格+单位+产地+批次”。举个例子,同一个苹果,可以按“500g一份”和“5kg一箱”两种规格售卖,价格不同、库存不同,下单时使用的最小单位也不同。我在设计数据库时,product表放公共信息,sku表放具体售卖规格,batch表放生产批次和保质期,库存表则精确到“sku_id + batch_id”维度。这样设计的好处是,出库的时候可以按批次先进先出,快过期的商品能优先卖掉。
库存设计上,我把库存字段拆成了total_stock(总库存)、locked_stock(锁定库存)、sold_stock(已售库存)三个字段,可售库存就是total_stock减去locked_stock。用户下单时先锁定库存,支付成功才真正扣减,支付超时或取消订单则释放锁定。这个流程对生鲜特别重要,因为生鲜商品的进货和损耗决定了库存总量是有限的,锁库存能防止“超卖”和“超卖后无货可发”的尴尬。
3.2 下单流程与超卖问题
下单是整个系统最核心的链路:用户从购物车选中商品、提交订单、生成订单号和订单明细、锁定库存、清空购物车,然后跳转支付。这个链路必须保证原子性,不然就会出现库存扣了订单没生成、或者订单生成了库存没扣的问题。我在实现时给整个下单方法加了@Transactional,同时把库存扣减SQL写成条件更新的形式:
UPDATE sku_stock SET locked_stock = locked_stock + #{count}, total_stock = total_stock WHERE sku_id = #{skuId} AND total_stock - locked_stock >= #{count}这样写的好处是,数据库层面的行锁能天然防止超卖,不用先查再改。如果更新影响行数为0,就说明库存不足,直接抛出业务异常,订单回滚。
不过这里还有一个细节:在高并发下,同一件热门生鲜商品被大量用户同时下单,数据库行锁会让请求排队,性能会下降。为了进一步提升吞吐,我引入了Redis预扣库存的方案:先把商品的可售库存加载到Redis,下单时先Lua脚本扣减Redis库存,扣减成功后再异步落库。这样Redis的原子操作能扛住瞬时压力,数据库只要消费队列慢慢更新就行。这个方案虽然多了一层复杂度,但面对秒杀和限时抢购场景时效果明显,这也是我在优化阶段做得最值得的工作。
3.3 订单状态机与自动取消
订单状态不能放在一张表的status字段里随便改,最好设计成状态机,生鲜交易的订单状态我定义成:待支付、已支付、备货中、配送中、已完成、已取消、售后中。每个状态定义允许的转移路径,比如“待支付”只能去“已支付”或“已取消”,“已支付”不能直接跳到“已完成”。实现方式不需要引入复杂的状态机框架,用一张状态流转表记录“当前状态+操作事件+目标状态”,配合一个校验方法就够了。
生鲜订单还有一个特殊需求:超时自动取消。因为生鲜商品的保鲜属性,如果用户下单后一直不支付,库存被白白锁定,会影响其他真正要买的用户。我实现了一个延迟队列机制:下单后把订单号放到Redis的ZSet中,score设为支付截止时间戳,用一个定时任务每分钟扫一次,把过期的订单捞出来执行取消。Redis的ZSet完美适合这种延迟消息场景,比用数据库轮询效率高很多,代码也不复杂。实际上,如果公司有条件,直接用RocketMQ的延迟消息更优雅,但对于这个项目的体量,ZSet方案已经绰绰有余了。
4. 缓存、事件与异常处理的细节实战
4.1 Redis缓存与数据一致性
Spring Boot整合Redis其实是老生常谈,但用好的关键不在整合,而在缓存策略。生鲜系统里,商品详情页和首页的销量榜单是热点数据,几乎每个用户打开App都会访问,每次都查MySQL,压力太大。我是这样设计的:商品基础信息和库存信息缓存到Redis,缓存key用“product:info:{id}”和“product:stock:{skuId}”,设置缓存过期时间为30分钟,并采用“先更新数据库,再删除缓存”的模式。
这个模式有一个经典问题:在删除缓存之前,可能有并发请求读到了旧缓存。要解决也很简单,给缓存设置一个很短的过期时间兜底,比如5分钟,就算极端情况下短暂出现脏数据,也能在几分钟内自愈。对于库存这种实时性要求极高的数据,我干脆不做缓存,或者只用Redis做预热和预扣减,真正的扣减以数据库为准。经验总结就是:缓存不是越多越好,越关键的数据越要谨慎,宁可多查一次库,也不要把库存数据搞错了。
4.2 事件机制:让订单创建不再耦合
Spring Boot的事件机制是我很喜欢的一个设计,它能让代码解耦,维护起来特别舒服。比如用户支付成功后,系统需要同时做这几件事:更新订单状态、通知商家备货、给用户发消息、扣减库存、记录日志。如果在支付回调方法里把所有逻辑串在一起写,一个地方出问题,整个链路都卡住,代码也会膨胀到几百行。用事件机制,支付成功后只发布一个OrderPaidEvent,然后各个监听器自己去处理分内的事,主方法只负责把订单状态改掉。
具体实现也不复杂,定义一个事件类继承ApplicationEvent,或者直接使用Spring 4.2之后支持的事件类加@EventListener注解。异步处理的话,再给监听方法加@Async注解,并启动类加上@EnableAsync。我在项目里还做了一个细节:事件监听内的业务失败不能影响主流程,所以监听器内部都有try-catch,失败后记录错误表,由定时任务补偿处理。这个套路在很多商业项目里叫“事件驱动加最终一致性”,在Spring Boot里用起来门槛很低,效果却很好。
4.3 全局异常处理器与统一返回体
后端接口如果每个方法都自己写try-catch,代码会非常难看。Spring Boot提供了@RestControllerAdvice注解,可以写一个全局异常处理器,把所有异常统一收口。我的做法是:定义统一的返回体Result ,包含code、msg、data三个字段,所有接口都返回这个结构;全局异常处理器里分三类处理——业务异常(BusinessException)返回具体的错误码和提示,参数校验异常(MethodArgumentNotValidException)返回第一个校验失败信息,其余系统异常则统一返回“系统繁忙,请稍后再试”并打印完整堆栈。
这个设计的好处是,前端联调时只要解析一种返回结构,处理逻辑简单许多。还有一个容易被忽视的点:异常处理器里不要把异常堆栈直接返回给前端,这属于安全漏洞,会给攻击者提供系统信息。我还在响应头里加了X-Content-Type-Options等安全头,防止基本的XSS和内容嗅探攻击。有面试经验的同学应该知道,这部分内容在面试中也是常客,属于看着简单但能体现代码功底的地方。
5. 与前端联调:Vue、Swagger和文件上传
5.1 前后端分离联调准备
这个项目我选了Vue配合Spring Boot做前后端分离,接口访问自然就有跨域问题。开发环境下,我在后端配置了CORS全局跨域规则,允许本地的Vue开发服务器地址访问。上线后,我推荐用Nginx做反向代理,前端请求统一走Nginx,再由Nginx转发到后端接口,这样既可以解决跨域,也能顺带做负载均衡和静态资源缓存。
接口对接还有一个规范问题。前后端分离项目最怕的就是接口字段名不一致、类型对不上,所以Swagger基本是标配。Spring Boot集成SpringDoc或者springfox都行,我在项目里用SpringDoc生成OpenAPI 3.0文档,启动项目后访问/swagger-ui.html,前端同事能直接看到所有接口的参数、返回结构,直接在线调试。接口文档这步不是可选项,而是省时间的关键,尤其是接口几十个的时候,没有Swagger你会被问疯的。
关于接口安全,除了登录用Token校验,还有一个经验是给接口调用方提供API Key机制。比如小程序端、管理后台、第三方配送平台,它们的调用方式不同,可以用在Header里传递X-Api-Key的方式做认证,避免和普通用户Token混淆。实现上就是写一个拦截器,对特定接口路径校验API Key是否在白名单内。这个方案简单可靠,适合内部系统间对接,不需要引入OAuth2那么重的框架。
5.2 文件上传下载与资源映射
生鲜系统里的文件上传场景不少:商品主图、资质证明、配送凭证图片,偶尔还有导出的订单明细Excel。Spring Boot上传文件的接口比较好写,但要注意几个问题:一是文件大小限制,默认1MB太小,上传大图片会直接报错,需要在配置里调整spring.servlet.multipart.max-file-size和max-request-size;二是文件存储位置和管理,不要直接存到数据库里,存到服务器的指定目录,并记录文件的相对路径到数据库。
资源映射这词看着高级,其实就是配置文件上传目录的访问路径。在Spring Boot中,默认只有static目录下的静态资源能通过URL直接访问,其他目录不行,需要写一个配置类继承WebMvcConfigurer,重写addResourceHandlers方法,把磁盘目录映射到URL路径。比如:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceHandler("file:" + uploadPath + "/"); }这样就能通过http://localhost:8080/upload/xxx.jpg访问上传的图片了。下载大文件时,我建议用InputStreamResource进行流式传输,避免把整个文件读进内存导致OOM。
5.3 接口文档:Swagger/OpenAPI 3
Swagger集成本身不复杂,但使用体验差异很大,关键是注解要写到位。类上写@Tag(name = "商品管理"),方法上写@Operation(summary = "分页查询商品列表"),参数上写@Parameter(description = "页码"),DTO上用@Schema标注字段含义。这些信息填写得越完整,前端同事用起来越舒心,接口文档的可用性完全取决于注释的质量。
我还做过一个小优化:开启Swagger的环境限制。生产环境不允许访问Swagger页面,因为会暴露所有接口信息,容易成为攻击者的信息收集目标。实现方法是在配置文件中设置springdoc.api-docs.enabled=false,或者用profile控制环境。见过不少项目在生产环境开着Swagger,这是很低级的安全风险,顺手就堵上了。
6. 部署运维:从Linux到Docker再到CI/CD
6.1 Linux上直接跑jar包
项目开发完成,最终要部署到服务器上。最简单的部署方式是把项目打成jar包,通过mvn clean package -DskipTests打包,然后把target目录下的jar上传到Linux服务器,执行:
java -jar fresh-market-server.jar --spring.profiles.active=prod这个运行方式适合快速验证,但不适合长期挂机,因为一旦关掉终端,进程就没了。更稳的做法是用nohup加&:
nohup java -jar fresh-market-server.jar --spring.profiles.active=prod > app.log 2>&1 &日志重定向到app.log,方便排查问题。再狠一点,可以写一个systemd服务文件,做到开机自启、崩溃自动重启,比手动启动靠谱得多。
但是要注意,jar包直接跑对服务器的环境要求比较高,JDK版本、内存大小、磁盘空间都要先确认。如果服务器内存只有1G,Spring Boot项目启动就可能很吃力,所以在部署前我会先用java -Xmx128m -Xms64m限制JVM堆内存,避免因为内存不足被杀掉。
6.2 Docker化部署
如果不想在服务器上装JDK、MySQL、Redis那一堆环境,Docker是更好的选择。我写了一个简单的Dockerfile:
FROM openjdk:17-jdk-slim LABEL maintainer="yourname" WORKDIR /app COPY target/fresh-market-server.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]配合docker-compose.yml,可以一次性把MySQL、Redis、应用容器都编排起来。这样在一台新服务器上部署整套系统,只需要执行docker-compose up -d,环境一致性也有了保障,不会再出现“我本地好好的,部署到服务器就挂了”的问题。K8s部署和Docker的原理类似,不过引入了Pod、Service、ConfigMap这些概念,如果项目使用者多、并发量大,才建议走K8s,否则单机Docker足够。
6.3 Jenkins + Gitea实现自动打包部署
手动上传jar包这种方式,项目迭代几次之后就会让人崩溃。后来我配置了Jenkins加Gitea的CI/CD流程:本地代码推送到Gitea仓库的master分支,Jenkins监听仓库变化,自动拉代码、执行Maven打包、构建Docker镜像、推送镜像到镜像仓库、再在服务器上拉取镜像并重启容器。整个流程跑通后,发布新版本只需要本地git push,剩下的交给流水线。
Jenkins配置的关键点是流水线脚本。我用的是Jenkinsfile,核心阶段就是Checkout代码、Maven编译、Docker构建、Docker部署。踩过的一个坑是:构建机和服务器的Docker环境如果不在同一台机器,需要配置Docker远程访问或者使用Docker registry中转。如果只是单机部署,直接让Jenkins在本机执行docker-compose up -d就完事了。
项目做完之后,如果想学习更多运维监控,可以引入Spring Boot Admin,它能查看应用的健康状态、内存使用、线程情况,还能在线查看日志。Spring Boot Admin的部署非常简单,服务端建一个独立Spring Boot项目引入spring-boot-admin-starter-server,客户端引入spring-boot-admin-starter-client并配置服务端地址即可,两行配置就能看到完整的应用体检报告。
7. 常见问题排查与高频面试题
7.1 高频问题与排查方式
我在开发这个项目的过程中,整理了一份常见问题排查表,能帮大家省去不少排查时间:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| 启动报端口被占用 | 8080端口被其他进程占用 | 用netstat -ano查占用进程,改配置或杀进程 |
| MySQL连接超时 | 数据库服务没启动或URL写错 | 检查服务状态,核对连接串和用户名密码 |
| 接口返回500但日志无堆栈 | 全局异常处理把所有异常吞了 | 在全局异常处理器里打完整日志 |
| Redis连接失败 | Redis服务没开启或密码错误 | 检查Redis进程,确认密码和配置 |
| 上传文件报超限 | Spring Boot默认1MB限制 | 调整multipart配置 |
| 打包后找不到配置文件 | 配置文件在src/main/resources外 | 把配置放到resources目录或打jar时指定路径 |
还有一个我印象很深的坑:Spring Boot项目启动正常,但访问接口时所有请求都404了。排查了很久,发现是Controller类上的@RestController注解被注释掉了,扫描不到这个Bean。这种问题最坑的地方是不报错,只表现为接口不可用,排查思路是先看日志里有没有加载对应的Controller,再用Actuator的mappings端点检查已注册的URL。
7.2 Spring Boot面试几连问
如果这个项目你要拿去面试,以下几个问题几乎必问,提前准备一下有好处。
Spring Boot启动流程是怎样的?回答的核心在于SpringApplication.run()方法:先创建SpringApplication实例,确定Web应用类型,加载ApplicationContextInitializer和ApplicationListener,然后准备Environment,打印Banner,创建ApplicationContext,执行refresh()完成Bean的创建和自动配置。对这个流程熟悉了,很多问题都能串起来。
Spring Boot常用注解有哪些?这个不用死背,按功能分模块讲:核心注解是@SpringBootApplication,它由@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan组合而成;Web层用@RestController、@RequestMapping;业务层用@Service;数据层用@Repository;参数校验用@Validated;条件装配用@ConditionalOnProperty等。边讲边结合你项目里的具体使用方式,比列一堆注解名字更有说服力。
Spring Boot与Spring Cloud的区别是什么?一句话概括,Spring Boot是构建单个微服务的工具,Spring Cloud是将多个微服务做治理和协调的解决方案。打个比方,Spring Boot是建房子,用水泥钢筋把房子盖起来;Spring Cloud是给小区做水电、物业、安保,让每栋房子之间能有序协作。在合适的场景选合适的工具,这是面试官最想听到的。
如何解决Redis缓存与数据库一致性?这是一个开放题,我通常回答三步走:优先使用Cache Aside模式,先更新数据库再删除缓存;对极端一致性要求高的数据不缓存;删除失败时用消息队列重试,或者设置短过期时间兜底。
7.3 信创环境兼容提示
这个项目做完后,如果你有信创环境部署的考虑,需要提前关注一下中间件适配问题。信创环境通常采用国产化的应用服务器、数据库和操作系统,比如东方通TongWeb替代Tomcat、达梦数据库替代MySQL、麒麟操作系统替代CentOS,这里面涉及的不只是Spring Boot的事,还有JDBC驱动、Servlet容器、文件路径规范等细节。Spring Boot项目从Tomcat切换到TongWeb,理论上大多数项目改动量不大,但还是要做迁移验证,尤其是部署描述符、Session机制、类加载冲突这几个点。
我在实际验证中总结的经验是:写代码的时候尽量别依赖具体中间件的特性,比如不要直接用Tomcat的API,不要假设连接串只能走默认端口,数据库层面用标准的JDBC和SQL语法,这样以后做信创迁移时会有更多余地。对于学习型项目,不需要在一开始就引入国产化适配,但要有意识地在架构上留出替换空间。
8. 个人经验:几个值得再优化的方向
这个项目做完,我自己最大的感受是“单体应用也能做出很完整的电商味道”。再往后扩展的话,可以试着加入更多实战元素:引入RocketMQ处理订单超时和异步通知,替代定时扫描Redis的方案;引入Elasticsearch做商品搜索,解决MySQL模糊查询效率低的问题;引入XXL-Job做定时任务调度,替代Spring自带的@Scheduled,因为分布式部署时谁执行任务需要协调。这些扩展方向,每一个单独拿出来都值得再写一篇博客。
最后再分享一个写项目时的体会:尽量把每一个核心业务都写成能在本地独立启动和调试的完整模块,并且养成随手补日志的习惯。生鲜交易这类系统,链路长、状态多,一旦出问题,日志就是唯一的破案线索。我在项目里用logback做了按天滚动和日志级别分级,接口入参和出参都打了debug日志,上线后排查问题真的能救命。这些习惯比某个具体的框架用法更重要,也会在复盘时成为你真正的技术积累。
本文还有配套的精品资源,点击获取