news 2026/11/5 3:24:00

SSM家装平台开发实战:从数据库设计到部署避坑全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM家装平台开发实战:从数据库设计到部署避坑全攻略

简介:该资源是基于SSM框架开发的暖心家装平台完整项目源码包,面向Java方向毕业设计、课程设计与期末大作业场景,适合需要快速搭建可演示的家装类Web系统的学生参考。压缩包共2000个文件,约101.68MB,包含178个Java源文件、174个JSP页面、318个JavaScript脚本、114个CSS样式表及89个XML配置等,目录结构完整,涵盖在线设计、材料选择、预算评估、施工监理与数据库管理模块,便于按模块阅读与二次开发。已有31人浏览学习,项目整合Spring、SpringMVC、MyBatis三层架构,并配有SQL脚本和前端资源,对理解SSM整合流程、家装业务数据建模及前后端交互具有直接参考价值。

1. 先说清楚:这个SSM家装平台到底是什么,值不值得做

如果你正在挑毕设题目或 Java Web 练手项目,“基于 SSM 的暖心家装平台设计”这个标题看着像普通管理系统,实际动手后会发现它比常见的商城、博客系统多一层麻烦:既要管装修案例这种内容展示,又要处理业主预约、设计师接单、线下量房、报价跟进这种连续业务。一个项目同时踩到 Spring 的对象管理和事务、SpringMVC 的数据绑定与拦截、MyBatis 的动态查询和参数绑定,恰好能把 SSM 三个框架都用到真正疼的位置。这个项目适合两类人:一类是要选毕设题目的学生,想找功能完整、答辩能讲清楚、代码能自圆其说的方向;另一类是刚学完 Java Web 的初学者,想用一个真实系统把三个框架串起来。我按照自己做过同类项目的思路,把从建表到能演示的完整路径写在下面,尽量让你看完能直接动手复现。

2. 跑通SSM家装平台的最小骨架:从数据库设计到项目结构

很多人在 SSM 项目里第一步就卡住,不是代码写不出来,而是表和表之间的关系没想清。家装平台里有业主、设计师、管理员三类人,也有案例、预约、报价、评价四类核心数据。把关系理顺,后面所有接口都顺。

2.1 数据库六张核心表:业务关系要提前定死

我一般不用复杂的权限表设计,直接用一个用户表加角色字段区分三类账号,再加一张设计师扩展表存从业年限和擅长风格,这样既好写 SQL,答辩也好讲。下面这几张表是常见做法,你可以按需加减字段:

create table t_user ( id int primary key auto_increment, username varchar(50) not null unique, password varchar(100) not null, real_name varchar(50) default null, phone varchar(20) default null, role tinyint not null default 2, -- 0管理员 1设计师 2业主 create_time datetime default current_timestamp ) engine=InnoDB default charset=utf8; create table t_designer ( id int primary key auto_increment, user_id int not null, avatar varchar(200) default null, specialty varchar(100) default null, years int default 0, intro varchar(500), status tinyint default 0, -- 0待审核 1正常 2冻结 constraint fk_designer_user foreign key (user_id) references t_user(id) ) engine=InnoDB default charset=utf8; create table t_case ( id int primary key auto_increment, title varchar(100) not null, cover_img varchar(200) default null, style varchar(30) default null, area decimal(10,2) default null, budget decimal(12,2) default null, designer_id int not null, description text, create_time datetime default current_timestamp, constraint fk_case_designer foreign key (designer_id) references t_designer(id) ) engine=InnoDB default charset=utf8; create table t_appointment ( id int primary key auto_increment, user_id int not null, designer_id int not null, case_id int default null, appoint_time datetime not null, address varchar(200) not null, note varchar(500), status tinyint not null default 0, -- 0待受理 1已确认 2已量房 3已完成 4已取消 create_time datetime default current_timestamp, constraint fk_appt_user foreign key (user_id) references t_user(id), constraint fk_appt_designer foreign key (designer_id) references t_designer(id) ) engine=InnoDB default charset=utf8;

