简介:这是一套基于SpringBoot构建的网上书店毕业设计资源包,面向Java初学者、毕业设计学生及电商项目开发者,旨在提供从需求分析、系统设计、编码实现到部署维护的完整参考。压缩包共1787个文件,大小约31.49MB,内部以java源码、vue组件、js脚本、css样式、html页面、xml配置及sql数据库脚本为主,同时包含class编译文件与备份文件,目录结构清晰,便于按前端、后端、数据库分模块研读。项目采用MVC分层架构,业务上覆盖图书管理、用户订单、安全登录等典型电商场景;数据库文档细致讲解了表结构设计、主外键关系、索引优化以及事务与并发控制,前端整合Vue/JavaScript/Thymeleaf技术,后端则借助Spring Security实现用户认证与授权,知识点覆盖面广。附带Maven构建相关文件,可辅助本地环境配置与项目启动调试。目前已有59人浏览学习,适合作为毕业设计选题参考,也是深入学习SpringBoot电商开发的实用实践教材。
1. 从压缩包到在线书店:先看清这 25 个字母在讲什么
拿到基于springboot网上书店源码数据库文档.zip这个压缩包,第一反应不该是解压然后双击一个什么文件,而是要先建立对这批交付物的心智模型。网上书店是典型的 CRUD 密集型业务:前台要图书展示、搜索、购物车、下单支付,后台要图书管理、订单处理、客户管理,几乎所有 Java 后端的核心知识点都能在这一类项目里找到落点。而标题里的 "springboot" 决定了技术栈的主干,Spring Boot 的自动配置、Starter 依赖管理、内嵌容器让这类中小型单体应用变得异常容易起步,但也正因为上手门槛低,大量项目在分层、异常处理、事务边界上偷工减料。拿到源码后你首先要确认的是:项目打包形式是 Maven 还是 Gradle,JDK 版本要求,依赖里有没有 MyBatis-Plus 还是原生的 MyBatis,前端是模板引擎还是前后端分离——这三个问题决定了你接下来怎么读这份代码。
这类压缩包的受众主要分两类:一类是做毕业设计或课程设计的学生,需要快速跑通并修改成自己的项目;另一类是刚转 Spring Boot 的开发者,想借助一个完整案例理解业务系统怎么组织。无论哪种意图,核心需求是一致的——在尽量短的时间内让项目在本地跑起来,然后能看懂、能改、能说清楚每个模块在干什么。对五年以上经验的开发来说,这份代码的信息量更多在数据库设计中索引是否合理、库存扣减有没有用乐观锁分页、订单号生成策略是否具备并发安全这些边界问题上,我们要带着这些疑问去拆包。
2. 数据库是网上书店的地基:先看 SQL 脚本和 E-R 图
2.1 图书、用户、订单这几张核心表的字段设计要抓住哪些点
网上书店的数据库设计通常不会少于六张表:用户表、图书分类表、图书表、购物车表、订单表、订单详情表,如果涉及后台管理员还会加一张管理员表。打开数据库目录下的 SQL 脚本,MySQL 和 SQL Server 版本往往同时提供,README 里一般会写推荐用哪个,没有写的话选 MySQL 5.7 或 8.0 即可,因为 Spring Boot 的默认数据源配置对 MySQL 支持最友好。
典型用户表的核心字段包括user_id、username、password、email、phone、status。密码字段要留意是用 MD5 加盐还是 BCrypt,如果是前者,在安全上已经落后,不过作为课设项目用 MD5 的不少见,你自己心里要有数。图书表是整库最复杂的表,字段通常有:book_id、book_name、author、publisher、isbn、price、stock、sales、cover_image、description、category_id。这里值得你多看两眼的是:category_id和图书表之间有没有建外键或索引,stock和sales是单独两个字段还是用公式推导,直接决定了并发下单时数据一致性方案怎么写。
订单表的设计最容易暴露一个问题——它是把多个图书 ID 直接拼成字符串塞进一个字段,还是拆成独立的订单详情表。规范做法是后者:订单表order_id、order_no、user_id、total_price、status、create_time、pay_time,订单详情表detail_id、order_id、book_id、book_name快照、price快照、count。快照的意思是下单那一刻的名称价格会冗余进来,防止后续改价影响历史订单。
2.2 初始化数据怎么组织:建库、插数据、测试账号三条主线
看初始化脚本时要按这个顺序去读,不要跳着看。第一步是CREATE DATABASE IF NOT EXISTS bookstore这类建库语句和USE bookstore,有些脚本还会帮你设置字符集为utf8mb4。第二步是建表语句,每个CREATE TABLE后面注意看ENGINE=InnoDB和DEFAULT CHARSET=utf8mb4,如果看到的还是MyISAM,说明脚本要么年代偏早要么是从别的版本改过来的。第三步是INSERT INTO初始化数据,除了图书测试数据外,重要的是管理员账号和用户账号的初始记录,通常在admin_user表和user表里。
-- 请先在 MySQL 里执行这段初始化,下面以管理员表为例 CREATE DATABASE IF NOT EXISTS bookstore DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE bookstore; CREATE TABLE admin_user ( admin_id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT '密码,默认是 123456 的 MD5', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; -- 网上书店密码用 MD5 存储很普遍,下面 123456 的密文只用于开发环境 INSERT INTO admin_user (username, password) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e');初始化数据的执行命令在 Windows 和 Linux 上略有差异,但道理一样:要把 SQL 文件路径写对,用重定向导入,而不是进入 MySQL 后用source时路径写错找不到文件。Windows 上常见的写法是mysql -uroot -p123456 < bookstore.sql,在 PowerShell 里重定向符号可能被解释成字符串格式问题,建议换成cmd /c或在 MySQL 客户端内部执行source。导入完成后,用SHOW TABLES;确认所有表都生成了。
2.3 网上书店的表关联索引是性能体检的重点
表都建好后,用下面这段 SQL 检查一下索引覆盖情况,这是在文档里最容易遗漏的内容之一:
SHOW INDEX FROM `book`; SHOW INDEX FROM `orders`;上线环境里会员搜索图书、根据分类翻页是高频查询,所以book表的category_id和book_name应该有索引。如果只有主键索引,数据量过了五万就会明显变慢,读取时全表扫描,这类项目在展示阶段看不出问题,但答辩或演示时一旦把热门关键词嵌进LIKE '%xxx%'查询里就可能被问住。另外orders表的user_id和create_time要建联合索引,否则用户订单列表查询效率会很低,这类细节在源码注释里一般不会写明白,得自己从EXPLAIN SELECT的走索引情况里判断。
3. 拿代码说话:网上书店从 Controller 到 Mapper 的完整数据流
3.1 Spring Boot 项目目录分层的三种经典摆法
解压源码后先看整体结构。你一定会遇到这三层基础目录:控制器层、服务层、数据访问层,不同项目的包名可能略有差异,比如controller/service/mapper/entity或web/service/dao/model。网上书店项目里,BookController负责接收请求和参数校验,BookService处理业务逻辑,BookMapper或BookDao面向数据库操作。
Spring Boot 满身注解,这些注解的语义在阅读时最容易混。在 Controller 层你经常会看到@RestController、@RequestMapping("/book")、@GetMapping、@PostMapping、@PathVariable这几个注解的组合变化。在 Service 接口实现类上,@Service注解和@Transactional出现得频率极高,其中@Transactional写在哪个方法上、传播行为如何设置,直接决定库存事务回滚的粒度。在数据访问层,如果是 MyBatis 就用@Mapper扫描或 XML mapper 文件,如果是 Spring Data JPA,你会看到JpaRepository<Book, Long>这类接口。
// 一个典型的网上书店图书分页查询 Controller 方法,参数从前端传来当前页和每页条数 @RestController @RequestMapping("/book") public class BookController { private final BookService bookService; // 构造器注入,网上书店项目里推荐这种写法,避免 @Autowired 字段注入的问题 public BookController(BookService bookService) { this.bookService = bookService; } @GetMapping("/list") public Result list(@RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "12") int pageSize, @RequestParam(required = false) String keyword) { return Result.success(bookService.getPage(pageNum, pageSize, keyword)); } }这段代码里的Result是常见统一返回值封装类,包含code、message、data三个字段,前端根据code判断请求是否成功并能显示message。pageNum和pageSize都加了默认值,避免前端没传参数时直接空指针。result.success()是静态工厂方法,相当于构造了一个响应对象交给前端渲染层,页面拿到之后是走 Vue 渲染还是 Thymeleaf 模板渲染完全取决于前端选型。
3.2 从搜索到下单:业务状态流转是理解项目源码的最佳线索
网上书店源码最核心的线索是订单状态字段的流转过程。新建订单、待支付、已支付、已发货、已签收、已取消这几种状态,在数据库里通常会以int类型存一个枚举值,少数项目会用字符串常量来存。阅读代码时,你可以在entity/Order或entity/Orders这类实体类上直接看到status字段,而改造项目时,注意翻一下状态常量定义用的是public static final int STATUS_CREATE = 0还是单纯散落在业务代码里的魔法数字,后者通常是源码中可维护性最弱的地方,可以做一点整理。
用户下单的核心操作是把当前用户的购物车条目拿出来,计算总价,逐个检查库存,减库存后生成订单,并同时生成订单详情。整个流程要求在一个事务里完成,不进事务就会出现库存扣了但订单没生成,或者订单生成了但库存不一致。在源码里对应@Transactional注解加在 checkOut 方法或 OrderService 实现类的 createOrder 方法上。
@Override @Transactional(rollbackFor = Exception.class) // 这里必须指定 rollbackFor,因为默认只回滚 RuntimeException public Order createOrder(Long userId) { // 1. 拿到用户的购物车列表,没有商品抛一个自定义业务异常 List<CartItem> cartItems = cartMapper.findByUserId(userId); if (cartItems == null || cartItems.isEmpty()) { throw new BizException("购物车是空的,无法下单"); } // 2. 计算总价,同时校验每个商品当前的库存是否充足 BigDecimal totalPrice = BigDecimal.ZERO; for (CartItem item : cartItems) { Book book = bookMapper.selectById(item.getBookId()); if (book.getStock() < item.getCount()) { throw new BizException("《" + book.getBookName() + "》库存不足"); } totalPrice = totalPrice.add(book.getPrice().multiply(new BigDecimal(item.getCount()))); } // 3. 创建订单(状态 0 表示待支付) Order order = new Order(); order.setOrderNo(generateOrderNo(userId)); order.setUserId(userId); order.setTotalPrice(totalPrice); order.setStatus(0); orderMapper.insert(order); // 4. 生成订单详情,然后扣减库存 for (CartItem item : cartItems) { OrderDetail detail = new OrderDetail(); detail.setOrderId(order.getId()); detail.setBookId(item.getBookId()); detail.setBookName(item.getBookName()); detail.setPrice(item.getPrice()); detail.setCount(item.getCount()); orderDetailMapper.insert(detail); bookMapper.decreaseStock(item.getBookId(), item.getCount()); } // 5. 清空购物车 cartMapper.clearByUserId(userId); return order; }代码顺序有讲究:先校验再创建,最后扣库存。如果把扣库存放在创建订单之前,任何原因导致后续逻辑抛异常,在@Transactional内都会回滚,但库存字段被锁的时间变长了,高并发场景下不是一个好习惯。还要注意generateOrderNo()这个私有方法的关键性,如果直接用时间戳当订单号,在演示时连续下单没有任何观感问题,但压测时同一毫秒内两个线程取到了相同时间戳,这就会造成订单号冲突,网上书店项目里最常用的替代方案是时间戳 + 用户ID + 随机数或直接依赖数据库自增主键。
3.3 购物车 session 持久化和库存扣减的深水区
购物车实现分两种:一种把购物车数据放 Session,另一种持久化到购物车表。网上书店项目源码里这两种都有,前者数据库设计更简单、启动更快,但用户关浏览器后购物车就丢了;看法是持久化到表里更符合真实电商习惯。这里要关注cart表在代码里的主键策略:是user_id + book_id联合主键,还是独立的自增 ID。如果是独立 ID,那判断同一个用户加同一本书是否应该加数量而不是新增记录的逻辑,就落在 Service 层的 createOrUpdate 方法里。
库存扣减是这类项目另一个深水区。源码里如果出现UPDATE book SET stock = stock - #{count} WHERE book_id = #{bookId} AND stock >= #{count},说明作者对并发扣减留了心眼,通过 SQL 层的条件来保证库存不小于零,而非先查询库存再在 Java 里做比较再更新。这一段是答辩时的高频提问点,值得仔细看。
<!-- 网上书店 MyBatis 的 Mapper XML 中,这种 UPDATE 写法具备最基础的防超卖能力 --> <update id="decreaseStock"> UPDATE book SET stock = stock - #{count}, sales = sales + #{count} WHERE book_id = #{bookId} AND stock >= #{count} </update>这里stock >= #{count}是关键,它在数据库层面拦截了库存不足的情况,如果更新行数为 0 就说明没有库存,Service 层需要根据返回值判断是否抛异常。如果源码里只是SELECT stock再UPDATE book SET stock = #{newStock},虽然功能能跑通但并发下超卖风险很高,改造时要优先替换掉。
3.4 网上书店拦截器链:登录校验从来不是 Controller 的职责
网上书店分为前台用户区和后台管理区,这决定了登录校验必然要区分不同路径。源码里一般会有一个WebMvcConfigurer配置类实现addInterceptors方法,把拦截器注册成两条链:/admin/**必须是管理员,/cart/**、/order/**必须是登录用户。它的原理是 HandlerInterceptor 在 Controller 方法执行之前调用preHandle方法,从 Session、Cookie 或 Token 里取出登录对象放到ThreadLocal或attribute中,然后后面的 Controller 方法直接读取,这比每个方法都去session.getAttribute要舒服得多。
有一类常见误区是把用户登录对象反复从数据库里查询后新对象就往 Session 里塞,用户改了个昵称后 Session 里的还是旧昵称。我建议你在改造时留意 Session 里的登录对象是否被做成了不可变 DTO,在用户表设计里email、phone这些字段是否有适合放进 Session 的必要,同时权限判断的参数尽量用HandlerMethod上的自定义注解配合拦截器完成。
路径说明表 /admin/** 需要管理员角色,通常检查 session 中 admin 对象是否存在 /user/** 需要客户登录,未登录的跳转到 /login /book/* 公开访问,无需登录也能看目录和详情 /cart/** 需要登录,购物车必须关联用户 /order/** 需要登录,订单查询只允许本人订单4. 拿到项目后让它跑起来:配置改动点、启动参数与常见报错
4.1 从 yml 到数据库连接:五个要逐个核对的配置位
网上书店项目解压后进入src/main/resources,application.yml或application.properties是启动前必看的文件。最重要的几个改动点按影响从大到小排列:datasource 的 URL、username、password 三项;mybatis 的 mapper-locations 路径;server.port 端口号;文件上传路径 base-path(涉及图书封面上传);日志配置文件的输出路径。
# application.yml 中数据源与 MyBatis 的基础配置,合起来是网上书店能连上数据库的最小集 server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/bookstore?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.bookstore.entity configuration: map-underscore-to-camel-case: trueuseSSL=false是因为本地开发环境没有配置 SSL 证书,否则 MySQL 8.x 服务可能会让连接报一条警告;serverTimezone=Asia/Shanghai解决了 MySQL 驱动版本高于 6.0 时默认时区导致的 8 小时时间差问题。如果你的数据库是 MySQL 5.7 且驱动是旧版本,那com.mysql.jdbc.Driver和com.mysql.cj.jdbc.Driver的区别就要提前弄清楚,前者会直接导致无 valid connection 的错误。map-underscore-to-camel-case: true把数据库下划线字段自动映射到 Java 的驼峰属性,网上书店的字段明明create_time,实体类是createTime,不开启这个开关查询结果会大量为 null。
4.2 Maven 构建和首次启动的确认清单
网上书店打包成 zip 解压后是一个 Maven 工程,项目根目录下至少有一个pom.xml。通过 IDE 导入时选择 File 然后 New Project from Existing Sources,告诉 IDE 这是 Maven 项目,让它自己去下载依赖。如果本地 Maven 仓库里没有 Spring Boot 的相关包,下载耗时很长,这时候你需要确认pom.xml中的 Spring Boot 版本和 JDK 是否兼容。举个例子 Spring Boot 2.7 用 JDK 8 或 11 完全没有问题,但如果项目用的是 Spring Boot 3.x,JDK 必须是 17 以上;这点在源码附带的文档里不一定提醒,靠 IDE 的编译报错就能发现。
首次启动遇到启动失败的常见排行榜第一是数据库连接失败(错误码开头是 Access denied 或者是 Communications link failure),第二是端口被占用,第三是 mapper XML 里的 SQL 语法错误。如果启动不了看不到任何日志,去target目录或 IDE 控制台看是否少了编译产物。用 Maven 命令行吹一下哨子:
# 在项目根目录执行,定位到 pom.xml 的同级目录下 mvn clean package -DskipTests # 构建成功后运行 java -jar target/bookstore-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev构建的过程中 Maven 会在编译阶段执行 MyBatis 的 mapper XML 校验,SQL 写错往往会直接体现在这里。-DskipTests跳过测试能省时间但也屏蔽了一些测试对数据源的依赖问题。项目本身是一个打包成可执行 jar 的 Spring Boot 应用,内嵌了 Tomcat,部署服务器时不需要额外装容器。启动完成看到Started BookstoreApplication in x.xx seconds那条日志,然后在浏览器访问http://localhost:8080确认首页正常渲染。
4.3 Spring Boot 版本太高导致的兼容性坑怎么识别
网上书店课设项目因为作者写作时间不同,pom.xml里的 Spring Boot 父级版本从 2.1.x 到 3.2.x 都可能有。对版本太高的常见吐槽集中在两点:一套代码从 2.x 升到 3.x 后很多第三方 starter 没有及时适配,尤其是 MyBatis 的mybatis-spring-boot-starter要升级到 2.3.x 以上才能兼容 Spring Boot 3,否则启动就报ClassNotFoundException: SqlSessionFactoryBean;另一个是javax.servlet迁移到了jakarta.servlet,导入过来源里的HttpServletRequest从javax换成jakarta才能编译通过。确认依赖冲突最快的方法是执行:
mvn dependency:tree -Dincludes=org.mybatis.spring.boot观看输出结果里是否存在两个版本的 mybatis starter。如果存在并且其中一个和 spring-boot-starter-parent 版本不匹配,最直接的解决办法是把 mybatis starter 提升到与 Spring Boot 3 配套的 3.0.x 版本,这个版本的官方坐标前缀也对应做了调整。用dependency:tree看依赖树比盯报错信息猜要快得多。
4.4 初始化账号和权限配置的验证路径
启动成功后第一件事是登录。网上书店的后台管理通常有独立的登录入口,路径一般是/admin/login或/admin/index.html,默认管理员账号在数据库里的状态如果是status = 1才允许登录,有少数项目status = 0表示启用,这取决于字段注释。前端商城登录入口是/login,普通用户在user表里。验证登录是否成功不要只看跳转,要故意用一个不存在的账号试一次,观察服务端返回的Result对象的code是不是 500 或 业务码,如果前端永远只显示请求失败且后端控制台打出了空指针,那逻辑在用户查询为空时没有做兜底处理,这一处代码要修改。
验证清单 1. 访问 http://localhost:8080/book/list 能看到图书列表 JSON 还是 404 2. 通过 /admin/login 输入 admin/123456 后,能否进入后台首页 3. 后台图书管理页面能不能正常上新书,封面上传是本地文件路径还是 Base64 4. 前台注册一个新用户,下单、支付、查到订单详情,整个链路是否完整 5. 使用一个未登录用户直接访问 /order/list,观察是否被拦截器跳回登录页5. 社区版与课设版的差距:四个增强点和一个避坑手段
5.1 密码从 MD5 升级到 BCrypt:改造 Spring Security 还是只换加密算法
网上书店源码里如果是 MD5 存储,那密码的强度就等同于明文。更稳妥的升级路线不需要引入完整的 Spring Security 造成大量配置成本,而是自己动手把加密工具类替换为BCryptPasswordEncoder,并给旧用户写一个顺势迁移逻辑:用户成功登录后用 BCrypt 对新密码重新加密并回写数据库。这样改动面小,数据迁移也平滑。
// 网上书店项目把 MD5 换成 BCrypt 的最小改法:定义一个新的 PasswordEncoder Bean import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class PasswordUtil { // 项目内统一使用这个对象做加密和校验,注意它是线程安全的 private static final BCryptPasswordEncoder ENCODER = new BCryptPasswordEncoder(); // 登录校验时调用:确认明文密码与数据库中保存的哈希是否匹配 public static boolean matches(String rawPassword, String encodedPassword) { return ENCODER.matches(rawPassword, encodedPassword); } }改动代码的地方是三处:注册用户的加密方法、登录校验比对方法、管理员后台创建子账号的方法。原有 MD5 数据的用户会在首次登录时被matches判成 false,然后代码里应该加一步兼容判断:如果数据库里密码是 32 位 MD5 且和输入密码的 MD5 相等,就顺手迁移,用 BCrypt 覆盖写回。
5.2 引入拦截器粒度更细的操作日志:不做 AOP 也能记录敏感动作
网上书店源码里最重要的功能除了买书外,管理员的图书上下架、修改库存、删除用户都属于敏感操作,如果不留操作日志,出事之后溯源很困难。一种做法是在管理端的 Controller 方法里重复调用logService.record("修改了图书", bookId),但这侵入性太强容易忘;常见做法是用一个自定义注解@OperLog("updateBook")打在 Controller 方法上,通过 HandlerInterceptor 统一捕获注解信息并记录当前管理员登录态。这个方案的边界是拦截器只能拿到 HandlerMethod 上的注解内容,如果日志需要打印被修改实体前后的值,必须用 AOP 环绕通知自己去取参数。
// 自研操作日志的最小框架:一个注解 + 拦截器钩子 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface OperLog { String value() default ""; }然后在拦截器里判断handler instanceof HandlerMethod之后调用getMethodAnnotation(OperLog.class),非空则把注解内容和 Session 中管理员 ID 写入日志表。日志表字段不要忘了ip_addr,从请求头获取时要注意反向代理环境下X-Forwarded-For是可能被伪造的,仅作为辅助信息。
5.3 分页查询的坑:前端传 pageNum 从 1 开始还是 MyBatis 从 0 开始
网上书店的图书列表页面几乎都用到了分页。PageHelper 插件使用挺广,源码里可能已经在 Service 层这样调用:
PageHelper.startPage(pageNum, pageSize); List<Book> list = bookMapper.selectList(); PageInfo<Book> pageInfo = new PageInfo<>(list); return pageInfo;PageHelper 的startPage后面必须紧跟着一条查询语句,中间不能掺杂其他 SQL 操作。pageNum在 PageHelper 中从 1 开始,但有的项目封装的分页返回类又自己做了一次减一操作,前端显示就是从 2 开始才有数据。源码排查时你把断点打在startPage前后看传参就能定位。另外用 PageHelper 的时候 count 查询会自动生成,如果图书表关联了分类字典表,count 子句的自动生成是可能会导致丢条件的,遇到总数不对时改用自定义 count 查询更稳妥。
5.4 前后端分离部署的 Session 失效问题
如果前端是 Vue 独立出来的,那整个项目包内多半有一个dist或front目录,构建产物由 Spring Boot 的static目录托管。这时浏览器和后端服务是同源的,Session 机制还能用,但如果开发时前端跑在 8080 端口、后端跑在 9000 端口,浏览器的 Ajax 跨域请求默认不会带上登录后种下的 Session Cookie,开发者常写的@CrossOrigin只解决了响应头跨域问题,并没有解决凭证传递问题。要在application.yml里显式配置服务端的 CORS 允许凭证:
# 后端允许特定前端域名跨域访问,并同意携带 Cookie,网上书店前后端分离模式下不可缺少的配置 server: port: 8080 servlet: session: cookie: http-only: true same-site: lax还有一个容易被忽略的小坑是spring.servlet.session.cookie.path,如果前端访问路径和后端服务路径不在同一个 context-path 下,Cookie 携带就处处受阻。对于复杂的分离架构,建议直接给项目引入spring-boot-starter-data-redis做 Session 共享,重启服务后用户登录态也不丢,这也贴近生产环境部署时的实践。这套方案在 Spring Boot 里的配置非常可靠:加上@EnableRedisHttpSession注解,把序列化策略调整为 JSON 之后,Session 由内嵌的 Tomcat 内存提升到 Redis 里,后续不管是单机重启还是水平扩容都多了一层保障。
本文还有配套的精品资源,点击获取