开篇:一个收银系统项目到底能学到什么
做Java开发这几年,带过不少新人,也接手过各种外包项目。如果要选一个最适合练手、又能完整覆盖主流技术栈的实战项目,餐厅收银系统绝对排在前列。这次要聊的“SSM基于java的餐厅收银系统zsbtd”,是一个典型的全栈项目——后端用Spring + Spring MVC + MyBatis这套经典组合,前端配合JSP和Bootstrap,数据库用MySQL,整套下来几乎把Java Web开发的核心环节都走了一遍。
先说这个系统能干什么。餐厅收银系统,解决的是一家线下餐饮门店最核心的日常运营问题:点单、下单、结算、打印小票、统计营业额。用户角色拆开来看,无非是收银员、服务员和老板(管理员)。收银员负责日常开台点单、结账,服务员可能管桌台状态,老板则要看每日流水、菜品销量、会员信息这些经营数据。把这几条线理清楚,系统的骨架其实就出来了。
这个项目适合谁?如果你正在学SSM框架但还停留在增删改查的Demo阶段,或者准备毕业设计、面试项目,这套收银系统是一个特别合适的实战样本——业务流程完整,表结构设计有讲究,前后端联调涉及细节多,做完之后你对整个Java Web开发的认知会上一大台阶。这篇文章我会把这个项目的核心设计、表结构思路、功能实现细节、部署过程以及我在调试中踩过的坑,全部摊开来说明。不吹不黑,跟着走一遍,代码跑起来只是及格线,搞懂为什么这么设计才算真正值回票价。
1. 系统拆解:收银业务的本质就是状态流转
1.1 先理清业务场景,再谈技术
很多新手拿到这种项目,上来就建表写代码,结果做到一半发现业务逻辑理不清,只能推倒重来。我习惯的做法是先把餐厅的一天在脑子里过一遍:顾客进店,服务员带到空桌,顾客点菜,后厨出菜,顾客吃完,收银员结账。就这么一条线,对应到系统里就是几个关键动作——开台、点单、加菜、退菜、结账、清台。
这个项目的核心业务其实就是“桌台状态”的流转。空台变成使用中,使用中结账完又变回空台。所有其他功能,菜品管理、订单管理、会员管理、统计报表,都是围绕桌台和订单这两条主线展开的辅助模块。理解了这个,你再看项目里的数据库表设计,就不会觉得表多杂乱。
整个系统划分为三个角色,对应三套权限和操作界面:
- 收银员:开台、点单、结账、打印小票、日结统计
- 服务员:查看空余桌台、录入菜品(通常由收银员代操作)
- 管理员(老板):菜品管理、桌台管理、员工管理、订单流水、销量报表、会员管理
角色权限这个事要提醒一句:具体到不同的毕业设计项目,权限划分会和这个略有差别。比如有的会把服务员和收银员合一,有的会增加后厨端。但万变不离其宗,核心始终是桌台状态和订单数据。
1.2 为什么用SSM这套老组合
现在Spring Boot已经是主流,为什么还要选SSM?这个问题很多同学都会问。我的看法是:SSM作为Spring Boot的前身,能帮你把“配置”这件事彻底搞懂。Spring Boot大量使用了自动配置,你写一个注解就能跑,但底层为什么这样装配、拦截器怎么注册、事务怎么生效,很多人在Spring Boot里反而说不清楚。而SSM这套组合,所有配置都是手动写出来的——web.xml、spring-mvc.xml、mybatis-config.xml、db.properties,一行一行配下来,框架之间的协作逻辑清清楚楚。
回到技术选型本身:Spring负责IOC和AOP,管理Service层的事务和依赖注入;Spring MVC负责前端请求的路由和参数绑定,Controller层拿到请求后调Service;MyBatis把SQL和Java方法映射起来,负责数据库操作。三层架构职责清晰,出了问题也容易定位。再加上JSP做页面渲染,Bootstrap做样式,这套技术栈看起来“老”,但对理解Web开发本质非常有帮助。
1.3 项目整体目录结构预告
拿到项目源码之后,先别着急跑,花十分钟看一眼目录。典型的Maven结构,大致是这样:
src/main/java ├── com.xxx.controller # Controller层:接收请求、返回视图/数据 ├── com.xxx.service # Service层:业务逻辑、事务控制 ├── com.xxx.dao # MyBatis的Mapper接口 ├── com.xxx.pojo/entity # 实体类,对应数据库表 └── com.xxx.utils # 工具类 src/main/resources ├── mapper # MyBatis的XML映射文件 ├── spring # Spring和Spring MVC配置文件 └── jdbc.properties # 数据库连接配置 src/main/webapp ├── static # 静态资源:JS、CSS、图片 ├── WEB-INF/jsp # JSP视图页面 └── WEB-INF/web.xml # Web应用入口配置动手之前把这个结构吃透,后面调试定位问题会顺手很多。很多人拿到一个新项目最喜欢直接点运行,报错了就一脸懵。先看结构、再看配置、最后看代码,这个顺序不能乱。
2. 数据库设计:餐厅收银系统的地基
2.1 核心表结构和关联关系
这个项目的数据库设计是整个系统的灵魂,学会怎么建表比学会怎么写CRUD重要得多。先列一下核心表,对照着看会更容易理解:
- 管理员表(admin):管理员ID、用户名、密码、姓名、角色类型
- 员工表(employee):员工ID、姓名、手机号、角色(收银员/服务员)、密码
- 桌台表(table):桌台ID、桌号、座位数、状态(空/使用中)、所属区域
- 菜品分类表(category):分类ID、分类名称
- 菜品表(food):菜品ID、名称、分类ID、价格、图片、是否在售
- 订单表(orders):订单ID、桌台ID、员工ID、下单时间、总金额、订单状态
- 订单明细表(order_detail):明细ID、订单ID、菜品ID、数量、单价、小计
- 会员表(member):会员ID、手机号、姓名、积分、余额
- 充值记录表/消费记录表:记录会员的资金流向
其中最关键的是订单和订单明细的“主从表”关系。一张订单对应多条明细,每条明细记录的是某一桌点的某个菜品的数量和当时的价格。为什么要单独记录单价?因为菜品价格可以调整,如果直接查菜品表当前的价格,历史订单的金额就失真了。这个细节在报表统计时特别重要,也是很多新手容易忽略的点。
2.2 为什么要这样设计状态字段
订单表里有个“状态”字段,这个字段的值一般有几种:未结账、已结账、已退单。你可能觉得就是一个int,没什么好说的。但实际开发中,这个字段配合桌台的状态,是整个业务流程正常流转的关键。
举个例子,一个桌台被开台之后,系统要生成一条订单记录,状态为未结账;顾客点菜,往订单明细表里插入数据;中途加菜,继续insert;如果某个菜没上或者顾客不要了,要做退菜处理,明细的金额要调整,订单总金额也要同步更新;最后结账,把订单状态改为已结账,桌台状态改回空闲。
这个过程里最容易出错的是什么?是数据一致性。点菜、加菜、退菜、结账,这些操作如果不用事务包起来,一旦中间某一步报错,就会出现订单金额和明细对不上、桌台状态卡死等问题。SSM项目里事务控制在Service层做,关于这一点后面章节我会专门展开。
需要注意的是,这套表结构并不是唯一的答案。实际项目里,有的设计会把桌台的当前订单ID直接挂在桌台表上,有的会用单独的“开台记录表”来追踪每次开台结账的流水。各类设计有各自的适用场景,就本项目而言,订单表里通过桌台ID关联,配合状态判断,配合上后面要讲的查询逻辑,已经能覆盖需求了。
2.3 初始化数据脚本的作用
项目里会附带SQL脚本,很多人直接把脚本往Navicat里一拖,表建完就算了。但我建议你仔细看看脚本里的INSERT语句。管理员账号是什么、测试菜品有哪些,这些都是你后面调试功能的敲门砖。
一般的脚本会包含:管理员账号(通常是admin/admin123之类)、几道测试菜品、若干桌台记录。拿到脚本后第一步就改密码、改数据库名,确保本地环境安全符合自己的配置,这个习惯要养成。
3. 核心功能从0到1:代码层面的实现细节
3.1 登录校验与拦截器:第一道安全防线
登录功能看起来简单——一个表单提交,查数据库,用户名密码对上了就放行。但SSM项目里的登录逻辑,真正讲究的是登录之后的拦截处理。
通常的做法是写一个拦截器(Interceptor),在Spring MVC的配置里注册,拦截需要登录才能访问的URL。用户登录成功之后,把用户信息放进Session。拦截器里判断:如果Session里有用户信息就放行,否则跳转回登录页。同时要排除登录请求本身和静态资源(CSS、JS、图片)的拦截。
这里有几个坑值得说一下。第一,拦截路径的配置要细心,登录接口和静态资源必须排除在外,否则就会出现“页面样式丢了”或者“能登录但是登录后跳不过去”的诡异问题。第二,Session里的用户对象要存完整,你写“当前登录人”的时候到处要用。第三,密码在数据库里最好存加密后的值。这个项目可能用的是明文或简单MD5,但你自己做的时候,至少要加个盐再MD5,这是职业习惯的问题。
3.2 开台点单:事务控制的关键场景
开台逻辑是这样:收银员点击某个空桌,系统判断桌台状态是否为“空”,是的话就把状态改成“使用中”,同时往订单表插入一条未结账的新订单。这一步看着简单,但有个经典问题——桌台状态和订单创建是不可分割的整体。如果只改了桌台状态,订单插入失败,桌台就永远卡在使用中,后面没法结账了。
解决方式有两种:一是Service方法加上@Transactional注解,让这两个操作在一个事务里,任何一个失败都回滚;二是在代码里加判断,更新和插入都完成之后再返回成功。我强烈建议用第一种,声明式事务是Spring最优雅的能力之一,也让代码更简洁。
接着是点菜。前端页面通常是一左边菜品分类、右边菜品列表的布局,勾选菜品、填数量,加入购物车,确认后提交。提交到后端之后,Service层要做的有两件事:往订单明细表批量插入菜品记录;重新计算订单总金额并更新订单表。
注意这里有个业务细节:如果顾客中途已经结过部分菜,或者某些菜是按份数点的大份小份,那么数量的处理和金额计算必须严谨。本项目里菜品没有口味、规格属性,相对简单,但你要理解这个扩展方向。
3.3 结账与打印:别忽略金额计算的精度
结账功能是这个系统的核心价值体现。顾客吃完了,收银员点击结账,系统要算出来这张订单打了多少折扣、实收多少钱。这里最常见的坑就是金额计算精度。
Java里做金额计算绝对不能用double或float,这个话每次都要说。0.1加0.2在二进制浮点数里不等于0.3,这在金额上是要出大事的。得用BigDecimal,构造的时候注意用字符串构造器,new BigDecimal("10.5"),而不是new BigDecimal(10.5)。订单金额的累加、折扣运算、找零计算,全部用BigDecimal,最后再四舍五入到两位小数。
结账完成之后,订单状态改为已结账,桌台状态恢复为空,同时(如果用了会员)要记录会员积分和消费流水。涉及到多张表的更新,又是一个需要事务保护的操作。
打印小票这个功能,在很多教学项目里是简化的,有的只出一个“打印成功”的提示,有的生成一个小票样式页面让浏览器打印。如果要做硬件的真实打印,一般是调用桌台小票打印机SDK或者通过系统命令打印。这个不是本项目的重点,但你要知道生产环境的打印不是一个简单的document.print()能覆盖的。
3.4 报表统计:SQL是核心武器
老板登录系统看什么?看今天卖了多少、哪个菜卖得好、哪个时段人多。这些功能的实现核心全在查询SQL里,后端只是把SQL查出来的结果包装成对象返回给页面。
一日营收报表,最朴素的思路就是查订单表,选出日期范围,按支付方式分组统计总金额;菜品的销量排行,则是把订单明细表和菜品表关联起来,按菜品分组、COUNT或SUM然后排序取前十。
写这类报表SQL的时候,我最想提醒的就是group by和聚合函数的使用。分组之后只能select分组的字段和聚合字段,这是SQL的语法规则。另外日期区间的过滤,如果日期字段是datetime类型,要注意边界问题——比如查今天的数据,要大于等于今天零点、小于明天零点,而不是直接等于今天的日期字符串。
MyBatis里写这些报表SQL,建议直接用XML里手写SQL,不要用自动生成的基本CRUD方法。原因很简单:报表SQL复杂多变,Java代码里拼SQL字符串既难读又容易出注入问题,XML里写SQL是MyBatis的灵魂所在。
3.5 会员管理与折扣:业务规则要清晰
会员模块在这个系统里承担的是复购留存的功能。会员可以充值、消费累计积分。会员结账享受折扣是常见规则,比如会员95折。
这块的复杂度不在于技术,而在于规则本身。折扣是在哪个环节计算?是读取菜品原价之后打折,还是直接改商品单价?我的实现是结账时先计算订单原价,再根据会员等级折扣算出折后价,订单表和明细表分开记录。这样报表统计“实收金额”和“应收金额”就有了依据。如果在明细表里直接把单价改了,后面想统计折扣让利多少就查不出来了。
会员充值时,账户余额和充值记录两张表必须同时更新,而且是先扣款再更新余额,中间任何一步出异常都要能回滚。这里同样是事务的用武之地。
4. 环境搭建与部署:从源码到跑起来
4.1 本地开发环境的搭配
这个项目需要的环境,我列一个经过验证的版本组合:
- JDK:1.8(SSM项目最常见的版本,避免用太高版本导致编译问题)
- Tomcat:8.5或9.0
- MySQL:5.7或8.0
- IDE:IDEA或Eclipse
- Maven:3.6以上
有同学问过,分开装可以同时跑吗?可以的,没什么冲突。关键点在于项目的编译级别和Tomcat版本要匹配,把JDK编译级别设置为1.8。
装环境这里最大的坑是环境变量和端口冲突。JDK、Maven的JAVA_HOME、PATH配置要正确,Tomcat默认8080端口容易背其他服务占用,启动报错第一反应就去看端口。MySQL的密码和项目配置文件里的密码要对上。
4.2 导入项目和配置文件的修改
拿到项目之后,导入IDEA要选Maven项目,让IDEA自动下载依赖。这个环节可能出现受网络影响下载失败的情况,其实有解决办法——换国内镜像源,修改Maven的settings.xml文件,把中央仓库地址换成国内镜像。相信我,这能省下大量抓狂的时间。
然后修改三个核心配置文件:
- jdbc.properties或db.properties:改数据库URL、用户名、密码。URL要注意characterEncoding=utf8和useSSL=false这些参数,不然可能出现乱码或警告。
- 项目本身的上下文路径配置:有的项目会配置Tomcat的访问路径,比如localhost:8080/restaurant,你要看清楚上下文路径,访问页面的时候要带上。
- Maven的pom.xml:确认依赖的版本,有些JDK版本下旧版本的依赖会报错。
4.3 部署到Tomcat的完整步骤
本地用IDEA直接跑很方便,但如果你要给别人演示,或者放到服务器上跑实际环境,那就要掌握传统部署方式。
第一步,在IDEA里用Maven打包,执行mvn clean package,如果没有配置跳过测试,要确保测试用例能跑通。
第二步,打包成功后在target目录下会生成war包。把war包复制到Tomcat的webapps目录下。
第三步,启动Tomcat。Linux下进入bin目录执行./startup.sh,Windows下就是执行startup.bat,看到“Server startup”日志就说明成功了。然后浏览器访问http://服务器IP:8080/war包名/登录页面路径。
这里要特别注意数据库连接的问题。如果你的Tomcat和MySQL不在同一台机器,jdbc.properties里的数据库IP不能写localhost,要写MySQL所在服务器的实际IP。另外MySQL要允许远程连接,用户要有对应权限。这两个不配置好,就会出现“页面能打开但登录就报数据库连接错误”的情况。
4.4 数据初始化与演示数据
脚本导入的顺序,一般是先建库、再建表、再插数据。用Navicat这类工具操作最省事,直接运行SQL文件就行。还有一种方式是命令行导入:mysql -u用户名 -p密码 < xxx.sql,在Windows下注意编码,文件有中文时最好加--default-character-set=utf8参数。
初始化数据里一般会有几个桌台、几道菜品和测试账号。跑起来第一件事就是用管理员账号登录,去菜品管理里看看菜品图片是否能正常显示。菜品图片一般放在webapp目录的static文件夹或上传目录下,如果图片加载不出来,大部分情况是项目的静态资源映射配置有遗漏,或者图片路径写成了绝对路径。排查顺序:看浏览器请求图片的URL是什么、本地文件是否存在、映射配置是否正确。
5. 调试记录:我踩过的那些坑和排查思路
5.1 启动报ClassNotFoundException或NoClassDefFoundError
这个报错出现的频次极高。第一反应去看是不是Maven依赖没下载完,或者jar包冲突。IDEA里右侧Maven工具栏点刷新,让依赖重新导入。如果还不行,看看pom.xml里有没有依赖之间版本不兼容的情况。
正常启动过程中如果遇到这种问题,可以通过clean和install来循环排查,有时也要考虑到IDEA没有对着Tomcat完成项目lib目录的同步。经验的判断路径一定是:先刷Maven,再clean,最后查看lib目录里有没有那个类所在的jar包。
5.2 MyBatis绑定异常
项目启动没问题,但一访问某个查询功能就报Invalid bound statement (not found): com.xxx.dao.FoodDao.selectAll,意思是Mapper接口的方法和XML里的语句没绑定上。原因通常有三个:
第一,XML文件没有放在Mapper接口对应的包路径下,Spring扫不到。 第二,applicationContext.xml或者spring-mybatis.xml里SqlSessionFactoryBean的mapperLocations属性没有配置正确路径。 第三,XML里的namespace写的不是Mapper接口的全限定名。
这个问题的排查有一个固定高效的方法:先看XML文件的位置,再看配置文件的mapperLocations。把这三个点全过一遍,基本都能解决。
5.3 页面乱码问题
乱码是JSP项目的特产。Java文件是UTF-8,JSP页面也要UTF-8,数据库连接要UTF-8,数据库表结构也要UTF-8,四个环节一个不对就乱给你看。
检查清单:JSP页面顶部的pageEncoding;web.xml里有没有配CharacterEncodingFilter;jdbc.properties的URL里has characterEncoding=utf8;MySQL表默认字符集是不是utf8mb4。最常见的还是web.xml里的过滤器没配,导致POST请求的参数乱码。
5.4 404错误与路径解析问题
页面404,要区分是静态资源404还是请求路径404。静态资源404,检查Spring MVC有没有在spring-mvc.xml里配置静态资源放行,比如<mvc:resources location="/static/" mapping="/static/**"/>。
请求路径404,看Controller类上有没加@RequestMapping注解,方法上的路径拼写对不对,项目上下文路径有没有考虑到。浏览器F12的Network面板能帮你精准定位:红色请求就是404,看URL完整路径。
5.5 结账时金额与预期不一致的排查思路
如果你改了项目的折扣逻辑或者会员规则,金额不对通常出在BigDecimal的使用时机上。比如你在循环累加时用了double变量,或者用数据库的float字段存金额,或者bigdecimal的scale没有统一。
排查技巧:打印日志,把订单明细里每一道菜的原始单价、数量、小计,以及最后的总额全部输出,然后拿计算器手动算一遍,看是哪一步开始错的。这种问题切忌瞎猜,数据说话。
5.6 Tomcat端口占用
启动Tomcat提示Port 8080 required by Tomcat v9.0 Server at localhost is already in use。处理方案是改Tomcat的端口,或者找到占用进程并杀掉。Windows下的操作:netstat -ano | findstr 8080,找到PID后用taskkill /PID 进程号 /F。Linux下是lsof -i:8080,然后kill -9。
附带提一句,改Tomcat端口要去conf/server.xml里改三个位置的端口——HTTP端口、AJP端口和重定向端口,改好一个其他两个也要注意避免冲突。
6. 代码走读:几个值得细品的实现片段
6.1 Service层的事务写法
一个标准的Service方法长这样:
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; @Autowired private OrderDetailDao orderDetailDao; @Autowired private TableDao tableDao; @Transactional(rollbackFor = Exception.class) @Override public boolean createOrder(Integer tableId, Integer employeeId) { // 更新桌台状态 tableDao.updateStatus(tableId, 1); // 创建订单 Order order = new Order(); order.setTableId(tableId); order.setEmployeeId(employeeId); order.setCreateTime(new Date()); order.setStatus(0); int rows = orderDao.insert(order); return rows > 0; } }重点看@Transactional这一行。spring配置文件里开启事务注解,加了事务管理后,如果updateStatus成功但insert失败,update操作也会回滚,不会出现桌台状态改了但没有订单的情况。rollbackFor = Exception.class的意思是所有异常都触发回滚,这个尽量带上。默认情况下只有RuntimeException才会回滚,如果你抛的是自定义异常或受检异常,不加这个参数事务是不会回滚的。
6.2 MyBatis的XML写法要点
拿菜品查询来说:
<select id="selectByCategory" parameterType="int" resultType="com.xxx.pojo.Food"> select * from food where category_id = #{id} and status = 1 </select>resultType写的是实体类的全限定名,MyBatis会自动把数据库字段映射到同名属性。如果数据库字段是下划线风格(food_name),实体类是驼峰风格(foodName),记得在MyBatis的配置文件里开启驼峰映射:<setting name="mapUnderscoreToCamelCase" value="true"/>。不开的话查询结果会是null,这个问题特隐蔽。
6.3 Controller层的参数接收和跳转
Controller接收参数有三种方式:@RequestParam、@PathVariable、直接封装成实体类。前端的表单提交,如果你用一个Food对象来接,表单字段名和实体属性名对应上就能自动绑定。
跳转分为转发和重定向。转发是return "food/list"这样写,路径对应WEB-INF/jsp下的视图;重定向是return "redirect:/food/list"。转发会保留请求域的数据,重定向发起新的请求不会保留。结账成功后跳回订单列表,建议用重定向,防止用户刷新页面重复提交。
7. 真实项目复盘:如果让我从头再做一遍
7.1 这个项目还能怎么扩展
做完了基础功能之后,可以沿着几个方向去加东西:
- 后厨端:接单大屏,只显示点了菜的桌台和菜品,出菜点菜即通知厨师
- 扫码点餐:顾客用手机扫桌台码,点单数据实时同步到收银端
- 库存联动:每卖出一个菜,原材料的库存自动扣减,库存不够时预警
- 数据可视化:用ECharts把报表数据展示成折线图、柱状图,老板看数据就直观多了
每个方向都是一块独立的知识点。扫码点餐能让你了解前后端分离和小程序开发;库存联动能让你深入研究事务和并发扣减问题;数据可视化会用到JSON接口和前端图表库。基于这个基础项目去生长,路线非常清楚。
7.2 我踩过最久的坑,分享出来
我做这类项目时,最深刻的教训之一是数据库连接配置里的时区问题。MySQL 8.0以上版本对时间处理更严格,连接URL如果不加serverTimezone=Asia/Shanghai,操作时间字段时会直接报错或者时间差8小时。这个坑在导入项目后第一次跑日期相关的功能时就会出现。
另一个容易卡住新人的点,是IDEA启动项目时控制台中文乱码。这并不影响功能,但看着难受。解决办法是IDEA的设置里把虚拟机选项加上-Dfile.encoding=UTF-8,还要把Tomcat配置里的VM options也加上。别看是小问题,不处理真的会带走大量时间。
7.3 教大家一个通用的调试思路
看到异常信息先别慌,也别立刻改代码。我的排查顺序是这样:
第一,看完整的异常栈,定位报错发生在哪一层。Controller、Service还是DAO,异常信息最后几行会告诉你。
第二,看是配置问题还是代码问题。配置问题典型如找不到bean、接口绑定失败;代码问题典型如空指针、类型转换异常。
第三,如果是数据相关的问题,打印SQL和参数。MyBatis里开启控制台SQL日志,配置:
<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>开了之后,每次执行的SQL和参数都会打印在控制台,你可以直接在数据库客户端里执行这条SQL,看是不是SQL本身写错了。
这三板斧能解决绝大多数开发中的问题。真正没法解决的才需要去搜索或问人,而搜索时把异常信息原文复制进去,比加一堆形容词有用得多。
8. 部署上线前必须处理的几个生产环境细节
8.1 数据库账号与权限的最小化配置
开发环境你可能直接用了root账号连数据库,但部署到生产环境绝对不能这么干。要创建一个专门的账号,只授予业务库的必要权限。这样做的好处是即使应用被攻破,对方也只能操作这一个库,不会把整个数据库服务搞瘫痪。
权限控制到位之后,还得考虑数据备份。写一个定时任务脚本,每天凌晨用mysqldump把数据库导出到一个备份目录,保留最近七天的备份。我见过不止一次因为数据库被误操作全表删除而不知所措的情况,备份脚本真的能救命。
8.2 配置文件的外置化
项目打成war包后,jdbc.properties里的数据库密码是明文的,而且还打在包里面。如果哪天要改数据库密码,难道重新打包一遍?这太蠢了。
更好的做法是把配置文件放到Tomcat的某个目录下,或者利用Spring的环境变量占位符。比如${DB_PASSWORD},然后在Tomcat启动时传入环境变量。这样代码仓库里不会出现数据库密码这种敏感信息,运维也灵活。
这个项目作为教学项目可能不会考虑这么深,但你在简历上写“掌握项目部署”的时候,能说出这一层,会明显不一样。
8.3 日志埋点:出了问题有迹可循
开发阶段看控制台就算了,部署之后你怎么看日志?必须给项目配上log4j或logback。关键的业务操作——开台、点菜、结账、退款,都要打印业务日志。后面出了问题,翻日志是最直接的排查手段,没有日志排查问题就像瞎子摸象。
日志至少分两类:一是业务操作日志,谁在什么时间做了什么操作,方便审计追溯;二是异常日志,捕获到异常时用log.error("结账失败,订单编号: {}", orderId, e)的方式把异常栈完整打印出来。这种写日志的习惯在团队开发里特别重要。
9. 常见问题速查表
我整理了一下这个项目从搭建到上线最常遇到的几类问题,做成一个速查表:
| 问题现象 | 优先排查方向 | 具体做法 |
|---|---|---|
| 项目启动后访问登录页404 | 上下文路径配置 | 确认访问URL是否包含项目上下文路径 |
| 登录页面无样式无图片 | 静态资源被拦截 | spring-mvc.xml配置静态资源放行 |
| 登录提示数据库连接失败 | 数据库连接配置 | 检查IP、端口、账号密码、时区参数 |
| 中文乱码 | 编码过滤器缺失 | web.xml配置CharacterEncodingFilter为UTF-8 |
| Controller注入的Service为null | 包扫描配置 | 确认spring配置文件里component-scan覆盖了Service包 |
| MyBatis查询结果实体属性为null | 驼峰映射未启用 | 开启mapUnderscoreToCamelCase配置 |
| 事务不生效 | 配置未开启 | 确认spring-mybatis配置了事务管理并开启注解 |
| 部署到Linux后上传的图片丢失 | 路径写死本地磁盘 | 改成相对路径或配置上传文件的存储目录 |
| 高并发点单时订单号重复 | 时间戳方案碰撞 | 改用UUID或数据库自增主键 |
这张表是基于我做类似项目的经验汇总的,你实际遇到的具体问题可能有出入,但排查方向大差不差。保存住这张表,遇到问题先对照筛一遍,比你盲目在网上搜有效得多。
最后再说点实在的
这个餐厅收银系统项目,我前前后后帮人调试过不下十个版本,每次都会有一些新的发现。比如有的项目把桌台表设计成硬删除,导致历史订单关联信息丢失;有的项目在金额计算里用了float,最后统计报表数字对不上;有的项目完全没做事务控制,一旦点菜过程中数据库闪断,桌台就直接卡死在“使用中”。这些教训没有一个是从教科书上学到的,全是在实际调试中一个个踩出来的。
给你一个我自己带项目的习惯:拿到收银系统这类业务流程清晰的项目,先写一份事件流程表,把每一个用户操作会触发的数据变更列出来——操作A会更新哪些表、插入哪些记录、状态怎么流转。写完这份表再去写代码,你会发现思路清晰很多,而且这份表本身就是一份很好的项目设计文档。
如果你按照这篇文章的步骤把这个项目跑起来,并且动手改了其中两个功能,我相信你对SSM的理解深度会超过大部分只做过Demo的人。这个项目值得你花两个周末,值得。
举报