剩下两张是报价表 t_quote 和评价表 t_comment:报价表关联预约单,存报价金额、报价文件路径、状态;评价表关联预约单和用户,存评分和文字评价。两张表属于附加业务,没有它们系统也能跑,但答辩时功能就少了一层。

这套设计的关键在于:预约表同时关联业主和设计师,把一次业务会话当成一条记录来跟踪状态;案例表挂在设计师下面,方便在案例详情页直接看设计师信息。把外键建好之后,MyBatis 里做嵌套查询或联表查询都方便,后面预约单要展示业主姓名和设计师姓名时,一条 join 就能查出来。

2.2 用Maven搭出三层骨架:依赖版本怎么选

SSM 的老问题就是依赖打架。我一般用 Spring 5.x、MyBatis 3.5.x、mybatis-spring 2.x 这组组合,JDK 用 8,Tomcat 用 8.5 或 9,能少踩很多坑。pom.xml 核心依赖如下:

<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.3.x</spring.version> <mybatis.version>3.5.x</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.x</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.x</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.x</version> </dependency> </dependencies>

这里我把 spring-jdbc 也加进来了,因为 Spring 管理 MyBatis 事务时需要它。Druid 负责连接池,整体思路是“DataSource 交给 Spring 创建,SqlSessionFactory 用 mybatis-spring 提供的 SqlSessionFactoryBean 装配”,你不需要手写 MyBatis 工具类。

版本背后的逻辑是:Spring 5.3 系列在 5.x 里属于长期维护分支,后续 6.x 要求 JDK 17 起的版本,对开发环境不太友好;MyBatis 3.5 系列和配套的 mybatis-spring 2.1 系列兼容性已经非常收敛。如果你还在用 Spring 4 和 mybatis-spring 1.x,也能跑,但 Spring 4 的老包名在 Tomcat 9 上会有兼容风险,所以新项目直接上这组版本更稳。具体的小版本号以你本地 Maven 中央仓库能拉到的为准,不用刻意追新。

2.3 第一行能响应的代码:把案例列表查出来

骨架搭好后,先写一个最小链路验证连通性。Controller、Service、Mapper 三个文件按经典分层写:

@Controller @RequestMapping("/case") public class CaseController { @Autowired private CaseService caseService; @RequestMapping(value = "/list", method = RequestMethod.GET) @ResponseBody public List<Case> list() { return caseService.listRecent(10); } }
@Service public class CaseService { @Autowired private CaseMapper caseMapper; public List<Case> listRecent(int limit) { return caseMapper.selectRecent(limit); } }
public interface CaseMapper { List<Case> selectRecent(@Param("limit") int limit); }

对应的 Mapper XML 里写一条最简单的 SELECT:

<select id="selectRecent" resultType="com.example.entity.Case"> select id, title, cover_img, style, area, budget, designer_id, description from t_case order by create_time desc limit #{limit} </select>

这段逻辑里有两个要点:Controller 用 @ResponseBody 返回对象,SpringMVC 借助 Jackson 自动序列化成 JSON,前端案例列表页直接拿 Ajax 数据渲染;Service 层目前是透传,但后面预约、报价的复杂事务都要在这一层写 @Transactional,所以先留出来。还有一点必须注意:实体类属性如果是驼峰命名,比如 coverImg,而数据库字段是 cover_img,需要在 mybatis-config.xml 里开启 mapUnderscoreToCamelCase=true,或者在 SQL 里写别名,否则值赋不上去。启动 Tomcat 后访问 /case/list,能返回一段 JSON 数组,才说明 Spring、SpringMVC、MyBatis 三个框架真正握上手了,这个黑匣子一旦打开,后面写功能就有底了。

3. 把“暖心”做成功能:预约与接单的完整链路

内容展示页面做完后,平台的核心是预约流程。业主看到案例心动了,发起预约;设计师在后台看到待受理单,确认后约量房。这条链路在答辩里是高频提问区,状态怎么流转、参数怎么校验、查询怎么做,都是送分题也是送命题。

3.1 预约订单状态机:五种状态不能乱跳

预约单状态用 tinyint 存数字,但代码里最好用枚举,避免魔法数字到处飞。我见过有人直接在 Controller 里写 if (status == 3),需求一变谁都改不了。用枚举把状态机固定在业务层:

