SpringBoot仓库管理系统毕设全攻略:从数据库设计到库存并发实战 每年到这个节点总有一批计算机相关专业的同学开始为毕业设计发愁。打开各种平台搜索“基于SpringBoot的仓库管理系统毕设源码”要么是下载要会员要么是源码拿到手缺胳膊少腿要么是代码能跑但一问三不知。作为一个带过不少毕设、也审过不少“参考代码”的过来人我特别能理解这种状态做仓库管理系统技术上不算难但真正让人头疼的往往是——系统该有哪些功能才算完整数据库表怎么设计才合理库存并发扣减这种问题答辩老师问起来怎么解释清楚这篇内容就是围绕SpringBoot仓库管理系统这个经典毕设题目来展开的。我把它当成一个完整项目来讲从选题逻辑、需求分析、数据库设计、核心业务代码一直聊到源码整理交付和论文答辩的准备。无论你是想拿这套思路去做自己的毕设还是想从零理解一个仓库管理系统到底是怎么搭出来的这篇都值得认真看完。1. 为什么“仓库管理系统”是SpringBoot毕设的黄金选题1.1 仓库管理系统的业务闭环恰好适合展示技术毕设选题有个隐藏逻辑业务不要太复杂到失控但技术点又必须足够丰富让老师觉得你有工作量。仓库管理系统恰好卡在这个平衡点上。它能覆盖的完整业务链路包括商品/物料基础信息管理、供应商管理、入库单管理、出库单管理、库存实时查询、库存盘点、低库存预警、用户登录与操作日志。这些功能组合在一起就是一个典型的“增删改查业务状态流转数据统计”项目。对于本科阶段来说它既能体现你对软件工程流程的理解又不会像电商秒杀系统那样在并发和分布式层面把你逼到死角。另外仓库管理系统有一个天然优势业务规则清晰外界场景明白。你自己去理解“入库增加库存、出库减少库存、库存不能为负数”这件事毫无障碍。这意味着你可以把主要精力放在代码质量、项目结构和答辩表达上而不是花大量时间去理解一个陌生的行业术语。1.2 SpringBoot在这类业务里的优势到底在哪先说实话市面上有些毕设还在用ServletJSP或者SSHStrutsSpringHibernate做仓库系统不是说不行而是这些技术栈在2024年的语境下已经明显脱离岗位需求了。SpringBoot之所以成为毕设和技术栈的主流选择核心原因有三个第一起步成本低。SpringBoot通过自动配置和starter机制把SpringMVC、事务管理、数据访问这些环节的配置量压缩到极小。一个spring-boot-starter-web加一个spring-boot-starter-data-jpa或mybatis-plus-boot-starter项目就能跑起来。这对毕设阶段的时间压力极其友好。第二生态成熟资料密度高。大部分问题你在搜索引擎里一搜就能找到答案。SpringBoot整合MyBatis-Plus、整合Redis、整合JWT、打包部署等案例比比皆是踩坑成本极低。第三实习和就业场景中认可度高。答辩现场老师问“为什么用SpringBoot”你可以理直气壮地回答它目前是企业级Java开发的主流框架基于约定优于配置的思想大大提升了开发效率同时保留了Spring核心的依赖注入和AOP能力。当然SpringBoot只是后端一个完整的前后端分离项目还需要前端部分。如果自己的前端基础一般推荐在不影响功能的前提下使用服务端渲染Thymeleaf模板或者直接用VueElement UI做一套CRUD界面二选一即可不用追求极致炫酷。1.3 合理的技术选型别给自己挖坑选型这块我的建议是“三板斧”后端SpringBoot 2.7.x 或 SpringBoot 3.x如果没有特殊限制就用2.7.x兼容资料最多持久层MyBatis-Plus看家本领省去了大量单表CRUD的XML编写数据库MySQL 5.7或8.0如果要加权限控制可以选Sa-Token或JWTSpring Security。但有一条经验要记住不要为了显得高级而引入大量你讲不清楚的组件。你用了Redis做缓存如果连缓存穿透、缓存击穿都说不明白答辩时反而扣分。技术栈够用、能自圆其说、每一项都能解释清楚比堆砌一堆高级名词重要得多。2. 需求梳理与功能模块划分先解决“做什么”再动手2.1 从角色出发梳理需求我见过太多同学拿到仓库管理系统的题目第一反应就是找一份现成源码然后照着改名字。这种做法最致命的不是代码能不能跑而是你根本不知道这些功能是给谁用的。仓库管理系统的角色通常可以分三类角色核心诉求涉及功能系统管理员管人、管权、看全局用户管理、角色管理、菜单管理、数据统计仓库操作员日常出入库操作入库登记、出库登记、库存查询、盘点管理部门领导/财务关心库存和成本库存报表、出入库流水统计、预警信息这样的角色划分直接影响权限设计和页面设计操作员不能看到用户管理菜单管理员不需要日常去录一张入库单。如果你的系统搞成“所有功能所有人可见”那在答辩时被问到“你的权限设计是怎么考虑的”就很难看。2.2 核心模块与加分模块的取舍仓库管理系统一般包含以下模块按优先级分基础数据模块商品管理商品名称、编码、规格、单位、类别、安全库存值供应商管理供应商名称、联系人、联系方式、地址仓库/库位管理可简化为单仓库业务单据模块入库管理采购入库/退货入库/其他入库出库管理销售出库/领料出库/报废出库盘点管理盘点单生成、盘点差异处理库存核心模块库存查询按商品、仓库、批号等维度查询库存预警低于安全库存自动标记提醒库存流水记录每一次入库出库的明细变化系统管理模块用户管理、角色管理、菜单权限、操作日志这里做一个取舍建议专注核心链路再选一个亮点加深化。核心链路就是商品、供应商、入库、出库、库存查询、库存流水这六块是仓库管理系统的骨架。亮点深化可以从库存预警细作支持邮件通知、盘点差异分析、出入库月报表图形化中任选一个。这样既保证完整性又有突出工作量的地方。2.3 页面与接口的对应关系有了模块清单就可以规划页面了。建议按以下方式组织前端页面登录页首页/仪表盘库存总量、今日入库、今日出库、预警列表商品管理页、供应商管理页入库单列表页、入库新建页、入库单详情页出库单列表页、出库新建页、出库单详情页库存查询页、库存流水页盘点管理页用户管理页、角色管理页、操作日志页每个页面都对应后端的一组REST接口。这一步规划好了后面写代码基本就是“照单做菜”不用东一榔头西一棒子。3. 数据库设计仓库系统的基石不能马虎3.1 核心表结构与字段类型选择的细节数据库设计是仓库管理系统里最能体现基本功的地方。一张表怎么建、字段类型怎么选、外键要不要用这些细节在答辩时都可能是老师的提问点。我建议核心表至少包括以下这些用户表sys_userid BIGINT PRIMARY KEY AUTO_INCREMENT username VARCHAR(50) UNIQUE password VARCHAR(100) -- 存BCrypt加密后的密文 real_name VARCHAR(50) status TINYINT -- 启用/禁用 create_time DATETIME角色表sys_role与用户角色关联表sys_user_role如果是简单权限模型至少要有用户表和角色表前端根据角色字段判断菜单显示。商品表productid BIGINT PRIMARY KEY product_code VARCHAR(50) UNIQUE -- 商品编码 product_name VARCHAR(100) category VARCHAR(50) specification VARCHAR(100) -- 规格型号 unit VARCHAR(20) -- 单位箱/个/公斤 safety_stock INT -- 安全库存 status TINYINT -- 上架/停用 create_time DATETIME这里最容易被忽略的是唯一索引。商品编码、用户登录名、角色编码这类业务上不能重复的字段一定要加唯一索引。很多同学在代码里写一堆判断逻辑去查重其实数据库层面用唯一索引兜底更可靠。字段类型选择的建议库存数量用INT或BIGINT涉及小数量的用DECIMAL(10,2)而不要用FLOAT或DOUBLE——浮点数在金额和数量计算中容易产生精度误差这是MySQL里非常基础但重要的细节。时间字段用DATETIME不要用VARCHAR存时间。3.2 为什么入库、出库一定要拆主表和明细表这是仓库管理系统设计里极其重要的一个结构问题。以入库单为例如果只设计一张“入库单表”把多件商品的入库记录都放在一行里用逗号分隔商品ID和数量那系统后期基本废了——统计、关联、追溯全部没法做。正确做法是拆成两张表入库主表stock_inid BIGINT PRIMARY KEY in_no VARCHAR(50) UNIQUE -- 入库单号 supplier_id BIGINT -- 供应商 in_type TINYINT -- 入库类型采购/退货/调拨 operator_id BIGINT -- 操作人 total_amount DECIMAL(10,2) -- 总金额 status TINYINT -- 审核状态 remark VARCHAR(500) create_time DATETIME入库明细表stock_in_itemid BIGINT PRIMARY KEY in_id BIGINT -- 关联入库主表 product_id BIGINT quantity INT unit_price DECIMAL(10,2) amount DECIMAL(10,2)主表记录“这一单是谁在什么时候从哪里入库的”明细表记录“这一单具体包含了哪几个商品、各多少数量”。这符合数据库设计的第一范式字段原子性也为后面“查询某张入库单的完整信息”提供了清晰的JOIN路径。出库表的设计完全对称stock_out主表 stock_out_item明细表。这么设计之后你要统计“本月入库总量”“某供应商累计供货金额”“某商品出库趋势”都是顺理成章的事情。3.3 库存表与流水表数据一致性的关键如果说主表和明细表解决的是“结构化存储”的问题那库存表和库存流水表解决的就是“数据一致性”的问题。库存表stock最简单的设计id BIGINT PRIMARY KEY product_id BIGINT UNIQUE warehouse_id BIGINT quantity INT -- 可用库存 locked_quantity INT -- 锁定库存可加可不加去掉也可以 update_time DATETIME为什么需要一个独立的库存表而不是直接去统计“所有入库数量减所有出库数量”因为性能。一个运行了几年的仓库系统可能有几十万条出入库明细每次查库存都做SUM聚合会非常慢。独立的库存表是一个典型的“以空间换时间”的冗余设计它的值通过每次出入库操作实时更新。库存流水表stock_log是另一个容易被忽略但极其重要的表id BIGINT PRIMARY KEY product_id BIGINT change_type TINYINT -- 1入库 2出库 3盘点增加 4盘点减少 change_quantity INT -- 变动数量正负号表示方向 before_quantity INT -- 变动前库存 after_quantity INT -- 变动后库存 order_no VARCHAR(50) -- 关联单号 create_time DATETIME这张表的意义在于可追溯。没有了流水表库存数据就变成了一个“只知当前、不知过往”的黑盒一旦某个数字对不上你根本没法排查。有了流水表你可以说“3月12日这单出库后该商品库存从50变成了30”有理有据。可以把库存表和流水表的关系理解为银行账户库存表是余额流水表是交易记录。银行不会只记余额不记流水仓库系统同理。4. 库存核心业务的实现从业务流程到代码4.1 入库业务的完整代码链路数据库设计完成后就可以写核心业务代码了。这里以“新增入库单”为例走一遍完整的代码链路。第一步Controller层接收请求RestController RequestMapping(/api/in) public class StockInController { Autowired private StockInService stockInService; PostMapping(/create) public Result? create(RequestBody Validated StockInDTO dto) { stockInService.createStockIn(dto); return Result.success(入库单创建成功); } }第二步Service层写核心逻辑。入库流程分两步保存入库单主表和明细表、增加库存并记录流水。这两步必须放在同一个事务里Service public class StockInServiceImpl implements StockInService { Autowired private StockInMapper stockInMapper; Autowired private StockInItemMapper stockInItemMapper; Autowired private StockMapper stockMapper; Autowired private StockLogMapper stockLogMapper; Override Transactional(rollbackFor Exception.class) public void createStockIn(StockInDTO dto) { // 1. 生成入库单号 String inNo IN System.currentTimeMillis(); StockIn stockIn new StockIn(); stockIn.setInNo(inNo); stockIn.setSupplierId(dto.getSupplierId()); stockIn.setInType(dto.getInType()); stockIn.setOperatorId(dto.getOperatorId()); stockIn.setStatus(1); stockIn.setRemark(dto.getRemark()); stockInMapper.insert(stockIn); // 2. 插入明细同时更新库存和流水 for (StockInItemDTO itemDTO : dto.getItems()) { StockInItem item new StockInItem(); item.setInId(stockIn.getId()); item.setProductId(itemDTO.getProductId()); item.setQuantity(itemDTO.getQuantity()); item.setUnitPrice(itemDTO.getUnitPrice()); item.setAmount(itemDTO.getUnitPrice() * itemDTO.getQuantity()); stockInItemMapper.insert(item); // 3. 更新库存如果库存记录不存在则新增存在则累加 Stock stock stockMapper.selectByProductId(itemDTO.getProductId()); int beforeQty stock null ? 0 : stock.getQuantity(); if (stock null) { stock new Stock(); stock.setProductId(itemDTO.getProductId()); stock.setQuantity(itemDTO.getQuantity()); stockMapper.insert(stock); } else { stock.setQuantity(stock.getQuantity() itemDTO.getQuantity()); stockMapper.updateById(stock); } // 4. 记录库存流水 StockLog log new StockLog(); log.setProductId(itemDTO.getProductId()); log.setChangeType(1); log.setChangeQuantity(itemDTO.getQuantity()); log.setBeforeQuantity(beforeQty); log.setAfterQuantity(beforeQty itemDTO.getQuantity()); log.setOrderNo(inNo); stockLogMapper.insert(log); } } }这里有个很重要的细节Transactional(rollbackFor Exception.class)。默认情况下Spring事务只在遇到RuntimeException时才回滚如果你不加上rollbackFor Exception.class某些业务异常抛出后事务可能不会回滚库存数据就错了。另外注意到代码里在更新库存前先查询了一次selectByProductId这属于“先查后改”的经典写法。在单用户操作场景下没有问题但在并发场景下会有隐患下面专门讲。4.2 出库扣减库存并发场景下的防超卖设计出库的逻辑反着来就行校验库存充足扣减可用库存记一条出库流水。但这里藏着仓库管理系统项目中最有技术含量的一问如果两个用户同时点击出库按钮库存会不会变成负数举个简单例子库存还剩10件A用户要出库8件B用户要出库5件。如果两个请求同时读到库存10然后各自计算并写回A写回2、B写回5最后库存反而变成了5B那单实际上把库存扣超了。这就是经典的“超卖”问题。解决方案有两种主流思路方案一悲观锁for updateSelect(SELECT * FROM stock WHERE product_id #{productId} FOR UPDATE) Stock selectByProductIdForUpdate(Param(productId) Long productId);在事务中通过FOR UPDATE把这条库存记录锁住其他事务必须等当前事务提交后才能操作。优点是实现简单、绝对可靠缺点是并发量高时性能会受影响。但对于毕设项目来说完全够用。方案二乐观锁版本号控制给库存表加一个version字段更新时校验版本号UPDATE stock SET quantity quantity - #{outQty}, version version 1 WHERE product_id #{productId} AND quantity #{outQty} AND version #{oldVersion}受影响行数为0则说明数据已被他人修改或库存不足需要重试或报错。两种方案在答辩时如果被问到建议的回答路径是先说明超卖问题的产生原因再对比两种方案的适用场景最后说明你的系统选用的是哪一种、为什么。推荐毕设直接采用乐观锁因为它不锁表、代码展示效果更好。出库核心代码如下Override Transactional(rollbackFor Exception.class) public void createStockOut(StockOutDTO dto) { String outNo OUT System.currentTimeMillis(); StockOut stockOut new StockOut(); // ... 插入主表 ... for (StockOutItemDTO itemDTO : dto.getItems()) { // 使用乐观锁更新库存 Stock stock stockMapper.selectByProductId(itemDTO.getProductId()); if (stock null || stock.getQuantity() itemDTO.getQuantity()) { throw new RuntimeException(商品ID: itemDTO.getProductId() 库存不足); } int updated stockMapper.deductStock( itemDTO.getProductId(), itemDTO.getQuantity(), stock.getVersion() ); if (updated 0) { throw new RuntimeException(库存更新冲突请重试); } // 记录流水... } }4.3 库存预警与盘点业务的实现思路仓库管理系统里最有展示效果的功能之一就是库存预警。实现思路很简单查询所有商品库存跟商品表里的safety_stock字段做比较低于安全库存就进入预警列表。SELECT p.product_code, p.product_name, p.safety_stock, IFNULL(s.quantity, 0) AS current_stock FROM product p LEFT JOIN stock s ON p.id s.product_id WHERE p.status 1 AND IFNULL(s.quantity, 0) p.safety_stock在首页仪表盘上把这个查询结果展示出来同时给“库存不足”的行标红视觉效果和专业性一下就出来了。如果有余力还可以加一个定时任务每天扫描预警数据给管理员发邮件或站内信通知。盘点的实现思路也不难新建一张盘点单选择要盘点的商品把实盘数量录入系统系统自动计算差异并在确认后调整库存、记录流水。核心伪代码如下Transactional public void confirmStockCheck(Long checkId) { // 查询盘点明细 ListCheckItem items checkItemMapper.selectByCheckId(checkId); for (CheckItem item : items) { Stock stock stockMapper.selectByProductId(item.getProductId()); int diff item.getActualQty() - item.getSystemQty(); if (diff ! 0) { // 动态调整库存 stockMapper.changeStock(item.getProductId(), diff); // 记录盘点流水 stockLogMapper.insert(StockLog.build(item.getProductId(), diff 0 ? 3 : 4, Math.abs(diff), stock.getQuantity(), stock.getQuantity() diff, 盘点单号: checkId)); } } // 更新盘点单状态为已完成 stockCheckMapper.updateStatus(checkId, 2); }5. 前后端分离的实战细节让项目真正能跑起来5.1 统一返回结构与分页接口约定仓库管理系统的后端接口强烈建议从一开始就统一返回格式。我常用的结构是{ code: 200, message: success, data: {} }定义一个泛型类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT error(String message) { ResultT r new Result(); r.setCode(500); r.setMessage(message); return r; } }这样前端处理响应时只需要判断code即可不用每种接口单独适配。分页接口建议返回统一的分页对象public class PageResultT { private Long total; // 总条数 private ListT records; // 当前页数据 private Long current; // 当前页码 private Long size; // 每页条数 }如果使用MyBatis-Plus分页查询非常简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }IPageProduct page new Page(current, size); IPageProduct result productMapper.selectPage(page, new LambdaQueryWrapperProduct() .like(StringUtils.hasText(keyword), Product::getProductName, keyword) .orderByDesc(Product::getCreateTime)); return PageResult.of(result.getTotal(), result.getRecords(), current, size);5.2 登录认证与权限控制的落地方式毕设阶段的登录认证最轻量实用的是JWT方案。流程是用户登录成功后后端生成一个Token返回给前端前端每次请求在请求头里带上Authorization: Bearer token后端通过拦截器校验Token有效性并解析用户信息。核心依赖dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency登录接口核心逻辑public String login(String username, String password) { User user userMapper.selectByUsername(username); if (user null || !BCrypt.checkpw(password, user.getPassword())) { throw new RuntimeException(用户名或密码错误); } if (user.getStatus() 0) { throw new RuntimeException(账号已被禁用); } // 生成JWTpayload放userId和username过期时间24小时 Algorithm algorithm Algorithm.HMAC256(your-secret-key); String token JWT.create() .withClaim(userId, user.getId()) .withClaim(username, user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() 24 * 60 * 60 * 1000)) .sign(algorithm); return token; }再写一个拦截器在doFilter中做Token校验校验通过后把userId放入ThreadLocal或请求属性后续接口就能拿到“当前操作人”。需要注意的是密码必须加密存储用Spring Security内置的BCrypt工具就行绝对不要明文存库这属于答辩翻车重灾区。5.3 联调过程中最容易出的问题前后端分离开发最常见的几类问题我在实际项目中遇到的频率大致如下跨域问题前端服务运行在8080端口后端在8081端口请求直接发过去会报CORS错误。解决方式是在后端加一个CorsFilter配置或者用CrossOrigin注解开发阶段推荐前者全局生效。时间格式不一致后端返回的LocalDateTime是数组或带T的字符串前端显示不正常。解决方式是在application.yml中配置统一时间格式化。分页参数丢失前端传的page、size和后端字段名不对齐。约定好参数名用RequestParam显式接收即可。大字段JSON序列化循环引用商品和分类之间如果建立了双向关联序列化时可能出现无限递归。解决方案是让DTO代替实体直接返回给前端或者用JsonIgnoreProperties忽略反向引用。这些不算高深的技术问题但每个都会卡住你好几个小时。建议开发时前后端接口对接一次成型减少联调返工。6. 源码交付、论文与答辩毕设的最后一公里6.1 源码交付前必做的规范化处理很多人以为毕设源码就是“代码能跑就算完事”实际上源码交付阶段的规范程度直接影响指导老师和答辩老师的评价。我建议在提交前做三件事第一写清楚README。一个完整的README应该包含项目技术栈列表、环境要求JDK版本、MySQL版本、Node版本、启动步骤初始化数据库、修改配置文件、启动后端、启动前端、默认管理员账号密码。这一步做完老师拿到源码后能自己把项目跑起来第一印象分就稳了。第二整理数据库脚本。不要把数据库文件直接托管在网盘里应该在项目根目录放一个sql/init.sql包含建库语句、建表语句、基础数据插入语句。注意脚本需要可重复执行使用CREATE DATABASE IF NOT EXISTS和INSERT INTO ... ON DUPLICATE KEY UPDATE这类写法。第三代码风格统一。包名统一使用com.xxx.warehouseService实现类命名统一XxxServiceImplController层的接口路径统一以/api开头。不要出现拼音命名、无意义的TestController、多余的System.out打印。6.2 准备演示数据和演示脚本答辩现场直接打开一个空数据库现场录数据这是最浪费时间的操作。提前准备一套演示数据集能让整个答辩过程流畅很多。演示数据设计思路至少准备20个商品、5个供应商、若干入库单和出库单、以及一条“库存低于安全库存”的商品记录让库存预警列表有内容可显示。再准备一条“库存充足”的商品用于现场操作出库演示。答辩时跟着这条脚本走登录系统展示首页仪表盘的统计数据和预警信息进入商品管理搜索刚才那个低库存商品进入入库管理新建一张入库单把那个低库存商品的库存补上去回到首页观察预警列表变化进入库存流水展示刚才入库操作产生的那条流水记录进入出库管理做一笔出库展示库存同步扣减效果一套流程走完系统的主要功能全部演示到位而且演示过程是逻辑连贯的不是“点开菜单随便看看”。6.3 答辩现场的高频问题与回答思路最后讲讲答辩。仓库管理系统这个题目的答辩问题我问过自己也问过学生翻来覆去其实就那几类“为什么选择SpringBoot做这个系统”核心回答点解决传统SSM框架的繁琐配置问题SpringBoot的自动配置和starter机制大幅提升开发效率Maven统一依赖管理内嵌Tomcat让项目可以独立运行便于部署演示。“库存扣减时怎么处理并发”核心回答点说明库存不足校验和扣减操作必须保证原子性。说出悲观锁和乐观锁两种方案的区别说明自己使用的是哪种。能主动说出“乐观锁通过版本号防止超卖失败后提示用户重试”这个回答就足够扎实了。“你的数据库表设计中为什么库存表要有单独的库存流水表”核心回答点单独库存表是为了避免每次都去聚合几百万条出入库明细提高查询性能库存流水表记录每次变动的来龙去脉两者结合才能既保证性能又保证可追溯。“系统有什么可以改进的地方”这是一个送分题但很多人答不好。建议提前准备两个方向引入Redis缓存热点商品库存和用户会话引入消息队列削峰处理高并发出入库请求。你说得越具体老师越能感觉到你是动过脑子的。顺便强调一句代码中有详细注释。别小看这点“代码可读性”在许多评分表里是明确加分项。写在最后的一点个人体会做了这么多年Java开发也帮不少朋友审过毕业设计我对仓库管理系统这个题目其实一直比较“偏爱”。它在技术上不吓唬人业务上逻辑清晰更重要的是它给你提供了一个完整理解“一个系统是怎么从表结构长成界面功能”的过程。哪怕不做毕设把它当成一个练手项目完整做一遍收获也远比刷几十集教程大。如果你正在做这个题目我的建议很简单先想清楚要做什么再设计数据表然后才是动手写代码。顺序千万别反。很多源码拿到手能跑但你自己写一遍才发现数据库少了一张表、某段事务根本没用、某处并发有坑。“能跑”和“能讲清楚”是两个等级的事而毕业设计考察的其实是后者。最后再分享一个小技巧代码注释不要写“这是什么”要写“为什么这样写”。比如// 这里使用乐观锁防止并发超卖比// 扣减库存有价值得多。别小看这一句话它能让你在一个月后回看代码时依然两三秒就想起当时的思考。祝顺利。