
我第一次在毕设选题表里看到“XX管理系统”这类题目时心里多少有点抗拒。倒不是觉得管理系统没价值而是这类系统太容易被做成千篇一律的增删改查前台收银、商品管理、订单查询页面换一换数据库字段换一换本质上一模一样。答辩时老师问一句“你的系统技术亮点在哪里”很多人就卡住了。后来我把题目定成“基于SSM框架和协同过滤算法的药店销售管理系统”这个问题才算彻底解决SSM负责把业务系统做扎实协同过滤负责让系统多出一个讲得出口的推荐模块业务和算法兼得工作量也足够撑起一篇完整毕设。这篇文章就是来完整复盘这个项目的做法。从业务场景拆解、SSM工程搭建、数据库建模到协同过滤算法落地、前后端联调、部署和答辩准备我会把实际开发中踩过的坑、当时没想明白后来才懂的逻辑、以及可以直接照抄的配置一并写出来。适合三类人看一是准备做Java方向毕设的同学需要一套能讲清楚技术方案的管理系统二是对协同过滤算法在真实场景落地感兴趣的人三是刚拿到别人源码包但跑不起来不知道从哪里下手的新手。1. 先把题目掰开揉碎药店场景里真正要解决的事1.1 走一遍药店的日常采购、库存、收银、报表做系统之前一定要先去理解业务而不是先想表结构。我那时专门找了一家小区周边的药店观察了一个下午。药店的日常大概可以拆成四条线。第一条线是采购入库。药店需要定期向供应商订货货到了以后验货、清点数量然后把药品信息录入系统同时标记批号和有效期。这件事在Excel里做非常痛苦尤其是药品品种一多哪个批号还剩多少、哪批药快到有效期全靠人脑记记不住就会积压过期药。第二条线是药品管理。药品和普通商品不一样它有分类、厂家、规格、剂型、单位、进价、售价还需要一个预警库存。比如某种降压药低于50盒就要提醒采购否则断货会影响老客户。虽然我们做的是毕设项目但字段设计必须向真实业务靠拢答辩时才有话可说。第三条线是前台收银。顾客来买药可能是现金、扫码也可能是老会员报手机号来结算。收银员需要快速找到药品录入数量计算总价然后减库存。注册会员之后用户每一次购买行为都会变成推荐模块的原始数据。第四条线是统计报表。店长关心的是这个月卖了多少、哪些药卖得好、哪些药长期不动。系统里需要提供按月份、按药品分类、按会员维度的统计图表这部分的SQL和可视化是论文里的常规亮点。这四条线连起来就是药店销售管理系统的核心闭环。需求分析阶段把这条业务流理清楚后面所有设计都有依据。1.2 为什么这类系统容易做“烂”以及怎么加入亮点我见过很多毕设管理系统的问题大多不是功能不全而是“没有数据深度”。所谓数据深度就是系统里存的每一笔数据它不仅仅被展示出来还能被进一步分析和使用。比如订单表多数管理系统只用来查流水、算总金额仅此而已。但在药店场景里订单明细其实就是一份“用户-药品”的购买行为记录。我们能从里面算出哪些药经常被一起购买某些老会员的复购周期是什么某种药最近30天的销量趋势如何。这些分析一旦做进去系统就从“登记台账”变成了“数据工具”。我加协同过滤模块的思路也是这样不做大而全的推荐平台只做一个小而清晰的推荐区——当收银员选中某个会员后系统根据该会员的历史购买记录自动展示“猜他需要”的几款药品。这个功能在论文里可以写一整章算法设计在实际演示中也能直观看到效果。相比单纯堆功能这种“一技之长”更容易拿到高分。2. SSM框架选型与工程搭建手写配置比想象中有价值2.1 为什么是SSM不是Spring Boot很多同学一开始都问我现在公司里都用Spring Boot为什么毕设还要用SSM答案分两层。第一层是题目要求和技术评审。直到现在仍有相当多高校的毕设题目和课程设计沿用SSM评审老师对这个组合非常熟悉他们很容易判断你的代码结构是否标准、是否真的理解了框架原理。Spring Boot虽然省事但大量配置被自动化了反而容易让论文里的“关键技术”部分写不出深度。第二层是个人成长。手写SSM配置的过程本质上是把Spring IoC、SpringMVC请求流程、MyBatis会话管理这些底层概念逐一实践一遍。等到你以后再切到Spring Boot会理解得更透学东西只是适应差异而已。所以我的建议很明确如果题目明确要求SSM就踏踏实实写配置如果题目没指定也可以用Spring Boot但论文里要补上“自动化配置的原理解析”来体现深度。这篇讲的是SSM路线。2.2 目录结构、依赖版本和三个核心配置文件SSM项目不像Spring Boot那样一键启动它需要我们手动组织工程。我的目录结构大致是这样src/main/java ├─ com.pharmacy.controller // 控制层接口入口 ├─ com.pharmacy.service // 业务层接口 ├─ com.pharmacy.service.impl // 业务实现 ├─ com.pharmacy.dao // MyBatis Mapper接口 ├─ com.pharmacy.entity // 实体类 └─ com.pharmacy.common // 公共工具MD5加密、结果包装类等 src/main/resources ├─ jdbc.properties // 数据库连接配置 ├─ mybatis-config.xml // MyBatis全局配置 ├─ spring-context.xml // Spring核心配置数据源、事务、Mapper扫描 ├─ spring-mvc.xml // SpringMVC配置注解驱动、视图解析器 └─ mapper // 各实体对应的Mapper XML文件 src/main/webapp ├─ static // CSS、JS、图片 ├─ WEB-INF │ ├─ web.xml // 启动配置、DispatcherServlet、编码过滤器 │ └─ views // JSP页面版本上我建议组合为JDK 1.8、Spring 5.3.x、MyBatis 3.5.x、MySQL 8.0、Tomcat 9。这是最稳妥的组合网上资料多遇到问题容易查到。核心依赖在pom.xml里大概是这样dependencies dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.29/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.29/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependenciesweb.xml是SSM的入口里面两个配置最重要。一个是字符编码过滤器必须放在所有过滤器的最前面filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping另一个是DispatcherServlet它接管所有前端请求servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mappingspring-mvc.xml里要开启注解驱动配置视图解析器并放行静态资源。spring-context.xml里则配置数据源、事务管理器和Mapper扫描。这里有一个容易忽略的点MyBatis的Mapper接口扫描和Mapper XML扫描要配对配置否则DAO层会注入不进去。2.3 跑通“Hello World”前的版本对齐细节第一次搭框架时我栽在数据库驱动上。MySQL 8.0的驱动类名和MySQL 5.x不一样jdbc.properties里必须写成jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/pharmacy_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456如果还按老教程写com.mysql.jdbc.Driver启动后直接报ClassNotFoundException。同理Tomcat版本上我建议别用Tomcat 10因为Tomcat 10把javax.servlet换成了jakarta.servlet很多老项目跑不起来Tomcat 9最省心。3. 数据库设计一张订单明细表是如何同时服务业务和算法的3.1 核心表结构从用户到销售的完整闭环数据库是管理系统的地基表设计好坏直接决定后面写SQL的难度。我梳理的核心表如下表名作用关键字段sys_user后台登录账号id, username, password, rolemember会员信息id, name, phone, create_timedrug_category药品分类id, name, descriptiondrug药品资料id, drug_code, name, category_id, manufacturer, specification, unit, purchase_price, sale_price, stock, warn_stocksupplier供应商id, name, contact, phone, addressstock_in入库记录id, drug_id, supplier_id, quantity, cost_price, batch_no, expire_date, create_timesale_order销售订单主表id, order_no, member_id, emp_id, total_amount, create_timesale_order_item销售订单明细id, order_id, drug_id, quantity, sale_price会员表单独建是为了让推荐模块有“用户”维度。sys_user只负责后台登录member才代表真实购买者。如果你只做管理员和收银员两种角色也可以简化成一张user表但会员那张表一定要独立因为协同过滤的核心输入就是会员ID和药品ID的对应关系。3.2 订单主表和订单明细为什么要分开刚学设计时我一度想为什么不能一张订单表把药品都拼成一个字符串存进去做起来你就知道这种设计在统计时几乎寸步难行。你想知道某个月某款药卖了多少如果用拼接字符串只能把每条记录拉出来拆字符串数据量稍大就崩SQL也丑。订单主表存订单级别的信息订单号、会员、收银员、订单总金额、下单时间。订单明细表存药品级别的信息属于哪个订单、哪个药品、数量、当时的售价。主表一条记录对应明细表多条记录。这样设计之后几十个SQL都变得非常顺查订单列表是主表查询查畅销药品是对明细表按drug_id分组求和查会员关联购买记录是明细表关联member。下单的完整链路包含查库存、扣库存、插订单主表、插订单明细四件事它们必须在同一个事务里完成否则可能出现“订单生成了但库存没扣”的严重数据错误。3.3 库存、批号与有效期的设计取舍药品库存管理有两个细节值得说一下。第一个是库存数量放在哪里。毕设级别可以把当前库存直接放在drug表的stock字段里同时设置warn_stock预警值。这样的优点是查询简单列表页能直接过滤预警库存。缺点是没有任何库存流水如果想追溯“某天库存为什么少了”得靠订单去推。如果希望更完整可以再加一张库存流水表inventory_log每次出入库都写一条日志。我建议核心用drug表字段流水作为论文的进阶设计来写。第二个是批号和有效期。真实药店是按批入库的不同批次进货价可能不同有效期也不同。毕设阶段为了控制复杂度可以在stock_in表里留下batch_no和expire_date两个字段入库时记录列表页可以根据过期日期筛选“临期药品”但不需要单独建批次库存明细。这个粒度对毕设来说足够论文里也能交代清楚。3.4 留给推荐算法的“数据接口”推荐模块要的数据不在额外的地方就在sale_order_item里。表结构里只要保证member_id和drug_id能正确关联我们就能源源不断地从历史订单中提取用户行为。实际实现推荐服务时我的思路是从sale_order_item关联sale_order表查询近90天所有有会员身份的购买记录。为什么选90天因为药品消费有时效性感冒药买过一次可能半年都不会再买但降血压药这种慢病药几乎每月复购。取近90天能兼顾时效性和数据量。这个口径如果在论文里写出来会显得你是认真考虑过业务逻辑的。4. 协同过滤推荐模块在老客户的购买记录里找规律4.1 选UserCF还是ItemCF先想清楚推荐给谁协同过滤分两大类基于用户的协同过滤UserCF和基于物品的协同过滤ItemCF。UserCF的思路是“和你相似的人买了什么就推荐给你”ItemCF的思路是“你买过的物品相似的物品才是你想买的”。药店的业务场景有一个特点会员群体相对固定很多是慢性病患者复购率高。从这个角度看UserCF天然适合“老客户复购提醒”。但UserCF也有明显的毛病需要实时计算用户之间的相似度一旦会员数量增长计算成本上升很快而且新会员没有任何历史行为根本找不到“相似的人”。ItemCF的算法实现更直观对每个药品统计其被哪些人购买过当用户已经买了A药品系统就找出所有与A最相似的药品推荐给用户。比如买过感冒灵的人大概率也买过退烧药、维生素C这和药店的销售直觉完全一致解释起来非常自然。所以我的推荐模块选择ItemCF为主算法UserCF作为论文中的对比方案来写。这样既有实现的深度又可以展开对比分析工作量也更丰富。4.2 药品相似度计算与推荐列表生成ItemCF的核心分两步求药品之间的相似度然后根据用户已购买的药品生成推荐。求相似度的方法有很多常用的是余弦相似度。具体到药店场景我们可以为每个药品构造一个“会员购买向量”向量的维度是所有会员值是该会员购买该药品的次数。两个药品的余弦相似度就是这两个向量夹角的cos值越接近1表示越相似。但全量计算所有药品两两相似度在毕设规模下可以承受但也要注意效率。我的做法是先用一个Map统计“每个药品被哪些会员购买”再只对“至少有几个共同购买者”的药品对计算相似度避免把零相似度的数据也跑一遍。核心逻辑用伪代码表示如下1. 从sale_order_item join sale_order查询近90天的购买记录 2. 构建MapdrugId, SetmemberId记录每个药品被谁买过 3. 遍历所有药品对(a, b) 公共购买者 setA ∩ setB 如果公共购买者数量 2跳过 计算sim |setA ∩ setB| / sqrt(|setA| * |setB|) 保存到Mapa, Listb, sim 4. 对于目标用户已购买的药品集合P 候选药品 所有与P中药品相似且用户未购买过的药品 推荐得分 sum(药品相似度) 按得分降序取TopN这段逻辑写到Service里用一个定时任务每天刷新一次相似度结果存到缓存Map中。推荐接口只做查询和拼接响应速度会很快。不要每个请求都重新算一轮算法那是性能灾难。4.3 冷启动新会员和新药品怎么推荐协同过滤最大的软肋是冷启动。一个新会员没有购买记录任何推荐逻辑都拿不到输入特征。完全不给推荐又会显得系统很呆。我的解决方案是加一个兜底策略没有历史行为时直接按销量返回热门药品TopN。这类“热门榜”虽然个性化程度低但至少页面有内容用户也不会觉得突兀。新药品的冷启动也同样处理。刚上架的药品没有购买记录它永远不会进入相似度矩阵自然也不会被推荐出来。我在列表页单独加了“新品上架”区域让新药品先被看见等积累了一定订单量后再自动参与推荐。4.4 算法性能优化和论文里可以写的指标论文里如果只写“我用协同过滤实现了推荐”是不太有说服力的。最好补上评价部分。离线评价最简单的做法是把历史订单按时间切分前70%作为训练集后30%作为测试集对测试集中的会员生成推荐计算三个指标准确率推荐列表中被用户实际购买的药品数 / 推荐列表总药品数召回率推荐列表中被用户实际购买的药品数 / 测试集中用户购买的所有药品数覆盖率推荐出的药品种类数 / 全部药品种类数这些指标写出来就是完整的“推荐算法实验”章节。不需要很复杂的数学推导只要把计算方式讲清楚再把结果列出来分析一遍工作量就非常饱满。5. 业务功能模块实现从药品目录到销售报表的完整链路5.1 商品维护与库存预警药品管理模块就是常规的增删改查但有几个点需要按真实药店来设计。药品编码drug_code应该设置为唯一索引不能重复药品名称、厂家、规格、单位、进价、售价、预警库存要齐全。录入页面需要做表单校验售价和库存不能为负数这是基础。库存预警的SQL很简单SELECT drug_code, name, stock, warn_stock FROM drug WHERE stock warn_stock ORDER BY stock / warn_stock ASC;页面上的表现是我把库存数量做成红色高亮一眼能看到哪些药该补货了。这个功能演示起来很直观我准备演示数据时特意把几款常用感冒药的库存压到预警线以下。5.2 收银下单一个事务要保护的所有操作收银模块是整个系统的核心也是最容易写乱的地方。页面交互大致是收银员在左侧搜索药品、点击加入购物车右侧生成临时购物车列表最后点击结算。结算的后端方法必须用Transactional注解内部按以下顺序执行Transactional public void createOrder(SaleOrderDTO dto) { // 1. 遍历购物车中的每个药品校验状态和当前库存 // 2. 计算订单总金额 // 3. 插入sale_order主表生成订单号 // 4. 遍历购物车写入sale_order_item明细 // 5. 执行update drug set stock stock - quantity }这里的第一步非常重要。不能先扣库存再写明细也不能先写订单再扣库存。只要中间任何一步抛异常整个事务回滚库存和订单保持一致。订单号的生成我采用一个简单办法当前时间字符串加四位随机数形如202501161523300123。毕设规模下基本够用。5.3 入库管理与供应商台账采购入库模块要和库存模块联动。入库时选择药品和供应商填写数量、进货价、批号、有效期保存后自动更新drug表的库存字段。同时把这次入库记录写到stock_in表里形成完整的采购流水。供应商是一个独立模块维护供应商名称、联系人、电话、地址。设计上可以预留一个“按供应商查看入库记录”的功能也就是从供应商维度反查历史采购情况这个能体现多表查询能力答辩时也容易讲。5.4 统计报表的SQL和图表联调统计报表是偏后的模块但也是老师会重点看的模块。我做了三个方向按月销售趋势SELECT DATE_FORMAT(create_time, %Y-%m) AS month, SUM(total_amount) AS total_amount, COUNT(id) AS order_count FROM sale_order GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month;畅销药排行SELECT d.name, SUM(oi.quantity) AS sale_count, SUM(oi.sale_price * oi.quantity) AS sale_amount FROM sale_order_item oi JOIN drug d ON oi.drug_id d.id GROUP BY oi.drug_id ORDER BY sale_count DESC LIMIT 10;前端图表用ECharts比较多。后端把查询结果包装成ListMapString, Object通过ResponseBody转成JSON前端用Ajax取数后再渲染折线图和柱状图。这里要特别注意JSON内容的字段名要和前端一致我一开始字段名没对准图表白屏了半天后来逐字段核对才发现问题。6. 开发中真正折磨人的地方联调、部署和接盘源码6.1 数据库连接和时区的三连坑这部分全是血泪。数据库连接过程最常见的三个坑驱动类名写错、时区报错、中文乱码。前面已经提到驱动和后缀要写成MySQL 8.0对应的形式这里再强调一遍。如果你拿到别人的源码一直启动报错先看这三个地方jdbc.properties的driver是不是com.mysql.cj.jdbc.Driverurl里面有没有serverTimezoneAsia/Shanghai数据库和项目连接字符集是不是都统一成了utf8这三个问题排查完数据库连接基本就稳了。6.2 MyBatis映射与动态SQL的常见翻车点MyBatis这一层最容易出的问题是实体类属性名和数据库列名对不上。解决方法是开启驼峰映射settings setting namemapUnderscoreToCamelCase valuetrue/ /settings这样数据库里的create_time就会自动映射到实体类的createTime字段。如果没开查出来之后属性全是null前端就看不到数据。多条件搜索我一般用动态SQLselect idselectByCondition resultTypecom.pharmacy.entity.Drug SELECT * FROM drug where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testcategoryId ! null AND category_id #{categoryId} /if /where ORDER BY id DESC /select这里有一个细节if判断里的参数名要和Mapper接口里的Param注解一致否则会报“There is no getter for property”的错误这几乎是新手必踩。6.3 前后端JSON交互与中文乱码后端返回JSON时Controller方法上要加ResponseBody或者类上直接用RestController。返回类型可以是Map或List。前端Ajax请求时如果传的是JSON格式的数据记得设置$.ajax({ url: /order/settle, contentType: application/json;charsetUTF-8, data: JSON.stringify(cartList), type: POST, success: function (res) { ... } });Controller接收时用RequestBody注解public Result saveOrder(RequestBody ListCartItemDTO cartList) {经常遇到的情况是前端明明传了数据后端却接收不到十有八九是contentType没设置。这个问题排查了半小时才发现就少了这一行。中文乱码则要三板斧齐做第一web.xml里加CharacterEncodingFilter第二数据库连接串带characterEncodingutf8第三IDE的编码设置改成UTF-8。三处缺一不可。6.4 拿到别人源码包后的通用启动排查步骤这个题目带了“附源码”三个字很多同学会直接买或下载源码包。我必须说一句源码包能跑起来才是源码跑不起来就是一堆代码文件。接手源码时按以下顺序排查效率最高先看有没有数据库脚本.sql文件有就先在MySQL里执行没有脚本就开始跑代码的基本都会失败。检查pom.xml的依赖版本执行mvn clean install把依赖拉下来看有没有下载报错。改jdbc.properties里的账号密码和URL确认连接的是你自己的数据库。用Tomcat 9启动项目看日志里面有没有报错。第一轮常见错误是驱动、时区、端口占用。如果项目起来了但页面空白先看控制台有没有404再打开浏览器F12看Network面板的请求状态挨个接口排查。资源文件里的图片、CSS、JS路径要注意工程名改了之后原来以/开头的绝对路径要跟着改。7. 测试、演示与答辩把毕设变成一个有说服力的项目7.1 测试用例设计正常、异常、边界和权限毕设的测试章节不需要写得很高大上但至少要有覆盖面。我习惯把测试用例按四类来组织正常流程、异常流程、边界条件、权限控制。模块测试内容输入/操作预期结果登录正常登录正确的用户名密码跳转到首页显示对应用户信息登录密码错误错误密码提示密码错误停留登录页药品管理新增药品填写完整合法数据保存成功列表出现新药品药品管理必填缺失不填名称直接保存表单校验提示不写入数据库收银下单库存充足添加数量小于库存下单成功库存减少收银下单库存不足添加数量大于库存下单失败提示库存不足库存不变推荐模块老会员有历史选择购买过的会员返回TopN推荐列表推荐模块新会员无历史选择刚创建的会员返回热门药品列表权限管理收银员访问管理员页使用收银员账号访问拦截器拒绝跳回登录页这些用例表直接放进论文附录或测试章节非常加分。7.2 环境部署与演示数据准备演示前一定要准备好一份“有说服力”的种子数据。如果数据库里只有十几条记录推荐模块基本测不出效果。我准备的是20个会员、50种常用药品、大约3个月左右的订单记录、每月大概几百条明细。这样协同过滤才能真正计算出相似度和推荐结果。部署打包用Maven来打mvn clean package打包成功后target目录下会生成一个war包把这个war包放到本地Tomcat的webapps目录下启动Tomcat访问http://localhost:8080就能看到项目。如果war包名是pharmacy-system访问路径就是http://localhost:8080/pharmacy-system/。7.3 答辩高频问题如何准备答辩时老师最喜欢问的核心问题大概有五个提前想好答案第一个请简述SSM框架的请求流程。回答要点浏览器发请求到DispatcherServlet它通过HandlerMapping找到对应Controller方法Controller调用Service处理业务Service调用Mapper操作数据库结果返回后经视图解析器渲染页面或返回JSON。第二个为什么选择协同过滤算法回答要点业务上有会员购买历史数据适合做个性化推荐选择ItemCF是因为药店商品间搭配购买关系明显算法可解释性强也方便冷启动兜底。第三个如果推荐结果不准怎么办回答要点冷启动问题用热门榜兜底数据稀疏时只对共同购买者足够多的药品对计算相似度还可以引入时间衰减因子越近的购买记录权重越高。第四个系统的事务是怎么保证的回答要点Spring声明式事务Transactional注解标注在业务方法上下单过程中任何异常都会导致整个事务回滚库存和订单保持一致。第五个你的算法效果如何评估回答要点用训练集和测试集切分历史数据计算准确率、召回率和覆盖率离线实验评估。7.4 论文章节安排与工作量分配论文目录我会这样安排第一章绪论写背景和意义第二章相关技术写SSM、MySQL、协同过滤原理第三章需求分析和可行性分析第四章总体设计写系统架构和功能模块划分第五章数据库设计第六章系统实现配合截图和核心代码讲解第七章重点写协同过滤推荐模块的算法设计和实验评估第八章系统测试。这样的结构逻辑完整工作量分配也均匀不会出现前面写太多后面草草结尾的问题。最后再说一点个人体会。做这类管理系统真正拉开差距的不是你会多少新技术而是你对一个简单业务有没有想清楚。把“订单从加入到扣库存”这条主链路跑通再往里面加算法亮点系统就会非常扎实。如果只想着堆页面、堆功能最后大概率是花架子。准备答辩时给自己写一个“演示脚本”固定用某个会员账号、展示某几个核心模块、点开某张报表五分钟讲完主流程剩下时间全部留给推荐模块讲解。这种方法我试过现场效果很稳也挺适合第一次做完整项目的同学参考。