public enum ApptStatus { PENDING(0, "待受理"), CONFIRMED(1, "已确认"), MEASURED(2, "已量房"), DONE(3, "已完成"), CANCELED(4, "已取消"); private final int code; private final String desc; ApptStatus(int code, String desc) { this.code = code; this.desc = desc; } public int getCode() { return code; } public String getDesc() { return desc; } public static ApptStatus of(int code) { for (ApptStatus s : values()) { if (s.code == code) return s; } throw new IllegalArgumentException("未知状态: " + code); } }

状态流转要有“谁允许谁”的规则。常见做法是:业主提交预约后是 PENDING;设计师确认后变 CONFIRMED;量完房设计师在后台标记 MEASURED;业主确认方案并线下付款后变 DONE;业主在 PENDING 或 CONFIRMED 时可以取消。关键坑位是 CANCELED 和 DONE 必须是终态,不能再回到 PENDING,否则报表统计会乱。业务层做一个状态变更方法,先查旧状态,再判断是否允许迁移,不允许就直接抛异常,提示“当前状态不可执行此操作”。枚举在页面上回显时也很方便,直接调用 getDesc() 就能显示中文,不用再写一堆 if 判断。

3.2 参数校验不能只靠前端:后端必须拦两道

预约表单里最有价值的是联系时间和房屋地址。有些同学把校验全部放在 jQuery 插件里,后端接口裸奔,随便传空字符串也能写库。答辩时老师只要在浏览器控制台里改掉提交数据,马上露出问题。后端校验最少做两件事:必填非空、长度上限和格式校验。手动校验虽然啰嗦,但可控性强:

@RequestMapping(value = "/submit", method = RequestMethod.POST) @ResponseBody public Result submit(@RequestParam Integer userId, @RequestParam Integer designerId, @RequestParam String appointTime, @RequestParam String address, @RequestParam(required = false) String note) { if (userId == null || designerId == null) { return Result.error("用户或设计师不能为空"); } if (address == null || address.trim().isEmpty() || address.length() > 200) { return Result.error("地址不能为空且不能超过200字"); } if (appointTime == null || !appointTime.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}")) { return Result.error("预约时间格式应为 yyyy-MM-dd HH:mm"); } appointmentService.submit(userId, designerId, appointTime, address, note); return Result.ok(); }

这里用正则校验时间格式是必要的:SpringMVC 默认把字符串转成 Date 时,对“2024-05-20 14:30”这种格式支持得并不好,直接绑定到 Date 字段容易报 400。与其在类型转换里折腾,不如先用字符串接收,校验通过后再手动 parse。你还可以把手机号、预算这类字段的校验规则沉淀成一个静态方法,Controller 里只调不写,代码短一半。前端校验只是体验层面的东西,后端校验才是系统安全的底线,答辩时老师重点看的就是这个底线守没守住。Result 对象是你自己的统一返回体,结构就是 code、message、data 三个字段,前后端约定好就行。

3.3 列表页的筛选查询:MyBatis 动态 SQL 是主力

业主端要按风格、面积、预算筛案例;设计师端要按状态筛预约单。这些筛选项组合起来,SQL 写死是不可能的。MyBatis 的动态 SQL 在这里就是主力,配合手写分页参数,效果非常直观:

<select id="selectCases" resultType="com.example.entity.Case"> select id, title, cover_img, style, area, budget, designer_id, description, create_time from t_case <where> <if test="style != null and style != ''"> and style = #{style} </if> <if test="minArea != null"> and area &gt;= #{minArea} </if> <if test="maxBudget != null"> and budget &lt;= #{maxBudget} </if> </where> order by create_time desc limit #{offset}, #{pageSize} </select>

标签会自动去掉第一个多余的 and,所以前面几条条件不用纠结前缀。这里我特意写了 >= 和 <=,因为 XML 文件里 > 和 < 是特殊字符,直接写会报 XML 解析错误,这是新手上路必踩的一脚。limit #{offset}, #{pageSize} 是手写分页的方式,offset 从 0 开始,每页几条由 pageSize 决定。如果你想用 PageHelper 也可以,但手写对答辩更友好,老师一眼能看懂,而且不会出现拦截器把分页 SQL 拦截坏的奇奇怪怪的问题。

如果做的是老式 JSP 页面,那要把筛选条件放到 Model 里回显,否则翻页后条件丢失;如果做的是前后端分离的 JSON 接口, 里的条件直接对应前端传来的对象字段。动态 SQL 在 MyBatis 里的地位相当于“可以组装的长串 SQL 模板”,用好了列表页基本不愁。

4. 管理端与文件上传:把案例图和报价单落地

功能链路通了一半后,管理端是另一个重头:装修案例要有封面图,设计师要传报价单。文件上传、后台登录拦截、Excel 导出,这三块是 SSM 项目里最常被问到的实操点。把这几样搞定,项目就不只是增删改查的展示层玩具。

4.1 文件上传:本地存储的最简方案

毕设项目引 OSS 有点重,本地存文件是常见做法。写一个上传接口,接收 MultipartFile,把文件写到项目的 upload 目录,文件名用 UUID 重命名,防止中文名和重名问题:

@RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public Result upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { if (file.isEmpty()) { return Result.error("文件为空"); } String originalName = file.getOriginalFilename(); String ext = originalName != null ? originalName.substring(originalName.lastIndexOf(".")) : ".jpg"; String fileName = UUID.randomUUID().toString().replace("-", "") + ext; String realPath = request.getServletContext().getRealPath("/upload/"); File dir = new File(realPath); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir, fileName)); } catch (IOException e) { e.printStackTrace(); return Result.error("上传失败"); } return Result.ok("/upload/" + fileName); }

