简介:这是一套面向计算机专业学生与Java Web初学者的在线收银系统毕业设计源码,基于SSM框架与MySQL开发,适合作为毕业设计、课程设计或小型零售企业收银管理系统的参考案例。系统涵盖系统用户管理、员工管理、用户管理、商品类别与商品管理、入库管理、销售管理及销售统计等模块,业务链路完整,贴近超市、便利店等实际收银场景。压缩包为zip格式,大小约23.97MB,内含源码、说明文档与LW等文件,文档部分包含架构设计、功能模块介绍、数据库设计、接口说明及环境搭建指南,便于理解与二次开发。项目采用JDK1.8、MyBatis、Tomcat7、Maven3.3等技术栈,代码结构清晰、注释较完整,可帮助读者掌握SSM框架的实际应用、业务流程处理与数据交互方式。目前已有78人学习,适合需要完整项目方案与排错思路的开发者参考。
1. 在线收银系统:一个能直接跑起来的 SSM 实战项目
如果你正在找一份能写进简历、能应付毕业设计答辩、还能真正跑起来的 Java Web 项目,那这套基于 SSM 框架的在线收银系统值得花时间拆一遍。它不是那种只有增删改查的玩具 Demo,而是把商品管理、收银结算、订单流水、会员折扣、库存扣减这几条真实业务线串起来的完整系统。技术栈是经典的 Spring + SpringMVC + MyBatis + MySQL,前端用 JSP 配合 jQuery,部署在 Tomcat 上。对于刚学完 SSM 想找个项目练手的人,或者被课程设计逼到墙角、需要一份结构清晰、有说明文档、有数据库脚本的源码包的人来说,这套东西的完成度足够让你少走很多弯路。下面我按实际拆项目的顺序,从环境搭建一路讲到二次开发怎么改。
2. 环境搭建与项目导入:从零把系统跑起来
2.1 开发环境与依赖版本确认
拿到源码包之后,第一件事不是急着导入 IDE,而是先确认版本。SSM 项目最怕的就是 JDK、Tomcat、MySQL 三者版本对不上,跑起来一堆ClassNotFoundException或者NoSuchMethodError。这套项目我实测下来,稳妥的组合是 JDK 1.8、Tomcat 8.5、MySQL 5.7。JDK 别上 11 或 17,因为项目里用的 Spring 4.x 对高版本 JDK 的模块化支持不好,容易在启动时炸出反射相关的异常。MySQL 用 5.7 是因为驱动包是mysql-connector-java 5.1.x,连 8.0 的库会报时区错误,虽然加个serverTimezone参数能解决,但没必要给自己找麻烦。
Maven 依赖这块,核心就是 spring-webmvc、spring-jdbc、mybatis、mybatis-spring、druid 连接池、jstl、servlet-api。项目里已经配好了pom.xml,你导入之后让 IDE 自己下载就行。如果下载慢,换成国内镜像源,这个不用我多说。
2.2 数据库初始化与配置修改
源码包里一般会带一个sql文件夹,里面是建表语句和初始数据。操作步骤很直接:
# 登录 MySQL 后执行 source /path/to/online_cashier.sql;执行完之后你会看到几张核心表:goods(商品表)、orders(订单主表)、order_detail(订单明细)、member(会员表)、user(收银员/管理员表)、category(商品分类)。表结构不复杂,但字段设计有讲究,比如orders里有total_amount、discount_amount、pay_amount三个金额字段,分别对应原价、折扣和实付,这个设计在后面讲结算逻辑时会重点说。
数据库建好之后,去改db.properties或者jdbc.properties(不同项目命名不一样),把里面的jdbc.url、jdbc.username、jdbc.password换成你自己的。这里有个细节:连接池用的是 Druid,配置文件里有一堆initialSize、maxActive之类的参数,本地跑的话保持默认就行,不用动。
2.3 Tomcat 部署与启动验证
导入 IDEA 或 Eclipse 之后,配置 Tomcat 运行环境。注意两点:一是web.xml里springmvc的 DispatcherServlet 映射路径是/,不是/*,这个别改错;二是项目的 context path 建议设成/cashier,方便后面访问。
启动成功后,浏览器输入http://localhost:8080/cashier/login,应该能看到登录页。默认账号一般在user表里,密码可能是明文也可能是 MD5,看项目实现。如果是 MD5,用在线工具算一下对应明文再登录。登录进去之后,左侧菜单能看到收银台、商品管理、订单查询、会员管理这几个模块,说明环境已经通了。
提示:如果启动时报
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,检查 Maven 依赖里 mysql 驱动的 scope 是不是被设成了 provided,改成默认的 compile 就行。
3. 核心模块拆解:收银结算链路与商品管理实现
3.1 收银台的前后端交互流程
收银台是整个系统的核心页面,逻辑比普通 CRUD 复杂。它的交互流程是这样的:收银员在搜索框输入商品条码或名称,前端通过 Ajax 请求后端/goods/search接口,返回商品信息并渲染到待结算列表;收银员可以修改数量,系统实时计算小计;如果顾客是会员,输入会员手机号后调用/member/query拿到折扣率;点击结算时,前端把整个商品列表和会员信息打包成 JSON,POST 到/order/create完成下单。
这个流程里,前端用 jQuery 做 DOM 操作和 Ajax 请求,后端用 SpringMVC 的@ResponseBody返回 JSON。我贴一段后端控制器的关键代码:
@Controller @RequestMapping("/order") public class OrderController { @Autowired private OrderService orderService; @RequestMapping(value = "/create", method = RequestMethod.POST) @ResponseBody public Map<String, Object> createOrder(@RequestBody OrderDTO orderDTO) { Map<String, Object> result = new HashMap<>(); try { // orderDTO 里包含商品列表、会员ID、收银员ID String orderNo = orderService.createOrder(orderDTO); result.put("success", true); result.put("orderNo", orderNo); } catch (Exception e) { result.put("success", false); result.put("message", e.getMessage()); } return result; } }这段代码的逻辑说明:@RequestBody把前端传来的 JSON 自动映射成OrderDTO对象,这个 DTO 里有个List<OrderItemDTO>字段装商品明细。orderService.createOrder()是业务层方法,负责生成订单号、计算金额、扣减库存、写入订单主表和明细表。返回的Map会被 Jackson 序列化成 JSON 给前端。参数方面,orderDTO里的memberId可以为空,表示非会员消费;payType字段区分现金、微信、支付宝,虽然实际支付没对接,但字段留好了扩展位。
3.2 订单创建的业务层事务处理
业务层是整个结算链路最需要关注的地方,因为它涉及多表写入和库存扣减,必须加事务。来看 Service 实现:
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private OrderDetailMapper orderDetailMapper; @Autowired private GoodsMapper goodsMapper; @Override @Transactional(rollbackFor = Exception.class) public String createOrder(OrderDTO orderDTO) { // 1. 生成订单号:时间戳 + 随机数 String orderNo = System.currentTimeMillis() + String.valueOf((int)(Math.random()*9000+1000)); // 2. 计算总金额和实付金额 BigDecimal totalAmount = BigDecimal.ZERO; for (OrderItemDTO item : orderDTO.getItems()) { Goods goods = goodsMapper.selectById(item.getGoodsId()); if (goods.getStock() < item.getQuantity()) { throw new RuntimeException("商品[" + goods.getName() + "]库存不足"); } totalAmount = totalAmount.add(goods.getPrice().multiply(new BigDecimal(item.getQuantity()))); } BigDecimal discount = BigDecimal.ZERO; if (orderDTO.getMemberId() != null) { Member member = memberMapper.selectById(orderDTO.getMemberId()); discount = totalAmount.multiply(BigDecimal.ONE.subtract(member.getDiscountRate())); } BigDecimal payAmount = totalAmount.subtract(discount); // 3. 写入订单主表 Order order = new Order(); order.setOrderNo(orderNo); order.setTotalAmount(totalAmount); order.setDiscountAmount(discount); order.setPayAmount(payAmount); order.setMemberId(orderDTO.getMemberId()); order.setUserId(orderDTO.getUserId()); order.setCreateTime(new Date()); orderMapper.insert(order); // 4. 写入明细并扣减库存 for (OrderItemDTO item : orderDTO.getItems()) { OrderDetail detail = new OrderDetail(); detail.setOrderId(order.getId()); detail.setGoodsId(item.getGoodsId()); detail.setQuantity(item.getQuantity()); detail.setPrice(goodsMapper.selectById(item.getGoodsId()).getPrice()); orderDetailMapper.insert(detail); // 扣库存,用乐观锁防止超卖 int affected = goodsMapper.reduceStock(item.getGoodsId(), item.getQuantity()); if (affected == 0) { throw new RuntimeException("库存扣减失败,可能已被其他收银台抢先"); } } return orderNo; } }逻辑说明:@Transactional(rollbackFor = Exception.class)保证任何一步失败都整体回滚,不会出现订单写了但库存没扣的脏数据。金额计算用BigDecimal而不是double,这是金融类项目的铁律,避免浮点精度问题。库存扣减用的是UPDATE goods SET stock = stock - #{qty} WHERE id = #{id} AND stock >= #{qty}这种写法,靠数据库行锁保证并发安全,返回影响行数为 0 就抛异常触发回滚。参数方面,discountRate在会员表里存的是小数,比如 0.95 表示 95 折,计算折扣时用1 - discountRate得到折扣比例。
3.3 商品管理模块的 MyBatis 映射与分页
商品管理是标准的 CRUD,但有几个点值得注意。MyBatis 的 Mapper XML 里,查询商品列表用了动态 SQL 做条件筛选:
<select id="selectByPage" resultMap="GoodsResultMap"> SELECT g.*, c.name AS category_name FROM goods g LEFT JOIN category c ON g.category_id = c.id <where> <if test="keyword != null and keyword != ''"> AND (g.name LIKE CONCAT('%', #{keyword}, '%') OR g.barcode = #{keyword}) </if> <if test="categoryId != null"> AND g.category_id = #{categoryId} </if> </where> ORDER BY g.create_time DESC LIMIT #{offset}, #{limit} </select>这段 SQL 用<where>标签自动处理AND前缀,避免拼接出WHERE AND的语法错误。resultMap里配置了关联查询,把分类名称映射到categoryName字段,前端表格直接显示中文分类名。分页用的是物理分页,offset和limit由 PageHelper 插件或者手动计算传入。如果项目里集成了 PageHelper,那 Service 层只需要调PageHelper.startPage(pageNum, pageSize)就行,XML 里不用写 LIMIT。
商品新增和编辑共用一个saveOrUpdate方法,通过判断 ID 是否为空来决定 insert 还是 update。删除做的是逻辑删除,goods表里有个is_deleted字段,删除时更新为 1,查询时过滤掉。这样做的好处是历史订单里的商品信息不会因为商品被删而查不到。
4. 避坑与常见问题:部署和运行时的真实排查记录
4.1 中文乱码问题
现象:商品名称在页面上显示成问号或者乱码。原因通常是三处编码没统一:数据库连接 URL 没加useUnicode=true&characterEncoding=utf8、Tomcat 的server.xml里 Connector 没设URIEncoding="UTF-8"、或者 JSP 页面没声明pageEncoding="UTF-8"。解决方法是三处都检查一遍,缺哪个补哪个。另外,如果是 POST 请求乱码,还要在web.xml里配一个CharacterEncodingFilter,把forceEncoding设为 true。
4.2 事务不生效
现象:订单写入成功但库存没扣,或者库存扣了订单没写。原因大概率是 Spring 的<tx:annotation-driven>没开,或者@Transactional注解加在了 Controller 层而不是 Service 层。Spring 的事务是基于代理的,Controller 层的方法如果没走代理就不会被事务管理。解决方法是确认spring-service.xml里有<tx:annotation-driven transaction-manager="transactionManager"/>,并且注解加在 Service 实现类的方法上。另外,如果 Service 类没有实现接口,Spring 默认用 CGLIB 代理,这个一般没问题,但如果是 JDK 动态代理,就必须有接口。
4.3 静态资源被拦截
现象:CSS、JS、图片加载不出来,浏览器控制台报 404。原因是web.xml里 DispatcherServlet 映射了/,所有请求都走 SpringMVC,静态资源找不到对应的 Controller 就 404 了。解决方法是在springmvc.xml里加<mvc:default-servlet-handler/>或者<mvc:resources mapping="/static/**" location="/static/"/>。前者是交给 Tomcat 默认 Servlet 处理,后者是显式配置资源路径。我一般用后者,路径清晰,不容易出玄学问题。
4.4 数据库连接超时
现象:系统跑一段时间后报Communications link failure。原因是 MySQL 默认 8 小时空闲连接会被断开,而 Druid 连接池里的连接没及时剔除。解决方法是配 Druid 的validationQuery为SELECT 1,testWhileIdle设为 true,timeBetweenEvictionRunsMillis设一个小于 8 小时的值,比如 300000(5 分钟)。这样连接池会定期检查空闲连接的有效性,把废连接清掉。
4.5 订单号重复
现象:高并发下偶尔出现订单号冲突,插入报唯一索引异常。原因是订单号生成用了时间戳加随机数,理论上仍有碰撞概率。解决方法是把订单号生成逻辑改成数据库自增 ID 拼接,或者用 UUID,或者加分布式锁。对于毕业设计级别的项目,最简单的改法是给order_no字段加唯一索引,然后在生成时循环重试直到不冲突。虽然粗暴,但能解决问题。
5. 二次开发与功能扩展:从能跑到好用
5.1 增加支付方式与对接思路
项目里pay_type字段已经预留了,但实际只做了现金支付。如果要扩展微信或支付宝,思路是在结算时调用第三方支付接口生成二维码,然后轮询查询支付状态。具体改造点:在OrderController里加一个/order/pay接口,接收订单号和支付方式,返回支付二维码链接;前端用定时器每 3 秒查一次/order/queryPayStatus,支付成功后跳转到小票页面。这里要注意的是,第三方支付的回调是异步的,订单状态更新要放在回调接口里做,不能只靠前端轮询。
5.2 报表统计功能的实现
收银系统一般需要日结报表和商品销售排行。这个项目里没有现成的报表模块,但数据都在orders和order_detail表里,加一个统计功能不难。我一般会写一个ReportController,提供两个接口:/report/daily按日期分组统计营业额和订单数,/report/ranking按商品分组统计销量。SQL 大概长这样:
SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(pay_amount) AS revenue FROM orders WHERE create_time BETWEEN #{start} AND #{end} GROUP BY DATE(create_time) ORDER BY day DESC;前端用 ECharts 画折线图和柱状图,数据直接拿接口返回的 JSON 渲染。这个功能加上去,答辩的时候演示效果会好很多。
5.3 权限管理的细化
目前项目里user表只有一个role字段区分管理员和收银员,粒度比较粗。如果要细化到菜单权限和按钮权限,可以引入 RBAC 模型,加role、permission、role_permission三张表,然后在登录时把权限列表存到 Session 里,前端根据权限控制菜单显示,后端用拦截器校验接口访问权限。这个改造工作量不小,但做完之后项目档次会提升一截。
5.4 改造验证与回滚习惯
每次改完代码,我习惯先跑一遍核心链路:登录 → 商品搜索 → 加入结算 → 会员折扣 → 结算下单 → 查订单 → 查库存。这七步走通,基本能覆盖 90% 的回归问题。数据库改动之前先mysqldump备份,改坏了直接回滚。从那以后我每次动核心业务代码,都强制走一遍这个流程,省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取