资讯动态

Spring Boot书店管理系统实战:数据模型、JWT认证与订单并发设计

发布时间:2026/10/14 4:04:35 来源:尧图企业网站定制
1. 毕业论文选题敲定书店管理系统为什么值得做一版每年大四上学期选题这件事都能劝退一波人。身边同学要么冲着人工智能大数据去报完题发现数据集都找不齐要么选了纯管理系统做出来的东西自己都羞于演示。我帮人审过不少毕业设计的代码书店、商城、图书馆、宿舍管理这几类确实常年霸榜但同样是管理系统有人做得像课程作业有人做得像能交付的产品差距根本不在功能多少而在数据模型、事务处理、权限控制这些内功上。我这次要聊的新月书店管理系统就是一个基于Spring Boot框架的典型全栈项目前端用Vue Element UI后端用Spring Boot MyBatis Plus数据库用MySQL。功能上覆盖图书管理、分类管理、用户登录注册、购物车、订单管理、库存管理等常规模块。它的定位很清晰——不是猎奇课题而是一个能让你完整走一遍从需求分析到部署答辩全流程的稳妥选择。为什么说稳妥因为书店管理系统天然包含了Web开发里几乎所有的基本功增删改查、分页搜索、文件上传、登录鉴权、关联表查询、事务操作、前端交互。这些东西往简历上一写面试官一眼就知道你有真实项目经验而不只是背了一堆框架概念。如果你是计算机相关专业又还拿不准做什么课题这篇内容可以给你一套可以直接落地实现的完整思路。2. 数据模型先行五张核心表的字段设计与取舍理由很多同学做毕业设计习惯先打开IDE建页面写着写着发现表结构不对又回头改表。我自己的习惯是反过来的先把所有业务场景在纸上走一遍把数据模型定下来再动代码。书店管理系统虽然看着简单但表结构设计得好不好直接决定了后面订单、库存这些逻辑能不能写得顺。2.1 图书表与分类表冗余字段的边界在哪里图书表是整个系统的地基。我设计的字段大致是这样CREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT, book_name varchar(128) NOT NULL COMMENT 书名, isbn varchar(32) DEFAULT NULL COMMENT 国际标准书号, author varchar(64) DEFAULT NULL, publisher varchar(128) DEFAULT NULL, category_id bigint(20) DEFAULT NULL COMMENT 所属分类, price decimal(10,2) NOT NULL, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存, sales int(11) NOT NULL DEFAULT 0 COMMENT 销量, cover_url varchar(255) DEFAULT NULL COMMENT 封面图, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1上架 0下架, description text, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;分类表单独拆出来不直接在图书表里存分类名字符串这是为了以后扩展。比如你想做计算机 编程语言 Java这种多级分类或者按分类统计销量存category_id才有操作空间。表里加一个parent_id字段就能支持层级结构哪怕第一版只做单级分类这个设计也给你留了后路。关于冗余字段我的体会是像book_name、price这种在订单明细里再次存储不是设计失误而是故意的。订单生成之后图书本身可能改名、改价、下架甚至删除如果订单明细只存book_id历史订单的数据就会跟着变这在真实系统里是绝对不允许的。毕业答辩时能说清楚为什么订单表冗余了商品快照字段是很加分的点。2.2 用户表、订单表与订单明细表角色字段和金额精度的坑用户表不难但有个细节值得注意角色字段role我建议直接用tinyint0代表管理员、1代表普通用户而不是用字符串。这样写拦截器判断权限的时候逻辑简单而且也不容易拼错。密码字段存的是BCrypt加密后的哈希值千万不要直接存明文这一点后面细说。订单表和订单明细表是典型的一对多结构。order表存订单主信息订单号、用户id、总金额、状态、收货人、电话、地址这些。order_item表存每一本书的下单快照book_id、book_name、price、quantity、subtotal。金额字段一律用decimal而不是double或float这是个老生常谈但依然有人踩的坑——二进制浮点数无法精确表示0.1这种小数做计算会出现0.30000000000000004这种结果银行和电商系统里这是不可接受的。在设计表的时候我还加了一个细节order表里的order_no订单号用了唯一索引而不是直接用自增id当订单号。因为自增id会暴露系统销量也容易被遍历抓数据真正上线的系统都会生成业务单号。后端的生成策略我用的是时间戳 随机数 用户id的组合虽然简单但足够演示业务主键与物理主键分离这个设计思想。2.3 逻辑删除与自动填充两个提升开发效率的小设计图书、用户这些表我都加了deleted字段做逻辑删除。真实的电商系统不会物理删数据因为订单、报表都关联着历史数据物理删除会导致统计失准。MyBatis Plus对逻辑删除支持得很到位只要配置一个全局逻辑删除字段所有SQL都会自动带上deleted 0的条件不需要你手写。create_time和update_time我用了MyBatis Plus的自动填充功能通过MetaObjectHandler这个接口统一处理而不是在service层每个方法里手动set。这样既减少了重复代码也保证不会有哪条记录忘记写创建时间。代码大致是Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }这几个表设计定下来整个系统的骨架就出来了。不要急着写代码我建议你把表结构画在纸上把图书到订单的流转路径走一遍确认每个环节需要哪些字段再动手建表。3. 技术栈定位Spring Boot MyBatis Plus Vue 搭配的底层逻辑技术选型是答辩时老师必问的问题也是很多同学说不清楚的地方。为什么用Spring Boot而不是SSH为什么持久层选MyBatis Plus而不是JPA前端为什么用Vue 2而不是Vue 3加TypeScript这些问题的答案其实跟毕业设计这个场景强相关。3.1 Spring Boot版本怎么选别迷信最新版Spring Boot目前主流在用的是2.x和3.x两个大版本。对于毕业设计我的建议是老老实实用2.7.x。原因很简单3.x要求JDK 17起步而且很多第三方starter还在适配中遇到问题网上能搜到的解决方案相对少。2.7.x配合JDK 8或JDK 11是经过大量生产环境验证的组合资料多、报错好查这才是学生项目最需要的。Spring Boot的价值不在技术先进性而在约定大于配置这个理念。以前SSH时代要写一大堆XML配置现在一个starter依赖加几句properties就能把Web容器、数据源、JSON序列化全部搞定。你自己写代码的时候只需要关注业务逻辑不用管配置细节这就把毕业设计的精力省下来放在真正该放的地方。3.2 持久层选型MyBatis Plus为什么比JPA更适合这种项目先说对比。JPA/Hibernate是面向对象的思路你操作的是实体对象框架帮你生成SQLMyBatis是面向SQL的思路SQL自己写灵活可控。两者各有优劣但放在书店管理系统这个场景里MyBatis Plus有几个很实际的优势代码生成器一键生成实体、Mapper、Service分页插件用起来简单逻辑删除、自动填充都是内置功能。JPA虽然也可以但配置复杂度和学习成本在这个场景里没必要。对比维度MyBatis PlusSpring Data JPASQL控制力手写SQL复杂查询好控制框架生成复杂查询要学JPQL学习曲线会MyBatis就能上手需要理解对象关系映射思想分页方案内置分页插件Pageable接口逻辑删除配置即用需要SQLDelete注解典型场景电商、管理类系统企业级复杂领域模型这里多说一句很多同学担心用MyBatis Plus显得不够高级。答辩的时候老师问起来你可以说项目里简单的单表查询用MyBatis Plus内置方法像订单报表、多表联查这种复杂场景自己写SQL既保证开发效率又不牺牲可控性。这个回答比模板生成器自动生成的要专业好几个档次。3.3 前端为什么用Vue Element UI以及项目的整体分层前端我用的是Vue 2 Element UI组合没用Vue 3 Ant Design也没有上TypeScript。核心考量是生态成熟度和上手成本。Vue 2的教程、组件库、报错解决方案在中文社区里一搜一大把Element UI的后台管理组件刚好覆盖表单、表格、弹窗、分页这些高频场景非常适合快速搭建管理系统。如果你基础不错用Vue 3 Element Plus也可以但考虑到毕业设计的时间紧别在工具链上折腾太久。整个项目的分层是经典的四层结构Controller接收请求和参数校验Service处理业务逻辑Mapper负责数据库操作Entity对应数据库表。另外一个容易被忽略的层是DTO/VO也就是请求和响应对象。很多同学直接把Entity返回给前端这样做问题很大密码字段可能泄露字段命名容易跟前端需求不一致还容易出现JSON序列化把懒加载属性带出来的问题。我建议每个接口返回专门定义的VO这既是规范也是答辩时能讲的点。配置方面我贴几个容易踩坑的地方。application.yml里数据源配置要加上useSSLfalse和serverTimezoneAsia/Shanghai否则MySQL 8连接会报时区错误spring: datasource: url: jdbc:mysql://localhost:3306/bookstore?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456MyBatis Plus的分页插件必须用配置类注册否则分页不生效Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(100L); interceptor.addInnerInterceptor(pagination); return interceptor; } }现在回头看这套技术栈每一项都不是最前沿的但组合起来的方案在学生能在三个月内独立完成、答辩能讲清楚、代码能经得起问这个目标下几乎是最优解。4. 登录认证与权限控制JWT方案的学生项目级实现书店管理系统的登录认证是很多同学第一次接触非CRUD的部分也是答辩时老师最喜欢追问的点。常见做法有三条路Session、Spring Security Session、JWT 拦截器。我最终采用了JWT方案下面说说为什么以及怎么落地。4.1 为什么不用Session和Spring Security传统Session方案的问题是前后端分离时不好处理跨域携带Cookie而且集群部署下Session共享要额外引入Redis。Spring Security功能强大但学习成本高配置复杂很多同学配完SecurityConfig之后连登录流程都看不懂答辩时更是一问三不知。JWT方案的好处是无状态前端登录成功后拿到token之后每次请求在请求头里带上Authorization后端拦截器验签通过就放行。逻辑直观代码量少自己能写清楚每一行。JWT本身是一个三段式的字符串Header.Payload.Signature。Header指定加密算法Payload存放用户id、用户名、过期时间这些声明Signature用密钥对前两段签名。服务端不存Session只要密钥不泄露就无法伪造token。4.2 登录接口的具体实现逻辑登录的核心流程是接收用户名密码查询用户用BCrypt校验密码通过则生成token返回。这里有两个细节第一密码用BCrypt加密存储不要用MD5。MD5加盐虽然也行但BCrypt每次加密结果不同、自带强度参数是业界主流做法。Spring Security的crypto包里有BCryptPasswordEncoder可以直接用不需要引入整个Spring Security。第二生成token的时候把用户id和角色都放进payload里这样后续权限判断用拦截器读取即可不用每次查数据库。我用的JJWT库版本选0.9.1或0.11.5这类稳定版。工具类大概是public class JwtUtil { private static final String SECRET newmoon-bookstore-secret-key; private static final long EXPIRE_TIME 7 * 24 * 60 * 60 * 1000; public static String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }4.3 拦截器实现权限判定前后端都要处理401后端核心是一个HandlerInterceptor在preHandle方法里从请求头取token解析失败或过期就返回401成功就把用户信息放进request的attribute里供后续的Controller获取。角色判断我拆了两个拦截器或者在一个拦截器里判断路径前缀——比如/admin/**的请求必须role为0才能访问/user/**的其他请求登录即可。这种基于路径的权限控制方式简单直观答辩时也能讲明白。拦截器注册要注意放行登录接口、图书列表这些公开接口。前端那块我在axios的请求拦截器里统一从localStorage取token并加到请求头响应拦截器里判断状态码401就跳转到登录页。有一个容易忽略的点JWT过期后前端只会拿到401但页面上的用户数据还在跳转前要清掉localStorage里的用户信息和token否则会出现登录页一闪而过又回到原页面的bug。4.4 权限控制里容易忽略的细节第一token过期时间不要设太短学生项目7天比较合适做演示的时候如果session频繁过期会很尴尬。第二不要把密码、手机号等敏感信息放进payloadJWT的Payload只是Base64编码不是加密任何人拿到token都能解码看到内容。第三密钥不要硬编码。虽然毕业设计项目硬编码影响不大但答辩被问密钥管理怎么做时你可以提一嘴生产环境应该放在配置中心或环境变量这个扩展意识很加印象分。前端路由守卫也要配合做权限控制路由meta里标记需要的角色beforeEach里判断当前用户角色是否匹配不匹配就跳403页面。前端控制只是体验优化真正的安全边界永远在后端这个道理要在答辩时主动讲出来。5. 订单与库存是命门事务边界、并发扣减与状态流转书店管理系统里图书增删改查都是基本功真正体现设计能力的是订单模块。一个下单操作涉及用户校验、库存扣减、订单生成、明细写入、金额计算等多个步骤只要有一步失败数据就不能残留。这一块也是并发问题的高发区老师必问。5.1 下单流程的事务边界怎么划我先说一个很多同学容易犯的错误把校验库存、扣库存、生成订单、插入明细全部放在Service方法里方法上标一个Transactional就完事。问题在于Spring的Transactional默认只回滚RuntimeException如果你在代码里catch住了异常还继续往下走事务就失控了。正确做法是业务方法内不要自己吞掉异常事务边界内的编码用RuntimeException往上抛统一交给全局异常处理器处理。我定义的下单Service方法核心逻辑是Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long userId, ListCartItem items, OrderAddress address) { // 1. 遍历购物车明细计算总金额 // 2. 校验图书状态是否上架 // 3. 扣减库存原子操作 // 4. 生成订单主记录 // 5. 批量插入订单明细 // 6. 清空购物车 }rollbackFor Exception.class这个参数要在答辩时主动提。Transactional默认rollbackFor是RuntimeException但我们的业务异常往往继承Exception如果没有这个参数业务异常抛出后事务不会回滚就会出现库存扣了、订单没生成的数据不一致这是真正在业务里会出现的严重bug。5.2 库存扣减从先查后改到原子更新秒杀场景处理并发问题书店系统也要考虑。两个用户同时买同一本书如果下单逻辑是先select stock判断stock0再update stock减一在高并发下就会出现两个请求都查到stock1然后都去减1最后库存变成0但订单生成了两单。这在技术上叫超卖。解决办法很简单粗暴但不失优雅把判断和更新合成一条原子SQL用数据库层面的行锁来保证安全UPDATE book SET stock stock - #{quantity}, sales sales #{quantity} WHERE id #{bookId} AND stock #{quantity}这条SQL执行后如果影响行数为0说明库存不够扣就抛业务异常回滚。因为UPDATE语句对行加锁是原子的两个并发请求只有一个能成功执行到修改另一个会在行锁上等待最后看到stock不满足条件影响行数为0从而被拦截。这个方案不需要引入Redis分布式锁对毕业设计来说理解成本低、实现简单、效果可靠是性价比最高的方案。5.3 订单状态流转用状态机思维代替散落的if-else订单状态的经典做法是用一个int字段0待付款、1已付款、2已发货、3已完成、4已取消、5退款中、6已退款。状态流转规则是固定的待付款可以取消或付款已付款可以发货已发货可以完成已付款可以申请退款……如果每个操作都散落在service里用if判断时间长了必然出现逻辑漏洞。我的做法是单独写一个枚举OrderStatus里面包含状态码和允许流转到哪些状态的方法。这个设计虽然简单但能体现工程意识public enum OrderStatus { UNPAID(0, 待付款), PAID(1, 已付款), SHIPPED(2, 已发货), COMPLETED(3, 已完成), CANCELED(4, 已取消); private final int code; private final String desc; public boolean canTransitTo(OrderStatus target) { // 列举每个状态允许的下一步状态 switch (this) { case UNPAID: return target PAID || target CANCELED; case PAID: return target SHIPPED || target CANCELED; case SHIPPED: return target COMPLETED; default: return false; } } }订单号我用了yyyyMMddHHmmss 四位随机数 userId后四位的组合虽然不能保证绝对唯一所以数据库还是要加唯一索引兜底但作为演示项目足够关键是把为什么不用自增id当订单号这句话在答辩时说清楚。5.4 模拟支付与超时取消一个能加分的小设计真实项目里支付要对接微信、支付宝学生项目做不了这个所以支付通常用模拟支付按钮代替。我在订单详情页放一个模拟支付按钮点击后把订单状态从待付款改成已付款同时根据订单明细批量增加图书销量。这一步虽然简单但流程上是闭环的。超时取消订单这个功能我建议做上。思路是下单成功时在订单上记录一个payDeadlineTime查询订单列表时如果当前时间大于deadline且状态还是待付款就自动把状态改为已取消同时回补库存。有人会用延迟消息、定时任务这些都可以提但最朴素的实现是用户刷新订单列表时做一次超时校验虽然不实时但对毕业设计来说逻辑清晰、容易演示。答辩时老师问为什么不用定时任务你可以说生产环境可以用XXL-Job扫表但这里用惰性检查避免引入额外组件再补一句假如订单量大了可以换方案这个回答就很完整了。6. 联调阶段踩坑实录跨域、日期格式与精度丢失项目做完前后端联调的时候才是真正暴露问题的时候。前后端分离的项目接口通了页面不动、数据对了格式不对这些坑我基本都踩过一轮下面挑几个有代表性的说说你遇到的时候能少走弯路。6.1 跨域问题不是加个注解就完事前端在8080端口后端在8081端口前端发请求必然触发跨域。很多同学的解决方案是在Controller上加CrossOrigin注解但我建议用全局配置类统一处理Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里面有个细节allowCredentials(true)和allowedOriginPatterns()不能和CrossOrigin(origin)混用否则浏览器会拦截。另外如果你配置了JWT拦截器一定要放行OPTIONS预检请求否则前端每次请求都会先被拦截器拦下来报一个跨域请求失败但后端日志又什么都没有的诡异问题。这个坑我排查了很久最后发现是拦截器没有放行OPTIONS。6.2 日期格式前端显示一串数字或者直接乱码前后端分离之后日期字段是最容易出格式问题的。LocalDateTime默认被Jackson序列化出来是一大串数字时间戳格式前端显示[object Object]或者一个很长的数字。解决方式是全局配置Jackson的日期格式而不是每个字段手动加JsonFormatspring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8后端返回给前端的订单时间、创建时间全部统一成2024-05-20 14:30:00这种格式前端就不用再做处理了。顺便说一句数据库连接配置里的serverTimezone也要设成Asia/Shanghai不然存进去的时间和取出来的时间会有8小时的偏差如果这个只差8小时的bug在答辩当晚被发现心态会炸。6.3 JavaScript精度丢失Long类型的id被截断这个坑隐蔽且经典。数据库表的id是bigintJava端对应的Long类型如果id值超过JavaScript的最大安全整数Number.MAX_SAFE_INTEGER约9千万亿序列化成JSON后前端拿到就会精度丢失最后一个数字可能变成0。如果自增id还没超过这个值这个问题不会暴露但一旦数据量上来就麻烦了。最省事的解决方式是统一把Long类型序列化成字符串Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - builder .serializerByType(Long.class, ToStringSerializer.instance) .serializerByType(Long.TYPE, ToStringSerializer.instance); } }前端拿到的id变成字符串查询接口参数那边也要做相应处理。这个点讲出去答辩老师会对你刮目相看因为它属于真正上线才会遇到的问题。6.4 联调效率工具Swagger和统一返回体我一开始写接口返回结构五花八门有直接返回Map的有返回List的有登录成功返回token、失败返回字符串的。前端对接起来痛不欲生。后来我统一用Result 包装所有接口返回code、message、data三个字段。这个习惯强烈建议从一开始就建立不要等前端同事来骂你再改。public class ResultT { private Integer code; private String message; private T data; // 静态方法 success() / error() }配合Swaggerspringfox或springdoc选一个生成接口文档前后端联调效率能提升一大截。Swagger本身就带在线调试功能前端可以直接在Swagger UI里试接口不用等后端写完再教他参数怎么传。集成方法很简单加依赖、加一个EnableOpenApi或者SwaggerConfig配置类Controller上写好ApiOperation注解就行。如果担心暴露接口信息可以加个开关生产环境关闭。毕业设计阶段开着完全没问题演示的时候还能顺手给老师看接口文档。7. 答辩前夜项目亮点、追问应答与三个扩展方向项目做完了代码能跑了不代表就能顺利通过答辩。很多同学答辩翻车不是因为项目不行而是老师问的问题答不上来。把下面这些问题提前过一遍心里会踏实很多。7.1 演示路径怎么设计别从登录页开始点我的建议是演示准备好三条路径第一条是普通用户的完整购物流程注册登录、浏览图书、搜索、加入购物车、下单、模拟支付、查看订单状态第二条是管理员的商品管理流程上架一本书、修改价格、上传封面、设置上下架然后回到前台验证数据变了第三条是稍微有冲击力的操作比如连续创建两个订单让库存归零再下单一次看系统怎么拦截。这三条走下来系统的主要能力都展示到了。这里有个演示技巧提前把测试数据准备好图书最好有十几本、覆盖两三个分类用户至少有两个不同角色。别现场注册账号、现场造数据灯光一打老师一催手一抖就打错了。7.2 老师最可能追问的五个问题我做过一个汇总表下面这几个问题在答辩中被问到的概率非常高追问问题推荐应答思路为什么选MyBatis Plus不用JPA单表用内置方法提升效率复杂查询手写SQL保证可控库存并发问题怎么解决不先查后改用UPDATE带条件原子扣减配合事务回滚JWT和Session有什么区别JWT无状态、适合前后端分离和分布式部署解放了Session存储订单超时未支付怎么办惰性检查已实现进一步可扩展延迟队列或定时任务方案如果要做一万并发怎么办加Redis缓存热点数据加MQ削峰订单接口做幂等设计被问倒不可怕可怕的是答非所问。每个问题你都能说出当前方案是什么、局限是什么、扩展方向是什么这三层就已经超出平均水平了。7.3 三个可行的扩展方向工作量不够时的加分项如果导师觉得工作量不够或者你想在答辩里更有底气下面三个方向值得做第一个是数据可视化。用ECharts做一个后台统计页面展示图书销量排行、分类占比、每日订单趋势。后端写一个统计接口按分类聚合销量、按月聚合订单数量再配合图表展示视觉效果非常震撼工作量也不大一周就能搞定。第二个是Redis缓存。把图书列表、首页推荐这些高频读取的数据缓存到Redis设置5分钟过期。演示的时候可以故意对比加缓存前后的接口响应时间数据是很直观的。这个方向还能顺势引出缓存穿透、缓存击穿这些概念都是答辩加分项。第三个是消息队列处理订单超时。引入RabbitMQ的延迟队列插件下单时发一条延迟消息消费者收到后发现订单未支付就自动取消。虽然配置稍微麻烦但这个消息驱动的思维很值钱也是简历上可以写的一笔。7.4 关于答辩PPT和材料准备最后分享我个人的一个小习惯答辩PPT里放一张项目的架构图手绘的也行标清楚前端、后端、数据库、鉴权模块之间的关系再放两张核心SQL的截图比如原子扣减和订单查询比贴一堆代码更有说服力。讲的时候用讲故事的方式从需求出发讲表怎么设计的讲下单并发问题怎么解的讲联调遇到过什么坑比背PPT生动得多。书店管理系统这个课题上限可以很高下限也不会太低。关键是你在做的过程中有没有真正理解每一步为什么这么设计。数据模型是你的设计图纸事务和并发是系统的承重墙权限认证是门禁系统把这些讲明白这个毕业设计就算真正完成了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