两个关键点。第一,getRealPath 拿到的是 Tomcat 解压后的目录,重启后如果重新解压,文件会丢失;如果想持久一点,可以把上传目录配成绝对路径,比如 D:/upload,再把 /upload/** 映射到那个目录。第二,图片上传后如果直接回显,需要保证 SpringMVC 放行静态资源,否则 标签访问 /upload/xxx.jpg 会被前端控制器拦掉,这事我在 5.4 里单独说。

上传接口最好再限制一下文件大小:Spring 的 multipart 解析器在 XML 里配 max-upload-size,超过会抛 MaxUploadSizeExceededException,注意要捕获它并返回 JSON,而不是让页面白屏。毕设里一般限制 5MB 以内就够用,避免有人拿大文件把磁盘塞满。

4.2 后台登录拦截:拦截器比过滤器更可控

管理端的页面和接口必须登录后才能访问。SSM 项目里常见的做法是用 SpringMVC 拦截器,而不是 Servlet 过滤器,原因是拦截器能拿到 Handler 对象,而且天然只拦截 SpringMVC 管理的路径。先写一个登录拦截器:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object admin = session.getAttribute("admin"); if (admin == null) { response.sendRedirect(request.getContextPath() + "/admin/login"); return false; } return true; } }

再注册进 springmvc.xml:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/admin/**"/> <mvc:exclude-mapping path="/admin/login"/> <mvc:bean class="com.example.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

这里有一个容易踩的坑: mvc:exclude-mapping 只排除了一层的 /admin/login,如果登录页还依赖 /admin/css/style.css 这种静态资源,那些访问也会被拦截。所以要么把静态资源放在 /static/** 下并配置放行,要么在 exclude-mapping 里加上 /admin/css/**。更省事的做法是登录页完全不使用样式,但毕设里没必要,把静态资源路径规划清楚就够了。拦截器里拿 session 判断空值后直接重定向,逻辑简单直接。千万别在拦截器里写业务逻辑,比如查数据库判断用户是否被禁用,那会让每次请求都多一次查询,不合适。

4.3 报价单导出:用 POI 生成 Excel

设计师确认量房后,需要把预算项目、单价、总价打成报价单。导出 Excel 用 Apache POI 是常见方案。写一个导出接口,直接在响应流里生成文件,前端触发下载:

@RequestMapping(value = "/quote/export/{appointmentId}") public void exportQuote(@PathVariable Integer appointmentId, HttpServletResponse response) throws IOException { Quote quote = quoteService.getByAppointmentId(appointmentId); response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=quote_" + appointmentId + ".xls"); try (Workbook wb = new HSSFWorkbook()) { Sheet sheet = wb.createSheet("报价单"); String[] headers = {"项目", "数量", "单位", "单价", "合计"}; Row headRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headRow.createCell(i).setCellValue(headers[i]); } // 这里从 quote.getItems() 循环写数据行,items 是报价明细列表 for (int i = 0; i < quote.getItems().size(); i++) { QuoteItem item = quote.getItems().get(i); Row row = sheet.createRow(i + 1); row.createCell(0).setCellValue(item.getName()); row.createCell(1).setCellValue(item.getCount()); row.createCell(2).setCellValue(item.getUnit()); row.createCell(3).setCellValue(item.getPrice().doubleValue()); row.createCell(4).setCellValue(item.getTotal().doubleValue()); } wb.write(response.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } }

这里选择 HSSFWorkbook 存成 .xls 格式,是因为兼容性最稳,老浏览器也不挑。响应头里的 Content-Disposition 是触发下载的关键,漏了它浏览器就会直接以文本形式打开乱码。POI 导出千万别把 Workbook 的关闭写在 write 之前,否则文件写不完整。使用 try-with-resources 自动关闭,顺序上先 write 再关,数据就不会丢。生成的文件只存在于响应流里,不落盘,这也是常见做法,既省磁盘又不用考虑清理逻辑。

5. 避坑笔记:SSM 项目里最常见的 6 个翻车现场

这部分是我自己的血泪经验汇总。SSM 不像 Spring Boot 开箱即用,配置错一处就起不来,日志还特别“委婉”。下面六条按“现象→原因→解决”写,踩过一次就不会再来。

5.1 日期参数一提交就报 400

现象:前端传“2024-05-20 14:30”,后台用 Date 字段接收,SpringMVC 直接回 400,控制台报类型转换失败。

原因:SpringMVC 默认没有注入日期转换器,字符串转 Date 不认识这种格式。用 @DateTimeFormat 注解也经常因为格式不匹配而失效。

解决:如果只有预约时间一个日期参数,用 String 接收再手动 SimpleDateFormat 解析;整个项目日期字段多的话,定义一个 @ControllerAdvice,通过 @InitBinder 注册自定义编辑器,统一处理 yyyy-MM-dd 和 yyyy-MM-dd HH:mm 两种格式。很多同学宁愿每个 Controller 都写一遍转换器也不愿意建公共基类,后面维护起来全是重复代码,在项目里这种重复就是坏味道。

5.2 MyBatis 的 #{} 和 ${} 混用导致 SQL 注入

现象:列表页有个排序下拉框,传 asc 和 desc 一直正常。某天有人把排序参数改成一条带分号的拼接语句,数据库直接报错或数据异常;更严重的情况是表被清空。

原因:排序字段用了 ${} 拼进 SQL。${} 是纯字符串占位,MyBatis 不预编译也不做转义,用户传什么它就拼什么,这等于把 SQL 注入的口子主动打开。

解决:值参数一律用 #{};动态列名,比如排序字段,必须做白名单。最稳的写法是不接收用户原始值,而是接收 code,在代码里 map 成固定列名。比如前端传 sort=time,后端转成 create_time 再拼进去。这种做法把用户能控制的输入空间缩成几个固定选项,注入面就不存在。另外,数据库账号要单独建一个只具备增删改查权限的账号,别让项目用 root 连库。毕设里很少有人注意这点,但我见过翻车最多的就是这里。

5.3 JSON 序列化出现 StackOverflow

现象:接口返回设计师的案例列表,浏览器端收到的是 500,日志里是一长串 StackOverflowError。

原因:实体类里存在双向关联,比如 Case 里有 designer 对象,Designer 里又有 List 。Jackson 序列化时 A 引用 B、B 又引用 A,无限递归。

解决:在发生递归的实体属性上加 @JsonIgnore,或者在属性上用 @JsonManagedReference 和 @JsonBackReference 配一对。如果想省事,干脆放弃对象嵌套,用 VO 类只放需要展示的平铺字段,DTO 只承载页面需要的数据。这是最不容易出问题的做法,也是现在越来越流行的做法,答辩时说起来也体面:解耦。

5.4 静态资源被 DispatcherServlet 拦掉

现象:Tomcat 启动后登录页能打开,但 CSS、JS、上传的图片全部 404,浏览器控制台显示资源加载失败。

原因:web.xml 里把 DispatcherServlet 映射到 /,所有请求都进了 SpringMVC,而 SpringMVC 默认没有配置静态资源映射,遇到 .css、.js、图片请求直接返回 404。

解决:在 springmvc.xml 里加两个标签,按需二选一:

<!-- 方式一:交给容器默认Servlet处理 --> <mvc:default-servlet-handler/> <!-- 方式二:显式映射静态资源目录 --> <mvc:resources mapping="/css/**" location="/static/css/"/> <mvc:resources mapping="/upload/**" location="file:D:/upload/"/>

方式一简单省事,适合静态文件没有打散的情况。方式二适合 /upload 单独指向磁盘目录的场景,注意 location 写成 file:D:/upload/ 时最后必须带斜杠。如果两种都配上,一般不会冲突,但建议只配一种,排查起思路清晰。

5.5 连接池连接数设得太大,MySQL 直接拒绝连接

现象:并发不高,但应用运行一段时间后突然报 Too many connections,重启 Tomcat 又正常。过一段时间又出现,周而复始。

原因:Druid 连接池最大连接数配了 200,但 Service 层没有统一事务管理,或者某个地方自己 new 了 SqlSession 没有关闭,连接泄漏。毕设项目并发很低,最大连接数配 20 完全够用,配 200 是典型的高配低用加风险自担。

解决:连接池参数用 Druid 默认值附近,maxActive 设为 20,initialSize 设为 5。重点检查所有 Service 方法是否统一由 Spring 管理事务,不要混用 MyBatis 原生 SqlSession。排查连接泄漏时,可以在应用关闭前打印连接池活跃数,如果活跃数一直不归零,说明有连接没释放。

5.6 Maven 打包缺失 lib 目录

现象:IDEA 里点绿按钮跑 Tomcat,功能一切正常;执行 mvn package 把 war 放到服务器上,启动到一半抛 ClassNotFoundException,打开 war 发现 WEB-INF/lib 目录是空的。

原因:IDEA 的 Run 配置用的是 Artifact,它会把依赖放进自己输出的目录;Maven 的 package 生命周期则按 pom 里依赖的 scope 和插件配置产出 war。两者不是一回事。如果依赖被标成 provided,比如 Servlet API,或插件配置有问题,依赖就不会被打进 WEB-INF/lib。

解决:统一以 Maven 的 package 为准。打包前先 mvn clean,别加 -o 离线参数,确保本地仓库依赖齐全。打包后看 war 包体积,SSM 项目至少几十 MB 才正常,要是只有几百 KB,基本就是 lib 没进去。在 pom 里把 Servlet API 这类容器提供的依赖保留 provided,数据库驱动 scope 用 runtime,其余默认 compile。插件用 maven-war-plugin 3.x 默认配置,不要为了省 web.xml 把 failOnMissingWebXml 改为 false 又强行打包。

6. 把项目从源码变成可演示系统:最后一道验证清单

系统做完到答辩前,最怕的是演示现场翻车。按下面的清单走一遍,能避免大多数临时状况。

6.1 部署前必须检查的六个文件

第一是数据库脚本,确认 ddl.sql 能在干净的 MySQL 上完整执行,不存在重复建表报错。第二是 jdbc.properties,数据库地址、用户名、密码必须和当前环境一致,尤其是密码里有特殊字符时要注意转义。第三是 springmvc.xml,注解扫描的包名必须和实际包路径一致,不一致会报 Controller 找不到。第四是 applicationContext.xml,Service 和 Mapper 的扫描包名同样要对上。第五是 web.xml,Spring 容器监听器、DispatcherServlet 的 url-pattern、字符编码过滤器这三项缺一不可。第六是 pom.xml,确认依赖没有标错 scope,特别是数据库驱动和 Servlet API 这两类。

检查时我习惯用 IDEA 的 Diagram 看依赖冲突。如果 pom 里同时出现两个版本的 spring-webmvc,运行时最操蛋。先把六份文件过一遍,再启动 Tomcat,看日志里有没有 “Bean named xxx is not defined” 这类关键字,有就说明扫描路径错了,直接改配置而不是在代码里加 @Component 硬凑。

6.2 演示前按表打勾

数据比代码更重要。数据库里至少要有 2 个设计师、5 个案例、3 条不同状态的预约单。演示时没有数据就等于给老师看空壳。把验证点做成表格,演示前按行打勾:

验证点操作路径预期结果
业主注册登录首页 → 注册 → 登录跳转首页,显示登录名
案例列表筛选案例页选“现代风格”只显示该风格案例
发起预约案例详情 → 立即预约生成待受理预约单
设计师接单后台 → 预约管理 → 确认状态变为已确认
上传封面图后台 → 案例管理 → 上传列表显示新图
导出报价单报价单 → 导出 Excel下载 .xls 且可打开

我自己的教训是:演示前一定先跑一遍“退出登录再登录”的完整流程。很多同学页面停留在已登录状态,演示时一刷新才发现 session 失效,一脸尴尬。把上面六行表格验证完,你的系统就是完整可交付的。希望帮到你,做毕设或练手项目,最重要的是自己能把每一行配置讲明白,能讲明白的代码才是你的东西。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 3:59:40

Windows安装MySQL 8.0完整教程:从下载到常见排错

1. 写在前面&#xff1a;为什么Windows装MySQL 8.0是个正经事先说实话&#xff0c;MySQL 8.0在Windows上的安装&#xff0c;本质上不难&#xff0c;但踩坑的人是真多。我见过不少同学卡在初始化报错、服务起不来、密码不对这些环节&#xff0c;来回折腾几个小时&#xff0c;最后…

作者头像 李华
网站建设 2026/11/4 12:00:52

从零实现K-Means聚类算法:原理、细节与Python代码解析

K-Means大概是机器学习里最容易被低估的算法了。表面上看&#xff0c;它简单到可以用一句话概括&#xff1a;把数据分成K堆&#xff0c;每堆找中心点&#xff0c;反复迭代到稳定。但真到了自己动手从零实现的时候&#xff0c;你才会发现里面塞满了细节——质心怎么初始化、距离…

作者头像 李华
网站建设 2026/10/24 3:58:29

CAN总线物理层详解:双绞线、120Ω终端电阻与抗干扰实战

1. 从一次深夜救火说起&#xff1a;CAN总线通信崩了&#xff0c;先别急着换芯片 半夜两点&#xff0c;台架上的ECU通信突然像得了癫痫&#xff0c;报文错误率高得离谱&#xff0c;仪表盘乱码&#xff0c;电机抖动&#xff0c;连救命的Bootloader都进不去。我蹲在车旁边&#xf…

作者头像 李华
网站建设 2026/10/25 11:57:49

轨迹可视化实战:坐标转换、抽稀与高性能回放全解析

轨迹可视化这件事&#xff0c;我做过的项目不算少&#xff0c;从共享出行平台的司机接单路径回放&#xff0c;到物流客户的车辆历史轨迹查询&#xff0c;再到无人机巡检的航线复现&#xff0c;说来说去都绕不开“轨迹点显示”这五个字。很多人觉得这玩意儿简单&#xff0c;不就…

作者头像 李华
网站建设 2026/11/4 12:56:04

TFT LCD串扰深度解析:水平与垂直干扰的成因及调试对策

开春那阵子我在调一块1.8寸的TFT LCD模块&#xff0c;128x160分辨率&#xff0c;ST7735S驱动&#xff0c;SPI接口。程序跑起来后我往屏幕中间画了一个约128x80像素的中灰色块&#xff0c;四周铺纯白。结果很奇怪&#xff1a;灰块左右两侧各出现一条明显的亮边&#xff0c;上下边…

作者头像 李华