1. 这套系统到底能解决什么问题先说说我为什么对这个题目特别有感触。前一阵帮某高校一位同学看毕业设计题目就是“小型超市管理系统”他自己在宿舍楼下便利店打了两个月工太清楚老板每天最头疼的是什么了进货靠手写、库存靠脑子记、月底盘点要熬夜翻单据、会员积分永远对不上账。所以他一上来就跟我说不想做一个只为了过答辩的“玩具系统”想把真正店里会用的东西做进去。这个“02841小型超市管理系统”从标题就能看出信息量编号02841大概率是学校毕设选题库里的固定编号说明这是个被反复验证过的成熟题目不是一拍脑袋想出来的。而“案例分析”四个字意味着这套东西不仅有源码还要讲清楚设计思路和业务逻辑恰恰是很多同学在毕设答辩时最容易吃亏的地方——代码能跑但问不出为什么这么设计。从实际落地场景来看这套系统面向的是300平米以下、单品数量在1000到3000之间的社区超市、校园便利店、小卖部这类业态。它的核心使命只有三个管住商品、管住进出货、管住钱和账。所有功能模块都是围绕这三条主线展开的。如果你正在做类似的课题或者想从零理解一套管理信息系统是怎么从需求分析一路变成可运行代码的这篇文章把整个系统拆开给你看重点讲清楚每个模块背后的业务逻辑、数据库设计的原因以及直接能用的核心源码逻辑。2. 整体规划与数据库设计思路2.1 功能模块怎么划分才合理一套超市管理系统绕不开这几个核心模块商品管理、库存管理、进货管理、销售管理、会员管理、供应商管理、统计报表再加上最基础的用户登录和权限管理。这里有个很多同学容易犯的错误一上来就追求大而全恨不得把进销存、财务、人力全做进去。实际上对于毕业设计的体量来说模块越多意味着每个模块的平均深度越浅答辩时反而容易被问倒。正确的做法是抓住核心业务线把几个关键模块做深做透。上面说的这些模块里商品、库存、进货、销售是四大核心会员和报表是加分项权限管理是安全底线供应商管理可以合到进货模块里一起做。以实际操作的视角去拆解小型超市一天的业务流程是这样的早上开门收银员登录系统开始接单顾客挑选商品到收银台结账系统逐条扫描商品条码自动计算总价并扣减库存。如果某件商品库存低于预设的安全值系统需要在后台给出补货提示。老板或店长再根据这些提示结合供应商信息生成采购单到货后再做入库确认。这些流程串起来系统的基本骨架就清晰了。2.2 数据库表结构为什么这样设计表结构是整套系统的地基也是答辩时导师最喜欢深挖的部分。这套系统的核心表我建议至少包含这些用户表user字段包括用户ID、用户名、密码、角色。角色这里要注意不要只做一个简单的字符串字段推荐用单独的角色字段去区分管理员和收银员这样在权限控制时逻辑更清晰。商品表product商品ID、商品条码、商品名称、分类、规格、单位、进货价、零售价、库存上限、库存下限。条码字段一定要加唯一索引因为前台收银是拿条码来查商品的这是高频查询路径。库存上限和下限是后面要讲的库存预警功能的数据基础如果表里没有这两个字段预警只能写死在代码里非常不灵活。供应商表supplier供应商ID、名称、联系人、联系电话、地址。这个表看起来简单但要注意进货单据必须关联供应商ID而不是只存一个供应商名字否则后续供应商改名或者重名会直接让历史数据变成脏数据。进货表purchase和进货明细表purchase_item一张主表记录进货单号、供应商、进货日期、操作员、总金额一张明细表记录每一条进货商品的数量和进价。为什么要拆成两张表因为一张进货单会包含多种商品如果所有信息塞在一行里将来想查“这批货里某一款商品进价是多少”就会非常困难。这就是数据库设计里的主从表模式。销售表sale和销售明细表sale_item结构和进货表对称记录每一个销售订单的总价、收银员、时间以及每条商品明细。还有退货场景如果顾客买完不满意要退货系统需要有退货记录这个可以在销售表里加一个状态字段也可以用单独的退货表。小型超市建议用状态字段逻辑更简单不需要把退货拆成另一套主从表。库存表stock这是整套系统里最容易出错的地方。很多人会想库存数量直接写在商品表里不就行了确实可以这样设计但更规范的做法是有独立的库存表字段包含商品ID、当前库存数量、预警状态等。为什么呢因为将来做库存变动流水时需要忠实记录每一次进货入库、销售出库、盘点调整的数量变化。如果只在商品表里维护一个当前库存数字一旦数据出错完全没有追溯的依据。会员表member和积分记录表member_points会员表记录会员ID、姓名、手机号、累计积分、等级。积分记录表记录每次积分变动的来源和去向是用积分兑换商品还是在消费时获得积分。这两个表的逻辑不复杂但要注意结账时积分计算和消费金额之间的数值关联。2.3 表结构里容易踩的设计坑第一个坑是金额字段的类型选择。很多初学者喜欢用float或double来存金额这在Java和MySQL里都会产生浮点误差。比如一笔订单的金额是19.9元float类型存进去可能变成19.899999。银行、超市这类对金额敏感的系统的通用做法是用decimal类型Java对应BigDecimal。这个问题几乎每次答辩都会被问到提前处理了会显得你很专业。第二个坑是时间字段的处理。建议统一用MySQL的datetime类型Java里对应LocalDateTime。不要用字符串存时间否则后续做“查询某个月的总销售额”这类统计时会非常痛苦。SQL里对字符串写比较运算性能和准确性都不如真实的时间类型。第三个坑是条码的唯一性和索引。前台的收银查询是每秒级别的访问频率条码字段如果没有索引商品数量一多查询会明显变慢。在product表的barcode字段上建立唯一索引是一个成本极低但收益很高的优化。3. 核心技术难点与源码逻辑解析3.1 库存扣减怎么保证不出错库存扣减是超市系统里最核心的数据一致性场景。顾客结账时系统不仅要生成销售记录还要同步把库存表里的数量减掉。这一步如果做不好就会出现“前台收了钱后台库存没变”的严重问题。从代码逻辑上讲库存扣减有两种做法第一种是逻辑删除再插入。先把当前库存查出来在Java里减掉1再写回数据库。这种做法存在一个问题如果两个收银员同时卖出同一商品两个人同时读到同一个库存数字各自减1后写回结果只减了1而不是2。这就是经典的并发覆盖问题。第二种是SQL原子更新。直接执行一条更新语句update stock set quantity quantity - 1 where product_id ?。这条SQL在数据库层面是原子操作数据库会保证同一时间只有一次更新生效。强烈建议采用第二种方式把并发问题交给数据库去处理而不是在应用层做复杂的锁控制。进货入库时同理update stock set quantity quantity 1 where product_id ?成本价和售价等信息维护在商品表里。3.2 销售和扣库存怎么保证一致性光有原子更新还不够。如果销售单插入成功了库存扣减失败了又会出现数据不一致。这里有两种常见处理方式一种是手动管理事务在代码里先开启事务执行完“插入销售单”和“更新库存”两条操作后统一提交任何一步失败就回滚。另一种是利用框架的事务注解比如Spring里的Transactional让框架帮你管理事务边界。下面这段代码是商品入库逻辑的参考实现核心思路是先开启事务再同时维护进货主表和库存表Transactional public void stockIn(StockInDTO dto) { // 生成进货单号例如 IN20250601001 String purchaseNo generatePurchaseNo(); // 1. 写入进货主表 Purchase purchase new Purchase(); purchase.setPurchaseNo(purchaseNo); purchase.setSupplierId(dto.getSupplierId()); purchase.setTotalAmount(dto.getTotalAmount()); purchase.setOperator(getCurrentUserId()); purchase.setCreateTime(LocalDateTime.now()); purchaseMapper.insert(purchase); // 2. 逐条处理进货明细写入进货明细表并更新库存 for (PurchaseItemDTO item : dto.getItems()) { PurchaseItem purchaseItem new PurchaseItem(); purchaseItem.setPurchaseId(purchase.getId()); purchaseItem.setProductId(item.getProductId()); purchaseItem.setQuantity(item.getQuantity()); purchaseItem.setPrice(item.getPrice()); purchaseItemMapper.insert(purchaseItem); // 3. 用原子更新维护库存数量 stockMapper.increaseStock(item.getProductId(), item.getQuantity()); } }执行原理很简单方法上加了Transactional注解Spring会在进入方法时开启一个数据库事务所有数据操作都在这同一个事务里执行。方法内任何一步抛出异常Spring会检测到并执行回滚之前已经执行的insert和update都会被撤销不会出现“只写了进货单没加库存”这种半截状态。销售出库的实现逻辑和这个对称区别是库存操作从increaseStock改成decreaseStock同时多了一步计算订单总价和累计会员积分。3.3 库存预警怎么触发才合理超市里最怕两种状况一种是热销商品卖断货了顾客来了买不到另一种是冷门商品囤了一堆占用资金和货架。库存预警功能就是用来减少这两种状况的。实现逻辑很简单商品表里设置了库存上限和下限每次出入库操作完成后检查当前库存是否低于下限。如果低于系统就要在界面上给出提示比如后台首页展示一个补货提醒列表或者给该商品打上“库存不足”标记。这里有一个值得优化的点不要用定时任务去做库存预警扫描。原因是实时性不够而且每次全表扫描对数据库也是一种负担。更优雅的做法是在每次库存变化时触发检查只对发生变化的商品做评估。之前帮某同学做系统时他一开始就是每天早上定时扫描结果下午某个商品已经卖光了他还不知道。改成实时检查之后断货提醒基本能做到秒级响应。3.4 报表统计的SQL怎么写效率最高报表功能是答辩时的加分项也是很多同学觉得难啃的骨头。最常见的需求有今日销售额、本月销售额、热销商品Top10、库存积压商品列表。这几个需求都可以用SQL的聚合函数来实现。比如热销商品Top10思路是对销售明细表按商品ID分组用SUM函数统计每个商品的销售数量再按数量降序排列取前10条记录SELECT si.product_id, SUM(si.quantity) AS total_quantity, SUM(si.amount) AS total_amount FROM sale_item si INNER JOIN product p ON si.product_id p.id WHERE si.create_time 2025-06-01 GROUP BY si.product_id ORDER BY total_quantity DESC LIMIT 10;这条SQL的执行逻辑是先用时间条件过滤出这个月的销售明细然后按商品ID分组累加每个商品的销售数量最后排序取前10。关联product表是为了在结果集里直接显示商品名称避免在Java代码里再写一遍按ID转换名称的逻辑。月度销售额统计则更简单关键在日期处理。硬件条件是create_time字段必须是datetime类型才能直接用DATE_FORMAT按月份聚合。假设你要统计当年1到6月每月的销售额可以先按月份分组再用SUM加总金额字段SELECT DATE_FORMAT(create_time, %Y-%m) AS month, SUM(total_amount) AS monthly_sales FROM sale WHERE create_time 2025-01-01 AND create_time 2026-01-01 GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month;注意这里用create_time 2026-01-01而不是create_time 2025-12-31 23:59:59这是为了避免12月31日最后一秒的数据漏统计。写成闭区间看起来没问题但边界情况处理不好的话月末最后一笔订单会计不进报表。这是一个很容易被忽略的细节但在实际运营中可能就是几百块钱的差异。3.5 收银台的交互设计要点收银是前台使用频率最高的功能首要设计目标是让收银员在几秒钟内完成一次结账操作。所以界面交互逻辑要以录入速度为核心来考虑。早期版本的常见做法是提供一个输入框收银员通过扫码枪录入条码或者手动敲入条码后按回车系统立即查询并加入结算列表同时显示商品名称、单价、数量快捷键直接完成结账。另一个实用细节是支持按商品编码模糊查询也就是商品的关键字搜索。因为小超市里有一种常见情况商品条码残破或者某种散称商品总是记不住条码收银员需要输入商品名称的拼音首字母或部分名称来查找。有搜索功能会方便很多它不光是应急手段也是收货员熟悉新品时最常用的寻货方式。还有一个容易被忽略的点是购物车列表里的数量修改和删除功能。顾客结账时经常说“这个不要了”“这个再多拿一个”如果系统不支持在结算列表里直接改数量或移除行项目收银员就要重新扫一遍体验会很差。4. 从能跑到好用的核心功能细节4.1 会员积分与折扣的价值小超市的会员机制和大型商超不太一样不求复杂但求实用。最基本的场景是顾客消费满一定金额能积分积分可以用来抵现或者换购商品。这套逻辑做在系统里并不复杂但在答辩时可以讲出很多业务思考是一个能加分的模块。积分累计的时机是在销售出库成功之后根据订单金额计算本次赠送积分。积分抵现的时机是在结算时勾选使用积分按一定比例抵扣金额。要注意积分抵现的金额在账目上必须单独记录不能直接混入商品销售金额否则对账时会对不上。会员等级的提升要考虑累计消费额还是累计积分两种模式各有优劣。如果等级提升只看得积分那会员可以通过参加积分翻倍活动快速升级系统承担的活动成本会偏高。实际设计中往往优先使用累计消费额来定等级驱动顾客持续消费比单纯积分活跃更直接。4.2 供应商信息与进货单据的联动逻辑供应商模块看起来是最简单的CRUD但和进货单联动时有一个细节值得仔细设计一个供应商供多种商品一张进货单也可能混合多个供应商的货。处理方式是把供应商做成一张独立的表进货单通过字段关联供应商ID而不是把供应商名称直接嵌进进货单的某一行里。这是数据库第三范式的基本要求也便于将来做供应商维度的统计比如查询某个供应商本月的供货总额。进货单审核后正式生效库存增加。如果发现进货单录入错误比如商品数量填错了怎么处理不建议提供“删除进货单”的功能因为进货单已经对不上库存流水了。更规范的做法是审核过的单据只允许做一笔冲抵单用数量为负的退货出库来冲掉之前多出来的库存再重新录正确的进货单。这样每一步操作都有记录库存流水永远能解释库存变动的原因。这个思路虽然多了一点复杂度但能保证数据永远可追溯。4.3 收银结算异常时怎么介入超市里总有台收银机突然断网或者扫码枪坏了的情况。系统需要考虑这种异常场景下的兜底方案。比较简单的设计是销售单在本地客户端生成草稿状态网络恢复后再提交到服务器。如果整体是单机版系统且没有前后端分离也可以退级为收银员手工录入结算。关键是不因为某个硬件故障就让整个商店停业收不了款。也可以做一个离线收银的小改良库存充足性在结算时做了本地提示如果网络暂时断开先把订单记录到一个本地文件中等恢复后再通过接口导入。当然这个设计对于毕业设计来说有点超前但如果你能在答辩时讲出这样一个思考效果会远超那些只会念代码的同学。4.4 界面布局与操作效率的平衡管理系统的界面不需要多花哨但操作效率上的打磨能明显提升体验感。最基本的原则是高频操作要用鼠标和键盘都能高效完成收银台界面要支持完全键盘操作商品搜索框要自动聚焦回车触发查询快捷键对应“结账”“挂单”“退货”。系统主界面比较适合用左侧导航树加右侧详情区的布局。左侧按“前台收银”“商品管理”“进货管理”“销售管理”“库存管理”“会员管理”“统计报表”“系统设置”分组右侧对应展示表格或操作表单。列表页需要支持分页和条件查询不要一次性把几千条商品全部加载出来。这也是很多初学者爱犯的错——图省事全部查出来结果数据一多页面就卡死。5. 常见问题与排查实录5.1 中文乱码问题这个问题的出现频率在实操中能排到前三。现象是界面上显示的中文全部变成问号或乱码。排查方向有三个数据库连接URL是否带了characterEncodingutf8参数数据库表本身的默认字符集是不是utf8mb4JDBC驱动的版本是不是太老导致编码参数没生效。这三处都改对了乱码基本能解决。5.2 销售金额对不上现象是报表统计的总销售额和前台现金抽屉里的钱对不上这种问题的排查思路是先查销售明细表和销售汇总表的差异有差异再去翻库存流水表检查是不是有退货订单没正确处理。如果退货时把商品库存加回去了但没记录到销售退货单里就会造成销售汇总虚高库存数量虚低。另一个容易被忽略的场景是使用积分抵现的订单。如果积分抵现的部分被代码混入商品销售金额去统计就会导致销售额虚高。建议把积分抵现额单独设计为支付方式的独立一行记录例如“商品原价合计100元积分抵现10元实收现金90元”这样报表统计时就能清楚地区分销售额与优惠扣减。5.3 并发结账时的库存超卖这是一个很大的典型坑。两个收银员同时扫同一个商品两边都先查库存都发现还有一件两边都卖给了各自的顾客最后库存变成负数。解决方案前面讲过不要用“先查再改”的模式要直接用SQL的原子更新。如果因为某些原因必须用“先查再改”至少要在事务里使用SELECT ... FOR UPDATE对那行库存记录加锁否则并发问题一定存在。5.4 系统部署时连不上数据库常见原因有三类数据库服务没有启动端口被占用或没被防火墙放行MySQL默认安装时只允许localhost登录而项目配置了远程地址。毕设演示时大都在自己的电脑上跑最容易犯的错误是忘记把MySQL服务启动起来或者是项目里配置的root密码和本地MySQL实际密码不一致。建议把数据库配置独立写到properties文件里养成不把账号密码硬编码在代码里的习惯答辩时提到这点会显得工程素养很好。5.5 常用SQL报错速查这里按实际踩坑频率整理了一份速查表可以直接截图留作参考。报错信息常见原因解决办法Unknown column xxx in field listSQL里的字段名写错和表结构不一致核对数据库表里的真实列名注意下划线Duplicate entry xxx for key PRIMARY主键冲突插入时给了已存在的ID主键改用自增策略或者插入前先查询Cannot add or update a child row外键约束失败引用了不存在的父表ID检查关联表的记录是否存在先插父记录Data too long for column xxx字符串长度超过字段定义调大varchar长度或改用text类型Lock wait timeout exceeded事务长事务导致锁等待超时检查是否有未提交的事务优化事务粒度6. 源码里值得重点阅读的代码片段6.1 数据库连接与工具类不管用什么框架数据库连接都值得单独封装成一个工具类。下面这是一个参考的连接管理方式基于传统的JDBC方式适合课程设计和毕业设计的基础版本public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/supermarket?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USERNAME root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }之所以要写三个close判断而不是直接统一关一个连接是因为如果某一步获取失败会返回null直接close会抛NullPointerException。这个工具类里URL带上了serverTimezone参数这是新版MySQL驱动必须加的时区配置不加会直接报错。以及characterEncodingutf8确保中文不乱码。6.2 商品查询与分页实现商品列表是后台最常用的查询功能直接查全部数据会导致页面渲染慢。分页查询是最基础的优化策略一次只查一页的数量配合关键字搜索条件。public ListProduct queryProductByPage(String keyword, int pageNum, int pageSize) { String sql SELECT * FROM product WHERE name LIKE ? OR barcode LIKE ? LIMIT ?, ?; // 省略获取连接的代码 ListProduct list new ArrayList(); try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); ps.setInt(3, (pageNum - 1) * pageSize); ps.setInt(4, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getInt(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(retail_price)); list.add(p); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这条SQL里有两点值得注意第一是LIKE条件的写法%keyword%表示模糊匹配能同时查名称和条码第二是LIMIT ?, ?里的第一个参数是偏移量计算方式是页码减一乘以每页条数这是JDBC分页里最容易算错的地方经常把limit写成limit pageNum, pageSize导致第一页正常后面全乱。6.3 登录模块和密码安全处理用户登录模块是系统的安全入口也是答辩时导师必看的地方。最基础但是不该省略的安全措施是密码不能明文存储。参考实现里可以用MD5加盐或者SHA-256哈希存储。给出一段MD5加盐的参考代码演示思路比直接复制更重要public static String md5WithSalt(String password, String salt) { String content password salt; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(content.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(b 0xff); if (hex.length() 1) { sb.append(0); } sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(MD5 algorithm not available, e); } }这段代码的要点是MessageDigest.getInstance(MD5)获取摘要算法实例digest方法计算哈希结果字节数组每个字节转成十六进制字符串。加盐的作用是防止两个相同密码的用户得到完全相同的哈希值盐值可以取用户名或者注册时间戳。虽然MD5在专业领域已经不算最安全但作为毕业设计来讲比直接明文存储高出了一大截至少体现了安全意识。如果能力允许可以把加密方案升级为加盐SHA-256效果更好。6.4 库存预警的触发逻辑前面讲过不要用定时扫描而是采用实时检查的方式。下面给出一个参考思路在销售扣减库存后立即查询一次该商品的当前库存再和商品的预警下限比较如果低于下限就记录一条提醒public void afterSaleDeduct(int productId, int quantity) { // 1. 原子扣减库存 stockMapper.decreaseStock(productId, quantity); // 2. 查询最新库存和预警值 ProductStockInfo info stockMapper.getStockInfoWithWarning(productId); if (info.getCurrentStock() info.getWarningStock()) { // 3. 生成预警记录 warningMapper.insert(new StockWarning(productId, info.getCurrentStock(), LocalDateTime.now())); } }这里的getStockInfoWithWarning方法通过一条SQL联表查询商品表和库存表把当前库存和预警下限一次性取出来避免两次数据库往返。预警记录表的存在价值是可查询、可追溯系统首页的补货提醒列表可以直接查这张表。7. 这套系统还能怎么扩展很多同学的毕业设计做完就放下了其实这个题目只要愿意深挖能延伸出很多有价值的方向。这里列几个我实际见过或自己在项目里尝试过的扩展点每一个都能单独成为一个章节来讲述。第一个是过期批次管理。超市里有大量食品饮料进货时间不同保质期不同如果不按批次管理容易出现“先进先出”原则无法落实、过期商品还在货架上卖的情况。加一张批次表在商品维度之下再挂一个入库批次维度的库存销售出库优先扣批次早的库存这就是一个完整且很有商业价值的模块。第二个是盘点功能。现实中超市每隔一段时间就要盘点库存盘点时发现账实不符是常态。系统支持生成盘点单、录入实盘数量、计算盘盈盘亏并自动生成调整记录。这个模块做出来会让你的系统在答辩时明显比别人更有亮点。第三个是数据备份策略。系统里的销售数据、进货记录几年下来会积累很多如果MySQL文件直接损坏损失非常大。可以在系统里做一个“一键备份”功能调用mysqldump命令把整个数据库导出成SQL文件存到本地磁盘指定目录。再定一个简单的策略每天自动备份一次保留最近三十天的备份。就这一件事能让你系统在稳定性层面超过大多数毕设作品。第四个是引入图表统计。做成卡片式的今日销售额、订单数、客单价再配一张近七天的销售趋势折线图。技术上可以用前端图表库如果用的是Java桌面端Swing可以用开源图表组件实现柱状图和折线图效果远好于纯文字表格。8. 经验总结做完这套系统的实际操作体会这套系统我前前后后陪不同的同学梳理过多遍也在实际小超市的运营调试中见过它被真正使用起来的样子。每次看这套代码或者类似的进销存项目感触最深的不是某个技术点怎么实现而是“业务理解”才是管理系统真正的分水岭。比如库存预警。代码本身很简单就是把当前库存和下限做个比较。但如果你不知道超市老板真正怕的是热销品断货你不知道他每天最关心的是哪几瓶水卖光了你写出来的预警模块就只能是一个干巴巴的数字对比。而当你理解了业务你会主动去思考预警列表该放在哪个界面、该按什么排序、该用多大的字体提醒这些细节才决定了一个系统好不好用。另一个体会是不要怕在答辩时承认某些功能的价值胜于花哨的代码。某一次交流时一位同学说自己根本没做什么复杂算法不过是把每个表的关系想清楚了把每次操作的事务边界划清楚了把每个查询的SQL写高效了。结果评委导师直接给了高分评价是“像个真正的软件工程师在做系统”而不是为了炫技堆砌一大堆用不上的框架。这话我至今都觉得很有道理。最后分享一个实在的小技巧。开发调试阶段给数据库多准备几组真实的商品测试数据比如矿泉水、方便面、火腿肠这些便利店真实会卖的东西单价有整有零。这样测试时会发现很多在假数据阶段发现不了的问题比如0.01元的金额精度问题、同价不同商品的区分问题。有一次测试尤其深刻用1.5元的矿泉水测试连续结算五十笔后发现有一个显示的金额是49.98而不是50元整就是因为有个中间步骤把单价用double存了一次精度丢了一点点。当场处理掉这个问题后才真正意识到BigDecimal不是小题大做而是做金额系统的基本底线。如果你正在做这个题目把我上面讲的这几点逐一对照检查一下表结构是不是规范到了第三范式核心的进出库操作是不是都有事务保护金额字段是不是都用decimal库存扣减是不是原子操作密码是不是脱了明文。这几道关过了你的系统就已经不是“能跑”的水平而是“站得住”的水平了。