☰
基于SSM框架与协同过滤算法的药店销售管理系统实战
2026/10/21 3:57:23 网站建设 项目流程

我第一次在毕设选题表里看到“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> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.29</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>

web.xml是SSM的入口,里面两个配置最重要。一个是字符编码过滤器,必须放在所有过滤器的最前面:

<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

另一个是DispatcherServlet,它接管所有前端请求:

<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>

spring-mvc.xml里要开启注解驱动,配置视图解析器,并放行静态资源。spring-context.xml里则配置数据源、事务管理器和Mapper扫描。这里有一个容易忽略的点:MyBatis的Mapper接口扫描和Mapper XML扫描要配对配置,否则DAO层会注入不进去。

2.3 跑通“Hello World”前的版本对齐细节

第一次搭框架时,我栽在数据库驱动上。MySQL 8.0的驱动类名和MySQL 5.x不一样,jdbc.properties里必须写成:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/pharmacy_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

如果还按老教程写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, role
member会员信息id, name, phone, create_time
drug_category药品分类id, name, description
drug药品资料id, drug_code, name, category_id, manufacturer, specification, unit, purchase_price, sale_price, stock, warn_stock
supplier供应商id, name, contact, phone, address
stock_in入库记录id, drug_id, supplier_id, quantity, cost_price, batch_no, expire_date, create_time
sale_order销售订单主表id, order_no, member_id, emp_id, total_amount, create_time
sale_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. 构建Map<drugId, Set<memberId>>:记录每个药品被谁买过 3. 遍历所有药品对(a, b): 公共购买者 = setA ∩ setB 如果公共购买者数量 < 2,跳过 计算sim = |setA ∩ setB| / sqrt(|setA| * |setB|) 保存到Map<a, List<b, 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比较多。后端把查询结果包装成List<Map<String, Object>>,通过@ResponseBody转成JSON,前端用Ajax取数后再渲染折线图和柱状图。这里要特别注意JSON内容的字段名要和前端一致,我一开始字段名没对准,图表白屏了半天,后来逐字段核对才发现问题。

6. 开发中真正折磨人的地方:联调、部署和接盘源码

6.1 数据库连接和时区的三连坑

这部分全是血泪。数据库连接过程最常见的三个坑:驱动类名写错、时区报错、中文乱码。前面已经提到驱动和后缀要写成MySQL 8.0对应的形式,这里再强调一遍。如果你拿到别人的源码一直启动报错,先看这三个地方:

  • jdbc.properties的driver是不是com.mysql.cj.jdbc.Driver
  • url里面有没有serverTimezone=Asia/Shanghai
  • 数据库和项目连接字符集是不是都统一成了utf8

这三个问题排查完,数据库连接基本就稳了。

6.2 MyBatis映射与动态SQL的常见翻车点

MyBatis这一层最容易出的问题是实体类属性名和数据库列名对不上。解决方法是开启驼峰映射:

<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

这样数据库里的create_time就会自动映射到实体类的createTime字段。如果没开,查出来之后属性全是null,前端就看不到数据。

多条件搜索我一般用动态SQL:

<select id="selectByCondition" resultType="com.pharmacy.entity.Drug"> SELECT * FROM drug <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="categoryId != 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;charset=UTF-8", data: JSON.stringify(cartList), type: "POST", success: function (res) { ... } });

Controller接收时用@RequestBody注解:

public Result saveOrder(@RequestBody List<CartItemDTO> cartList) {

经常遇到的情况是前端明明传了数据,后端却接收不到,十有八九是contentType没设置。这个问题排查了半小时才发现就少了这一行。

中文乱码则要三板斧齐做:第一,web.xml里加CharacterEncodingFilter;第二,数据库连接串带characterEncoding=utf8;第三,IDE的编码设置改成UTF-8。三处缺一不可。

6.4 拿到别人源码包后的通用启动排查步骤

这个题目带了“附源码”三个字,很多同学会直接买或下载源码包。我必须说一句:源码包能跑起来才是源码,跑不起来就是一堆代码文件。接手源码时按以下顺序排查,效率最高:

  1. 先看有没有数据库脚本(.sql文件),有就先在MySQL里执行,没有脚本就开始跑代码的基本都会失败。
  2. 检查pom.xml的依赖版本,执行mvn clean install把依赖拉下来,看有没有下载报错。
  3. 改jdbc.properties里的账号密码和URL,确认连接的是你自己的数据库。
  4. 用Tomcat 9启动项目,看日志里面有没有报错。第一轮常见错误是驱动、时区、端口占用。
  5. 如果项目起来了但页面空白,先看控制台有没有404,再打开浏览器F12看Network面板的请求状态,挨个接口排查。
  6. 资源文件里的图片、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、协同过滤原理,第三章需求分析和可行性分析,第四章总体设计写系统架构和功能模块划分,第五章数据库设计,第六章系统实现配合截图和核心代码讲解,第七章重点写协同过滤推荐模块的算法设计和实验评估,第八章系统测试。这样的结构逻辑完整,工作量分配也均匀,不会出现前面写太多后面草草结尾的问题。

最后再说一点个人体会。做这类管理系统,真正拉开差距的不是你会多少新技术,而是你对一个简单业务有没有想清楚。把“订单从加入到扣库存”这条主链路跑通,再往里面加算法亮点,系统就会非常扎实。如果只想着堆页面、堆功能,最后大概率是花架子。准备答辩时给自己写一个“演示脚本”,固定用某个会员账号、展示某几个核心模块、点开某张报表,五分钟讲完主流程,剩下时间全部留给推荐模块讲解。这种方法我试过,现场效果很稳,也挺适合第一次做完整项目的同学参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询