前后端分离这四个字,很多搞Web开发的朋友已经听过无数次了,但真正动手把一套完整的系统从零搭起来,还是会遇到一堆文档里查不到的坑。我最近用SpringBoot+Vue+MyBatis+MySQL这套主流组合,写了一个电子产品销售系统,涵盖了商品展示、购物车、订单管理、后台管理等完整的电商闭环。这篇文章会把这套系统的设计思路、核心接口实现、前端页面构建、联调部署全过程拆开讲清楚,尤其把操作中容易翻车的细节都标记出来,适合正在学SpringBoot和Vue、想找一个完整项目练手的人,也给打算做前后端分离项目的同学一个可以直接参考的样板。
这套项目我用的是经典的三层架构加RESTful API设计:后端SpringBoot提供纯接口服务,前端Vue负责页面渲染和用户交互,MyBatis作为持久层框架操作MySQL。整套代码量不算大,但麻雀虽小五脏俱全,从用户登录态管理到商品分页查询、从购物车数据持久化到订单状态流转都有涉及。读完这篇文章,你能对整个前后端分离项目的开发流程建立完整认知,也能拿到一份可以直接放到简历上的实战经验。
1. 技术选型:为什么是SpringBoot+Vue+MyBatis+MySQL
1.1 前后端分离的核心逻辑
先聊清楚什么叫前后端分离,以及为什么这套项目要这么做。传统的Java Web开发用JSP或者Thymeleaf,后端把数据塞进模板里,由服务器渲染成HTML再返回给浏览器,前端代码和后端逻辑搅在一块,改个页面样式往往要重新打包整个应用。
前后端分离的思路是把前端和后端看成两个独立的应用。前端用Vue这类框架自己管理页面路由和数据渲染,通过AJAX请求调用后端的JSON接口获取数据,两边只通过HTTP协议通信。这样做的好处非常直接:后端团队专注于接口的稳定性和业务逻辑,前端团队专注于页面交互和用户体验,互不干扰。而且同一套后端接口理论上是通用的——以后要做App、小程序,接口很可能不需要改。
当你真正把项目拆成两个工程分别启动时,你会意识到一个事实:前后端分离带来的不只是架构上的清晰,更是开发模式上的解耦。前端开发不再依赖后端环境的启动,后端调试也不再需要关心页面上某个按钮的点击事件。这也是当前企业级项目普遍采用这种模式的核心原因。
1.2 四个核心组件的角色分工
这套系统里的四个技术组件,每一个承担的角色都非常明确,缺一个都要出问题。
SpringBoot是整个后端服务的地基。它基于Spring框架,但通过自动配置大幅简化了传统Spring项目的繁琐配置。以前用SSM(Spring+SpringMVC+MyBatis)搭项目,要写一堆XML配置文件:数据源配置、事务管理配置、MyBatis的SqlSessionFactory配置等等。SpringBoot把这些都自动化了,只需要在application.yml里写上数据库连接信息,框架自己会把该建的对象建好。我选SpringBoot还有个重要原因:它内置了Tomcat,打包成jar后直接java -jar就能运行,部署成本极低。
Vue在前端负责所有页面逻辑。这套项目中我用的是Vue 2搭配Element UI组件库。Vue的核心优势是响应式数据绑定:你只需要维护好JavaScript里的数据对象,页面上的展示会自动跟着变化,不用像jQuery那样手动操作DOM。对于销售系统这种数据交互频繁的场景,Vue能明显减少代码量。
MyBatis是数据库操作层的关键工具。它做的事情本质上是把Java方法和SQL语句关联起来:你定义一个接口方法,在XML文件里写对应的SQL,MyBatis负责把参数传进去、把查询结果映射成Java对象。相比JPA的全自动ORM,MyBatis更灵活可控,复杂的多表查询写起来更直观,尤其适合这种表结构相对清晰的业务系统。
MySQL负责数据存储。电子产品销售系统涉及用户、商品、订单、购物车等结构化数据,MySQL这种关系型数据库天然适合。配合Navicat或命令行工具做数据管理,开发效率很高。
这套组合经过了大量生产环境验证,稳定性有保障,社区资料也多,遇到问题时几乎都能搜索到对应解法。
2. 系统功能拆解与数据库设计
2.1 功能模块怎么划分
从一个产品销售系统的实际业务出发,我先把功能拆成了两个大端:用户端和管理端,再按照角色细分模块。
用户端面向普通消费者,核心功能有四个。账号模块负责注册与登录,登录成功后签发Token,后续请求都要带着这个凭证;商品模块负责展示电子产品列表,支持按分类筛选和关键词搜索,列表要分页展示;购物车模块允许用户把商品加入购物车,修改数量,勾选后生成订单;订单模块则记录了用户下单的商品明细、收货信息、订单状态,用户可以查看自己的订单列表和详情。
管理端面向运营人员,功能相对集中。商品管理需要实现新增、编辑、上下架操作;分类管理维护商品的所属类目;订单管理可以查看全部订单,修改订单状态,比如把待发货改成已发货;用户管理查看注册用户列表并支持禁用恶意账号。
把需求摸清楚之后才能开始建表,否则字段遗漏后面非常难受。这个确认需求的过程,我建议花两三天时间仔细对一遍,比代码写了一半再改表结构划算得多。
2.2 数据库表设计的关键细节
整个系统的数据表我设计了六张,每一张表的字段都对应实际业务。
用户表user用来存储账号信息,字段包括id主键、username用户名、password密码、nickname昵称、phone手机号、create_time创建时间。密码字段要存加密后的密文,我用的BCrypt加密,验证密码时用加密工具比对,千万不能明文存密码,这是最基本的安全红线。
商品表product存放电子产品信息,字段有id、name商品名称、category_id所属分类、price单价、stock库存、image封面图、description详情描述、status上下架状态。价格字段用DECIMAL类型,避免浮点运算精度问题。库存字段在后续的购买逻辑里非常关键,下单时要扣减库存,要防止超卖。
分类表category就简单很多,id和name两个核心字段,再加一个sort排序值。电子产品可以分成手机、电脑、耳机、配件等类目,用户端首页按分类入口进入商品列表。
购物车表cart_store和订单相关的表要说明一下。购物车表字段包括id、user_id、product_id、quantity数量、checked是否选中,一个用户对应多条购物车记录。订单表orders包含订单编号order_no、用户ID、总金额、收货信息、订单状态等。订单详情表order_item记录每个订单下的具体商品快照——商品名称、单价、数量、图片。为什么要做快照?因为商品信息是随时可能变的,价格调整了或者商品下架了,已生成的订单不能跟着变,所以要冗余一份当时的商品信息,这是电商系统里很常见的做法。
2.3 表关系与业务闭环
六张表之间的关系并不复杂,但要在设计阶段就理清楚。
用户表和购物车表是一对多关系,一个用户可以有多个购物车条目;用户表和订单表是一对多关系,一个用户可以下多个订单;商品表和分类表是多对一关系,多个商品属于同一个分类;订单表和订单详情表是一对多关系,一个订单对应多个商品明细。
在设计时我特别留意了外键的使用。实际开发中我通常不物理建外键约束,而是依靠代码逻辑保证数据一致性。原因很简单:外键约束会降低数据库的写入性能,而且在分库分表场景下外键基本不可用。只要业务代码里做好关联删除和逻辑校验,不建外键是更符合生产实践的选择。
还有一个设计要点就是订单编号。用户下单时生成唯一订单号,我用的是时间戳加用户ID加随机数的组合,保证并发场景下不会重复。如果直接使用数据库自增ID当订单号,很容易被猜到业务量,也不够安全。
我在写建表语句时习惯把字符集统一设为utf8mb4,这个编码支持emoji和一些特殊字符,避免用户输入某些生僻字或者特殊符号时出现乱码。排序规则选择utf8mb4_general_ci就够了,对中文排序要求高的场景可以选utf8mb4_unicode_ci,不过一般项目用不上。
3. 后端接口开发:SpringBoot+MyBatis的实战要点
3.1 后端工程结构与初始化
后端项目的包结构我按照标准的三层架构来组织:controller层负责接收前端请求并返回数据,service层处理业务逻辑,mapper层通过MyBatis与数据库交互。model包放实体类,config包放配置类,common包放统一返回结果和异常处理。
项目初始化直接用Spring Initializr,通过start.spring.io生成基础工程,勾选Web、MyBatis、MySQL Driver依赖。不过这里有个小坑:不同版本的SpringBoot对MyBatis Starter的支持略有差异,我用的SpringBoot 2.7.x对应mybatis-spring-boot-starter 2.3.x,版本选不对会出现启动报错或者Mapper扫描不到的情况。
统一返回结果的设计值得单独说一下。前后端分离项目中,前端需要知道接口是成功还是失败,错误信息是什么。我定义了一个Result类,包含code、message、data三个字段。成功时code为200,业务失败时code为自定义的错误码,比如参数错误400、未登录401。前端拿到响应后先判断code,再决定走正常流程还是弹出错误提示。如果没有这个统一结构,前端的错误处理代码会写得支离破碎。
还有全局异常处理,用@RestControllerAdvice注解配合@ExceptionHandler,捕获所有异常并转成标准格式的Result。如果不做这一步,框架抛出的默认异常信息会直接返回给前端,既不友好也可能暴露内部细节。我第一个版本就漏了全局异常处理,前端拿到500错误时只有一堆堆栈信息,排查问题非常痛苦。
3.2 核心接口实现流程
后端最关键的两个流程,一个是登录鉴权,一个是购物车到订单的流转。登录我用的是JWT(JSON Web Token)+拦截器方案。用户登录成功后,后端签发一个包含用户信息的Token返回给前端,前端存储下来并在后续请求的请求头中携带,后端拦截器校验Token有效性。
JWT的用法其实不难:引入java-jwt依赖,登录时生成Token,拦截器里解析Token拿到用户ID放到请求上下文。需要处理的核心问题是Token过期和异常Token的响应方式,要让前端在Token失效时自动跳回登录页而不是一直报500错误。拦截器中如果校验失败,我直接返回401状态码和一个明确的JSON提示。
商品列表和搜索其实最容易出问题的是分页。用MyBatis分页最方便的方式是引入PageHelper插件,在查询前执行PageHelper.startPage(pageNum, pageSize),紧跟其后的查询语句自动被拦截并加上LIMIT。要注意的是PageHelper只对紧随其后的第一条查询生效,所以千万别在startPage和查询之间插入其他数据库操作,否则分页条件就错位了。页面上还需要返回总条数和总页数,我用PageInfo对象封装分页结果,一次全给前端。
购物车加入订单的流程是这样的:前端提交一个包含商品ID和数量列表的请求,后端拿到后校验库存是否充足,计算总金额,生成订单记录和订单详情记录,最后扣减库存。这三个操作必须放在同一个事务里,只要有任何一个环节失败,整个订单都不能生成。我用的方式是给service方法加@Transactional注解,如果抛异常就回滚。顺序上要注意先扣库存再生成订单还是先生成订单再扣库存,本质上没有绝对标准,但我的习惯是先检查库存,再插入订单,最后更新库存,这样失败时库存不会被误扣。
3.3 MyBatis的坑与实用写法
MyBatis最核心的是XML映射文件,但如果Mapper接口和XML文件放错位置或者命名空间不对,启动时就会直接报错。SpringBoot项目里,我习惯把这些XML文件放在resources/mapper目录下,然后在application.yml里配置mapper-locations: classpath:mapper/*.xml。Mapper接口放在com.xxx.mapper包,接口名和XML文件名必须一致,这是MyBatis的硬性约定。
动态SQL是MyBatis最强大的功能。商品列表的查询条件不是固定的:用户可能按分类筛选、按关键词搜索、按价格区间过滤。我使用 标签配合 标签拼接SQL,MyBatis会自动处理多余的AND关键字,这也是我最推荐的条件查询写法。
另一个值得记录的是批量操作。管理端需要批量上下架商品,前端的做法是传一个商品ID数组,后端就需要通过MyBatis实现批量更新。在XML中,用 标签拼接IN条件或者批量UPDATE语句。数量不多的时候直接用拼接的方式没问题,但如果批量数据量很大,就要考虑分批处理或者是用CASE WHEN的方式。这里我给个最稳妥的实现:
<update id="batchUpdateStatus" parameterType="map"> UPDATE product SET status = #{status} WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </update>参数传递上有个容易踩的坑:如果你在Mapper接口方法上使用了@Param注解指定参数名,XML中引用参数时必须用注解指定的名字,否则会占位符对不上。这说明Mapper接口的参数命名规范很重要,不要依赖编译期保留参数名,显式声明更安全。
最后说MyBatis的缓存。MyBatis自带一级缓存和二级缓存,一级缓存默认开启且作用范围是SqlSession,但在Spring整合环境下SqlSession每次请求都会重建,所以一级缓存实际用处不大。二级缓存需要在Mapper XML中显式配置,但因为缓存的是对象引用,在多线程下可能会读到脏数据,我不建议在电商这种数据一致性要求高的系统里开启。让数据库自己管数据是最让人放心的方式,缓存这种优化手段等真到了性能瓶颈再考虑也不迟。
4. 前端构建:Vue页面与状态管理
4.1 从环境准备到项目初始化
前端开发的第一步是搭好Node.js环境。Node.js装好之后npm包管理命令才能用。我遇到过不少新手卡在这一步,注意Node.js版本不能太老也不能太新,Vue 2项目建议使用Node 16或者18的LTS版本,太新的版本有时会有依赖兼容问题。
项目创建我用的是Vue CLI方式。特别提醒一下,如果你在命令行执行vue create my-shop-frontend时发现命令不存在,说明没有全局安装Vue CLI,先执行npm install -g @vue/cli安装即可。初始化过程中可以选择手动配置功能,勾选Router和Vuex,CSS预处理器选Less或者直接不用,按需选择即可。
项目目录里src下会有几个关键目录:router放路由配置,store放Vuex状态管理,views放页面组件,api目录放所有和后端交互的请求封装。我习惯把axios请求单独封装成一个request.js文件:创建axios实例、设置baseURL、添加请求拦截器注入Token、响应拦截器中统一处理错误码。
4.2 路由配置与登录态控制的联动
路由配置要做的事情不只是把路径和组件对应起来,更重要的是控制访问权限。一套销售系统里,后台管理页面绝对不允许未登录的人随便访问。
我的做法是在路由定义里给需要登录的页面加meta属性,里面放一个requiresAuth: true标记。然后在Axios实例里再加一层路由守卫,用beforeEach钩子检查用户要访问的页面是否需要登录,需要的话再看Vuex里有没有Token,没有Token就强制跳到登录页。这样用户手动在地址栏输入管理端地址,也会被弹回登录页。
Vuex管理全局状态的重点是用户信息和Token。用户登录成功后,把Token和用户昵称存到Vuex里,同时写入localStorage。页面刷新时Vuex数据会丢失,需要在应用启动时从localStorage读取并恢复状态——在App.vue的created生命周期里执行一次初始化操作。这个环节逻辑不复杂,但漏掉的后果很直接:用户刷新一下页面就掉登录了,体验很糟糕。
4.3 核心页面的实现思路
商品列表页是用户进入系统后的主页面,我采用栅格布局,一行展示四个商品卡片。每个商品卡片展示图片、名称、价格和加入购物车按钮。数据来源就是在api目录中调用后端接口获取商品分页数据,把返回的list渲染到页面上。Loading状态要处理好,接口请求时显示加载动画,请求失败时显示错误和重试按钮,不然用户会误以为页面卡住了。
购物车页面相对复杂一些,因为需要处理多选、数量增减、金额计算这些交互。关键技巧是使用Vue的computed计算属性:选中商品的总金额不用手动在每次操作后重新计算,只要把计算逻辑写在computed里,依赖的数据一变化,金额自动刷新。这就是Vue响应式系统带来的开发效率提升,如果用原生JS写这部分逻辑,代码量会成倍增长。
管理端的商品管理表单页面我用了Element UI的Form组件,配合它的校验规则实现必填项检查。编辑和新增共用一个表单组件,通过传入不同的初始数据区分模式。这里有一个小技巧:打开编辑弹窗时,把row对象直接交给表单会导致修改弹窗内容时,表格里那一行的数据同步变化,因为两个对象引用的是同一个地址。所有需要新增和编辑共存的表单,一定要浅拷贝一份数据,推荐用JSON序列化的方式实现深拷贝。
// 数据深拷贝,避免直接赋值导致的数据联动 const formData = JSON.parse(JSON.stringify(this.getRowData)) this.form = formDataElement UI的表格组件用起来很顺手,自带分页组件和排序功能,后端返回的分页数据只需绑定到对应属性上即可。有一点要记住:表格里显示的字段如果后端返回的字段名和前端期望的不一致,要么让后端改字段名,要么在前端用formatter函数处理,不要在前端页面里做复杂的字段重新组装。
5. 前后端联调与部署上线
5.1 跨域问题怎么干净地解决
前后端分离项目联调遇到的第一个拦路虎就是跨域。前端跑在8080端口,后端跑在8081端口,浏览器直接请求就会报CORS错误。这里的原理是浏览器的同源策略:只有当协议、域名、端口完全一致时,浏览器才允许请求访问。
解决方案有两条路。第一条是在开发环境下用Vue CLI的proxy代理:在vue.config.js中配置devServer.proxy,把/api开头的请求转发到后端地址。这样浏览器认为请求是同源的,因为请求发给了前端自己的8080端口,由前端开发服务器转发到后端。这是开发阶段最推荐的方式,配置一次就不用再管了。
第二条是在后端配置支持跨域。SpringBoot中可以用@CrossOrigin注解加在Controller类上,也可以写一个WebMvcConfigurer配置类统一配置跨域规则。但生产环境下一般不这么干,因为前后端通过Nginx反代时是同一个域名,不存在跨域问题,开着跨域配置反而多一道安全风险。最好的实践是:开发环境用前端代理,生产环境用Nginx统一转发,后端压根不用开CORS。
5.2 后端打包部署的完整流程
后端部署可以聊的不算复杂,但每一步都有它的必要性。首先用Maven的package命令打jar包,执行mvn clean package -DskipTests跳过测试,构建产物出现在target目录下。SpringBoot内置的Tomcat让部署少了很多麻烦,直接传jar包到服务器,执行java -jar productsystem.jar就能运行。
生产环境的数据库连接配置不能直接写在代码里,我通常用application.yml里的profile机制区分开发和生产配置。SpringBoot支持通过spring.profiles.active参数动态指定使用哪份配置:打包时默认用开发配置,部署时启动命令加上--spring.profiles.active=prod,就会加载application-prod.yml里的生产配置。生产配置里把数据库地址换成服务器的MySQL地址,数据库密码用环境变量注入,避免明文写在代码仓库里。
服务器上Java进程要保证可持续运行。最基础的方式是使用nohup java -jar命令加上&符号让进程后台运行,但这种方式在服务器重启后有风险。我用的方案是把启动命令写成一个Shell脚本,服务器重启后手动执行脚本即可。更进阶一点的可以用systemd配置成系统服务,开机自启动,但这些后面项目上线时再优化也来得及。
端口和防火墙也要提前确认。后端端口部署在服务器上,需要确保安全组和防火墙放行该端口,否则外部访问不到。如果端口被占用,使用lsof命令先查一下端口是被哪个进程占用,再决定清理进程还是换端口。
5.3 前端打包与Nginx托管
前端部署比后端简单很多。在项目根目录执行npm run build,Vue CLI会先进行代码检查、然后打包压缩,生成一个dist目录,里面是静态文件——一个HTML入口和一堆JS、CSS文件。这些文件不依赖Node环境,只需要一个Web服务器托管即可。
生产部署最常用的是Nginx。把dist目录上传到服务器的某个路径下,然后修改Nginx配置:前端页面文件用root指令指向dist目录,所有API请求用location配置转发到后端服务。Nginx的配置文件配置好后,执行nginx -t检查语法,然后nginx -s reload重载配置生效。
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这段配置里有两处关键。第一处是location /api/,把所有API请求转发给本机8081端口的后端服务,解决了生产环境的跨域问题。第二处是try_files指令,指定如果用户直接访问某个子路由路径如/product/123,恰好这个文件不存在,就返回index.html让前端路由接管处理。如果不配置这一行,刷新页面的时会出现404,因为Nginx找不到那个路径对应的实际文件。
6. 常见问题与排查技巧实录
6.1 项目启动阶段的经典报错
数据库连不上是我遇到频率最高的一个问题。刚配好MySQL时,后端启动一直报Communications link failure或者Access denied。排查时要分清是网络问题还是认证问题:先用命令在服务器本机执行mysql -u username -p试试能不能连上,能连上说明网络没问题,再确认用户名密码和授权情况。MySQL 8.0之后的认证插件跟旧版本驱动有兼容性问题,如果用的MySQL 8.0但JDBC驱动是5.x,就会报Unknown character set或者加密集群错误,解决办法是升级mysql-connector-java到8.x并配置serverTimezone参数。
中心还有一个频繁踩坑的点是时区问题。MySQL 8.x默认时区和中国时区不同,不设置的话数据库的时间字段和Java程序的时间会有时差。在JDBC连接串中加上serverTimezone=Asia/Shanghai和useSSL=false,这两个参数建议固定写上。
端口冲突也有很多人遇到。执行java -jar启动时如果提示端口被占用,先查一下那个端口是不是被其他服务占了。可以用netstat -ano命令查看端口占用情况,找到对应进程PID后强制结束才可以重新启动。开发期间后端前端一起跑,端口选择上尽量错开:后端用8081,前端用8080,一眼能分清。
6.2 MyBatis相关的非常经典的坑
第一个MyBatis高频坑是XML文件没有编译到classpath目录。Maven项目默认只把resources下的文件打进classpath,如果XML文件放在src/main/java目录下,打包时会直接被忽略。解决的方案早就确定了:XML放resources/mapper目录下,并在pom.xml的build节点里配置resources资源路径,确保XML被包含进jar包。另一个相关的问题是修改XML文件后没生效,这是因为项目一直跑着旧版本的class,需要先clean再重新编译。
第二个高频坑是参数传递丢失。用@Param指定参数名之后,XML里的#{xxx}名称必须和它完全一致,写错一个字母就会报BindingException。这个问题排查起来很费劲,我犯过一次把参数名写错,日志只提示找不到参数,根本不知道从哪里查起,后来把日志级别改成DEBUG,看到MyBatis输出的Preparing语句才发现占位符不存在。遇到绑定相关的异常,先检查Mapper接口的@Param注解和XML中的参数引用是否完全对应,这个检查胜过看日志猜半天。
第三个问题是动态SQL里的字段名写错。MyBatis默认开启驼峰命名映射后,数据库列名下划线转Java属性驼峰。但如果你没有开启map-underscore-to-camel-case这一项,查询返回的create_time字段无法映射到createTime属性,全部是null,这个问题的特征很奇怪——数据明明有值,Java对象里就是取不到。我建议在application.yml中把该项配置设为true,配合统一的命名规范,能省掉不少麻烦。
6.3 前后端联调阶段的典型问题
404错误是最常见的联调问题。前端请求的URL和后端Controller的映射路径不一致就会出现这种情况。解决时先把浏览器开发者工具打开,看请求URL和实际后端接口路径差在哪,别急着改代码。如果后端用的路径是/product/list但前端请求的是/products/list,一字之差就是完全不同的接口,或者后端地址端口改了前端配置文件没同步更新,类似的问题听上去低级但反复出现。
JSON格式对不上的问题也值得一提。后端返回字段是createTime,前端期望的是createdAt,或者后端返回的日期是一串时间戳,前端不知道怎么显示成日期字符串。最理想的是在前后端约定接口文档时就统一这些约定,但实际项目中往往是后端先行开发,然后前端拿字段名做适配。好消息是现在前后端对接已经比较标准化,Restful风格和JSON字段命名一般使用驼峰式,只要遵循这个惯例,这类的对接成本会低很多。
另外还有Token失效后的状态处理。前端的请求拦截器在后端返回401时需要做两件事:清除本地的Token和用户信息,并且跳转到登录页。如果不处理,用户会看到一个长时间卡在加载状态的页面或者报错弹窗,而实际原因只是令牌过期。现在的实现是在响应拦截器里统一捕获401错误导向登录页,用户重新登录后还能接着用之前收藏的商品,体验才过得去。
6.4 部署环境的运行期问题
服务器上跑起来以后,遇到的问题是前端页面能打开但接口全部请求失败。这个现象几乎都是Nginx转发配置不对引起的:检查Nginx的error.log日志,如果看到connect() failed,说明proxy_pass指向的后端地址不通,先确认后端进程是否还活着,再检查地址端口是否填写正确。
Tomcat的线程池配置也是一些较深的坑。高并发情况下如果后端接口处理太慢,大量请求会堵塞在容器线程池,表现为所有接口响应都变得缓慢,即使轻量级请求也排队。SpringBoot中可以通过server.tomcat.max-threads参数调整线程池大小,对话时要结合实际场景调整,没有一个适用于所有项目的固定值。我一般先保持默认,压测后再调整。
数据库连接池的配置也会出问题,尤其是并发场景下连接数不够会抛异常。HikariCP是SpringBoot默认的连接池,它的配置就写在application.yml里。maximum-pool-size默认值比较保守,如果QPS较高建议调大一些,但也不能盲目调很大,因为每个连接都会占用数据库资源,太大会适得其反。要注意对核心接口做压测,看峰值的连接数需求量,再设置一个安全余量。
我在实际操作中的体会是,排查这些问题的关键要先弄清楚报错发生在哪一层——是浏览器请求没发出,还是Nginx转发失败,还是后端接口本身报错,还是数据库语句执行出错。从上到下逐层排查,效率会高很多,而不是东看一眼西看一眼地浪费时间。
这套项目做下来,最大的感受是前后端分离真的不只是写了两个工程那么简单,它倒逼你把接口设计、数据格式、部署方案全部提前想清楚。遇到问题不要慌,大多数坑都是网上别人踩过的,只是你还没搜到关键词。最后再分享一个小技巧:所有和数据库相关的字段、接口相关的路径,前后端开发过程中一定要做一次书面约定,哪怕只是写在项目README里,也能让你少熬好几个小时的夜。