
去年帮一个学弟改课设他是用JSPServlet写了个图书管理系统功能倒是有但代码全堆在一个Servlet里光登录逻辑就写了三百行。我一边改一边想现在出去面试简历上写JSP已经没有太多竞争力了。后来我干脆自己动手搭了一套“SpringBootVue 图书电子商务网站管理平台”既给他做参考也顺手把前后端分离的完整流程梳理了一遍。这个项目名字虽然长骨架拆开其实就是那么几样东西SpringBoot写后端接口Vue写前端页面MySQL存数据再用JWT做登录态管理。今天把这套东西从选型、建表、写接口到联调部署的完整过程拿出来说说特别适合马上要做毕设或者课设、但还没想清楚怎么下手的同学。1. 为什么选“图书电商平台”做毕设/课设以及我最终圈定的功能范围1.1 选题的价值一个能覆盖主流知识点的“完整闭环”每年选题季总有人纠结做得太简单怕评委问几句就没东西答做得太复杂时间不够又容易烂尾。图书电商系统属于一个很保守但绝对不浪费的选项。它既有前台用户浏览、搜索、加购、下单的完整购物流程又有后台图书管理、分类管理、订单管理、用户管理这类管理端需求整套业务能把SpringBoot的接口开发、MySQL的多表关联、Vue的组件通信和路由守卫、前后端分离的联调部署全部串起来。我选这个题目的另一个原因是图书的字段不像商品SKU那样复杂。图书只需要“书名、作者、ISBN、定价、库存、封面”几个核心字段就能撑起业务没有多规格、颜色、库存批次这些让人头皮发麻的设计。这意味着你的精力可以放在把主流程跑通、把代码写规范而不是花大量时间去处理复杂的商品模型。对课设来说这个复杂度刚刚好对毕设来说也完全足够展开论述。1.2 功能范围前台后台各做多少个模块我最终圈定的范围是五个前台模块和五个后台模块。前台包括首页轮播与推荐图书、图书分类浏览与关键字搜索、图书详情、购物车、订单结算与个人中心。后台包括图书管理增删改查上下架、分类管理、订单管理发货/状态流转、用户管理禁用/解禁、统计看板订单量、销售额、库存预警。为什么把统计看板也加上因为它是数据库聚合查询的真实应用场景随手画几张走势图和排名图就能让项目看起来很“完整”对评分很友好。我没有做秒杀、限时折扣、优惠券、多级评论这些扩展点会在项目文档里列成“后续可扩展方向”需要的话再往里面加。确定功能范围的原则是先保证主流程闭环再考虑亮点功能千万不要一开始就求大而全。1.3 技术栈敲定直接沿用主流组合不玩花活技术栈建议直接用最稳妥的组合后端是SpringBoot 2.7.x MyBatis-Plus MySQL 8.0 JWT前端是Vue 2.7 Element UI Axios Vuex构建工具用Vue CLI。为什么不盲目追新SpringBoot 3.x搭配JDK17很多学校机房的JDK还是8一旦版本踩坑最消耗耐心。Vue 3不是不行但Element-Plus和Vue 3的组合对不熟悉Composition API的同学来说学习曲线偏陡毕设时间有限用Vue 2.7的选项式API找一个现成的后台管理页面改起来最快。这套组合另一个优势是参考资料多搜一个问题能出来大量对应版本的回答对赶工阶段极其友好。技术栈的选择绝对不是越新越好而是越顺手越好。项目的核心价值在于业务逻辑和个人思考不在于框架版本号。2. 从IDEA里长出来的项目骨架初始化、配置、目录划分2.1 Maven工程创建与SpringBoot版本选择我用IntelliJ IDEA直接创建Spring Initializr工程注意这里有一个埋了很多人的坑Spring Initializr默认给的SpringBoot版本经常会比你本机JDK版本高。如果你的JDK是1.8顺手选了SpringBoot 3.1.0项目新建完跑main方法就会报UnsupportedClassVersionError或者启动过程中一堆莫名其妙的Bean创建失败。我自己习惯新建工程时在Spring Boot版本下拉框里固定在2.7.18同时Project SDK选1.8Java编译版本也设为1.8。pom.xml里除了spring-boot-starter-web我还会加spring-boot-starter-validation、mybatis-plus-boot-starter、mysql-connector-j8.0.33以及jjwt和hutool。这些依赖是经过大量项目验证的黄金组合组件版本不要随手升级否则很容易踩到兼容性坑。Maven仓库建议配阿里云镜像不然第一次拉依赖能把人等困。2.2 前后端分离的目录结构设计后端目录我按下述方式划分controller放接口入口service放业务逻辑mapper放MyBatis-Plus的Mapper接口entity放数据库实体common放统一返回体、异常枚举、全局异常处理器config放拦截器和CORS配置util放JWT等工具类。前端则在src下分api、router、store、views、components、utils几个目录。前后端分离最大的好处是后期如果换掉前端或者单独出移动端后端接口可以直接复用。整个源码打包交付时把前后端分成bookstore-back和bookstore-front两个目录根目录放一个README.md一打开项目就知道怎么跑。很多人的项目乱不是代码乱是目录结构没有表达意图。目录清晰了代码的可维护性就成功了一半。2.3 配置文件中容易忽略的配置项application.yml里有几个配置项特别关键数据源配置、Jackson日期格式、MyBatis-Plus逻辑删除、JWT密钥与过期时间。我贴一段常用的数据源配置spring: datasource: url: jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai和useSSLfalse是很多人忽视的细节不加会在连接时报时区错误或SSL告警。另外我会在开发环境把server.port设为8081前端Vue devServer跑8080两边错开避免端口冲突。还要配好spring.jackson.date-format和time-zone因为Java 8的LocalDateTime默认序列化出来是一串数组前端拿到的不是“2024-06-01 12:00:00”这种格式等于没有时间。IDEA里经常要配置启动服务比如修改启动端口直接在配置文件里改server.port即可也可以在启动类的Program arguments里加--server.port8081两种方式都行。跑jar包就用java -jar bookstore.jar --server.port8081。这些操作不难但提前搞清楚能省很多事。3. 数据库设计11张表如何撑起一个图书商城3.1 核心表结构与字段设计核心表按业务拆分用户表userid、username、password、nickname、avatar、phone、role、status、create_time、分类表categoryid、name、sort、图书表bookid、category_id、title、author、publisher、isbn、price、stock、sales、cover、description、status、create_time、购物车表cartid、user_id、book_id、quantity、create_time、订单表ordersid、order_no、user_id、total_amount、status、consignee、phone、address、create_time、订单项表order_itemid、order_id、book_id、book_title、book_image、price、quantity。为什么订单项里要把图书标题、封面、价格复制一份这个设计很多人想不明白。因为订单属于快照数据如果直接关联book表图书改名或改价之后历史订单就会跟着变。电商系统里订单必须保留下单那一刻的商品信息所以冗余字段是刻意为之不是不规范。同理订单表也直接存收货人姓名、电话和地址不能去关联用户表否则用户改了收货信息历史订单就全乱了。3.2 外键、索引与唯一约束怎么权衡毕设项目里我建议不要建物理外键而是在代码层控制关联逻辑。物理外键会在插入、更新、删除时强制校验反而给测试和造数据带来麻烦。逻辑外键配合普通索引就够了book表的category_id建索引order_item表的order_id建索引。查询频繁字段要建索引这点在答辩时可以用来展示你对数据库性能的基本理解。订单号order_no必须加唯一约束我写的生成规则是时间戳用户id随机数。如果要更保底可以用雪花算法但毕设里随机数足够。用户表username也加唯一索引这是登录的基础。数据库初始化SQL脚本放在项目根目录的db文件夹里面同时插入管理员账号和几个测试用户这样项目跑起来立刻有数据可看老师演示时也不会尴尬。3.3 初始化数据与Redis缓存的实际取舍我把管理员、图书分类、20本图书、测试用户、测试订单都写进了init_data.sql导入数据库后不需要手动造数据。Redis在毕设里经常被拿出来凑亮点我一开始也加了Redis做缓存但后来发现在单机部署、数据量小的课设里Redis反而引入了缓存一致性、序列化、环境依赖等额外问题。最后我只在首页轮播图、图书分类列表这种极少变化的查询上用了Redis其他接口直接查数据库。技术选型是服务于项目的不要为了在答辩时多说一个词而给自己挖坑。你可以在文档里写“本项目支持Redis缓存默认关闭”但代码里不要到处都是RedisTemplate的调用。项目跑不稳的时候任何多余依赖都是负担尤其是毕设答辩现场出问题的场景能少一个变量就少一个变量。4. 后端核心接口的实现逻辑从登录到下单的一条完整链路4.1 JWT登录鉴权拦截器 注解别在Controller里写重复代码登录接口接收用户名和密码把密码用MD5加盐加密后去UserMapper里查记录查到就生成JWT把userId、role、过期时间塞进token返回给前端。前端把token存在localStorage每次请求在请求头里带Authorization: Bearer xxx。后端用一个LoginInterceptor统一拦截配合自定义注解PassToken放行登录接口和图书查询等公开接口。这样Controller里不会出现几十行“取token、解析用户、判断权限”的模板代码。很多课设代码每个接口开头都写一大段判断维护起来非常痛苦。拦截器通过WebMvcConfigurer注册代码只要写一次后续接口不用关心登录态。这是专业和业余的分水岭。老师问“怎么实现登录拦截”你能说出“HandlerInterceptor 自定义注解”这个组合就已经拿到分了。4.2 图书分页搜索和“库存扣减”的安全细节图书的分页查询用MyBatis-Plus的Page对象配合LambdaQueryWrapper关键字模糊匹配title或author分类筛选和上下架状态都通过wrapper拼进去。分页插件在config里用MybatisPlusInterceptor注册一下之后只用Page参数就可以。库存扣减是电商系统最容易出Bug的地方。我的下单逻辑是先校验购物车生成订单主表和订单项表然后执行一段关键SQLUPDATE book SET stock stock - #{quantity} WHERE id #{bookId} AND stock #{quantity}这条SQL天然做了原子更新只有库存足够时才能扣减成功通过返回影响行数判断是否超卖。有人用“先查询再扣减”的方式在高并发下会出一个经典问题两个请求同时读到stock1然后都扣成0库存变负。毕设虽然没有并发压测但答辩老师大概率会问“如何防止超卖”能答出这条带条件的UPDATE说明你真思考过并发问题。4.3 统一返回体和全局异常处理让前端少做判断前后端接口的返回格式我定义成ResultCode枚举加ResultT通用对象格式固定为{ code: 200, message: success, data: {} }。Controller只返回业务数据统一包装由封装类来干。同时加了RestControllerAdvice全局异常处理器把参数校验异常、业务异常、未知异常分别处理避免堆栈信息直接吐给前端。这样前端axios拦截器里只需要判断code是否为200其他情况统一弹错误消息。项目里有一个通用的异常处理机制在答辩时可以很自然地展开异常处理不只是try/catch它是系统可维护性的一部分。这个设计是代码量的减法也是答辩分数的加法。5. 前端Vue工程路由、请求封装、页面状态管理5.1 Vue Router的配置和登录守卫前端我用Vue CLI生成工程路由模式用history还是hash要想清楚。针对把项目交到老师电脑上跑的场景hash模式更省心因为双击index.html也能打开而history模式部署到服务器必须做路径回退配置。开发环境我用了history模式做演示最后打包我改回了hash模式反正地址栏多个#号不影响功能。路由守卫方面在router.beforeEach里判断meta.requiresAuth需要登录的页面检查localStorage是否有token没有就跳转登录页有token但访问/admin开头的路由还要检查角色是否为管理员。这个思路对应后端JWT拦截是两个端安全配合的经典案例。同时注意meta信息不要乱用每个路由配置要清晰不要把所有判断逻辑全堆在守卫里。5.2 Axios拦截器自动带token和统一报错我在utils/request.js里创建axios实例设置baseURL和超时时间。请求拦截器从localStorage拿token塞进请求头响应拦截器先看HTTP状态码再按后端返回的code做分支code200直接返回datacode401说明token过期要跳登录页清空登录态其他code用Element UI的Message组件弹出错误信息。这样页面里发请求的代码会很干净一个const res await getBookList(params)拿到的直接是数据处理完正常逻辑即可。很多新手把loading、错误提示、token处理全写在组件里每个页面都一大坨后期改一个错误提示文案要改十几个文件。其实把公共逻辑下沉到请求封装层是所有前端项目都应该坚持的原则。5.3 购物车和订单状态在Vuex/Pinia里怎么管我用Vuex管理两个全局状态购物车数量和当前用户信息。购物车数量放到全局是因为导航栏的角标在任何一个页面都要显示每次进页面就算一次购物车数很蠢。点击加入购物车之后除了调后端接口还要再dispatch一个action更新角标。当前用户信息成功登录后存进Vuex个人中心和路由守卫都能直接访问。需要强调的是Vuex的state不要存tokentoken放在localStorage。因为页面刷新后Vuex会清空如果登录态只放在Vuex里刷新后路由守卫就会认为用户没登录所以刷新后要重新拉取一遍用户信息。如果你用Vue 3可以用Pinia替代Vuex思路是一样的只是按官方推荐而已。6. 前后端联调与打包部署CORS、路由模式、静态资源合并6.1 开发环境跨域问题的三种解决方案开发环境前端跑在8080端口后端跑在8081端口浏览器会拦截跨域请求。我试过三种方式第一种是后端加CORS配置用CrossOrigin注解或者WebMvcConfigurer统一配置允许的来源第二种是前端用Vue CLI的devServer.proxy把/api代理到http://localhost:8081第三种是在Nginx做反向代理。前后端分离项目里最推荐第二种因为开发时不用改后端代码而且网络请求看起来是同源的比较干净。如果急着联调后端配置一个全局CORS也能解决。但要注意allowCredentials(true)和allowedOrigin(*)不能同时使用浏览器会直接拒绝这个矛盾的配置。我第一次写就踩了这个坑前端报错不是跨域而是“credential is not supported”排查了大半天才反应过来。6.2 打包Vue并放进SpringBoot的static目录让一个jar同时提供前后端为了让交付简洁我最后把前端打包产物放进了后端的static目录。流程是先执行npm run build然后把dist目录下所有文件复制到SpringBoot项目的src/main/resources/static下面。SpringBoot会自动将static目录作为静态资源根目录如果再放一个index.html直接访问http://localhost:8081/就能打开前台首页。这里的坑是如果路由是history模式前端跳转到的子路由比如/book/detail/1刷新之后SpringBoot找不到匹配的controller会返回404。解决方案是把404请求forward到index.html但更省事的做法是直接换回hash模式。这样路径是/#/book/detail/1永远只请求一个路径刷新也不会挂。毕设演示时为了避免意外我强烈推荐hash模式。6.3 用Nginx部署时的关键配置和Linux环境注意点如果评审老师要求部署到服务器用Nginx会显得更正规。Nginx配置里核心是location /指向dist目录location /api/代理到后端jar的端口。同样在history模式下需要加上try_files $uri $uri/ /index.html;。我遇到过MySQL在Linux上安装的坑比如用rpm方式安装MySQL 5.7时会有依赖冲突用yum或者tar.gz解压安装更稳。服务器上跑jar包建议用nohup java -jar xxx.jar app.log 21 方式后台运行同时配上--spring.profiles.activeprod这样的多环境配置。数据源、日志级别、文件上传路径用单独配置文件隔离可以避免很多环境差异导致的问题。部署这件事不难但细节很多每一个坑都是你答辩时能讲的“真实部署经验”。7. 我亲手踩过的坑按排查链路来分享7.1 SpringBoot版本和JDK版本不匹配启动就失败有一次新建项目没注意版本SpringBoot给我带了个3.2.1跑main方法直接报“Error creating bean”之类的错早期根本看不出原因后来仔细看控制台才发现是UnsupportedClassVersionError因为本机JDK是1.8。当时我一度想装JDK17但项目里师兄给的代码都是基于JDK8的就放弃了升级路线直接把SpringBoot版本降到2.7.18刷新Maven后一切正常。给同学们一条经验选型前先确认三件事——本机JDK版本、Maven仓库能不能拉到依赖、IDE版本对应的Spring Initializr默认版本。如果一上来就选最新版大概率会在环境上消耗一到两天。SpringBoot版本太高不一定是好事学会“降级”也是开发基本功。7.2 MySQL 8.0 SSL连接错误和时区问题使用数据库连接池时控制台一直打红字告警内容类似SSL connection error。我在url里加了useSSLfalse解决了告警但真正头疼的是时区问题。不配置serverTimezone时查询时间字段会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这串乱码其实是中文时区名没有被正确识别。解决办法就是url后面加serverTimezoneAsia/Shanghai。另外MySQL 8.0默认的认证插件是caching_sha2_password用旧版驱动强连会报权限错误。建议mysql-connector-j依赖至少用8.0.33版本或者创建数据库用户时指定mysql_native_password双保险。我经历过从MySQL 5.7换到8.0时明明密码没写错就是连不上最后排查到认证插件才发现是驱动版本太老。7.3 Vue打包后接口调不通问题出在环境变量开发环境接口正常打包之后请求全部404。排查过程是这样的打开浏览器F12的Network面板发现请求地址变成了/bookstore/api/...前端确实把请求发到了同域但后端jar里没有把根路径映射到接口而后端接口的实际路径是/api/...。问题出在axios的baseURL我直接写死了开发环境的http://localhost:8081打包时没有替换。后来我在项目根目录建了.env.development和.env.production两个文件分别定义VUE_APP_BASE_URL代码里用process.env.VUE_APP_BASE_URL动态读取。环境变量要从第一天就规划好不要图省事硬编码。这个坑非常典型属于“本地好好的一打包就完蛋”出现频率极高。7.4 MyBatis-Plus字段映射和逻辑删除的小坑实体字段用了isHot数据库列叫is_hotMyBatis-Plus默认开启驼峰命名映射后能自动对应但如果是Boolean字段MyBatis-Plus有个特殊行为实体里boolean类型字段如果叫isHot生成SQL时可能会查不出数据。我的解决办法是在实体字段上写TableField(is_hot)强制映射一劳永逸。另一个容易忽略的是逻辑删除配置了TableLogic后默认只拦截MyBatis-Plus自动生成的SQL。如果自己写了复杂XML SQL就要在SQL里手写deleted 0条件否则删除旧数据后列表查询依然能查出已删除记录看起来就像“删不掉”。这类问题排错时看不到任何报错只有数据行为异常非常考验细心程度。8. 最后的交付源码结构、文档、演示视频和答辩思路8.1 源码结构怎么整理才像“专业项目”交付时不要直接甩一个几十MB的zip包。我会整理成三块压缩包根目录放README.md、数据库脚本db、源码目录bookstore-back和bookstore-front外加一个docs说明文档。README写得越细致越加分至少包括项目介绍、开发环境、启动步骤数据库导入脚本、前端npm install和npm run serve、后端启动类运行、默认账号管理员和用户、功能列表和截图。很多人的源码就是src下堆了一堆文件连README都没有老师想跑都无从下手。细节上还要把.idea、target、node_modules这些无关目录排除掉控制包体积。一个几百MB的压缩包解压都要半天体验很差第一印象直接拉低。8.2 项目文档和演示视频的细节文档不要照抄需求文档模板写一堆空话我按自己项目的真实功能写每个模块一页截图加一段操作说明重点描述实现逻辑和关键代码位置。演示视频建议直接录屏时长控制在5到8分钟先演示前台用户流程注册/登录/浏览/加购/下单再切管理员后台展示图书管理、订单处理、统计看板视频里顺手打开控制台把请求状态和数据库变化拍进去证明功能是真实联调成功的。有一年答辩我就看到有同学视频里点了“下单”后台数据库却没有生成订单记录当场被老师质疑。这种低级失误千万别犯。演示视频不是越炫越好关键是让看的人能确认“这个项目真的能跑起来”。8.3 答辩时老师最爱问的几个问题及答题思路最常被问的问题不外乎分页怎么做JWT的原理和安全性为什么订单要冗余图书信息库存怎么防超卖前后端分离怎么解决跨域这些问题在前面章节都有具体实现答辩时不用背概念直接说你代码里的设计就行。比如问JWT你可以说token由header、payload、signature三部分组成服务端用密钥签名客户端每次请求带token拦截器校验签名和过期时间不需要在服务端保存session所以适合前后端分离。再比如问为什么用MyBatis-Plus就说单表CRUD不需要手写SQL提高开发效率复杂的统计查询仍然用XML手写SQL两者结合。“知道什么时候用框架简化什么时候手写SQL”这句话就是老师想听到的思考深度。最后说一点个人体会图书电商平台不是一个多新颖的题目但它能稳扎稳打把一套主流前后端分离技术贯通起来。我做这套系统最明显的感受是中间真正花时间的不是代码本身而是环境配置和版本取舍。如果本文里这些坑你能提前避开至少能省出一周时间。省下来的时间干点啥不好把论文多打磨两遍或者把图表做得更漂亮一点它在答辩时比多写十行CRUD都管用。