SSM框架入门到实践:从SpringMVC到MyBatis搭建内容管理后台
2026/9/23 12:46:30 网站建设 项目流程

简介:基于SSM框架的风俗文化管理系统完整项目,适用于Java Web毕业设计、课程实训以及SSM初学者。系统采用B/S结构,后台使用Spring、SpringMVC、MyBatis,前台使用JSP,数据库为MySQL,分管理员与用户两种角色,涵盖节日、饮食、服饰、礼仪、信仰、建筑等风俗信息的管理与展示,并支持个人中心、留言板、论坛、收藏管理等功能。压缩包为zip格式,大小约26.05MB,内含项目源码及数据库脚本,可直接导入IDE学习二次开发。资源已有106人学习,适合需要参考SSM完整项目结构、后台管理设计或撰写毕业设计文档的读者,可通过源码快速理解权限控制、增删改查与B/S端交互的实现思路。

1. 风俗文化管理系统为什么还在用 SSM:一个老框架的务实之选

拿到一个“基于SSM的风俗文化管理系统.zip”,先别急着解开压缩包看代码。这个名字里最关键的信息是“SSM”,也就是Spring + SpringMVC + MyBatis三件套,它决定了这个系统的技术基因。很多刚接触项目的读者会问:现在Spring Boot都出到3.x了,为什么还要去碰SSM?真实场景是,大量院校、文化馆、非遗保护中心的信息化系统,至今仍跑在SSM结构上,尤其以“zip包交付”形式存在的项目,往往需要你在本地部署、改代码、跑通演示,这种场景下SSM反而是最稳的选择——它结构清晰、依赖透明、IDE支持成熟,出了问题能顺着JSP页面一路查到SQL,排查链路一目了然。这篇笔记就从这个zip项目出发,把它背后的领域模型、落地路径、部署细节和坑全拆开,让你拿到类似项目时能直接上手复现,而不是对着代码发懵。

这个系统解决的是“民俗资源数字化管理”的通用诉求:民族风俗条目、节庆活动、非遗项目、场地信息、图片资料,再加上管理员和普通用户两级权限,本质上是一套带内容管理属性的后台系统。它的受众很明确——需要交付课程设计、需要给文化馆做内部系统、或者想借一个完整案例吃透SSM的开发者。接下来我把整个系统的骨架拆给你看。

2. SSM 框架选型与项目结构拆解:为什么这套组合三十年不过时

2.1 Spring 的 IoC 容器:把对象管理权交出去

先看Spring在系统里干了什么。传统Java开发里,Service要调用DAO,你得自己new一个DAO实现类,模块一多,依赖关系就成一团乱麻。Spring引入控制反转后,所有Bean的创建和注入都交给容器管理。在风俗文化管理系统里,典型表现是Service层从不直接new一个Mapper,而是通过@Autowired或XML配置注入。这样做的好处是替换实现类时不用改业务代码,比如把SQL日志从控制台输出切到文件输出,只需要改配置,不用动Java代码。

<!-- applicationContext.xml 核心配置示例 --> <context:component-scan base-package="com.culture.system" /> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/culture_db?useSSL=false&amp;characterEncoding=utf8" /> <property name="username" value="root" /> <property name="password" value="your_password" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.culture.system.dao" /> </bean>

这段配置是整个SSM项目的地基层:第一行扫描业务组件,把@Service@Repository标注的类自动注册为Bean;数据源里的characterEncoding=utf8是中文民俗资料不乱码的前提;mapperLocations指定MyBatis的SQL映射文件位置;MapperScannerConfigurer让MyBatis自动为DAO接口生成代理实现。参数上有两点值得注意:一是MySQL 8.x必须用com.mysql.cj.jdbc.Driver,老项目里的com.mysql.jdbc.Driver会直接启动报错;二是密码不要硬编码,正式环境用${}占位符配合properties文件加载。

2.2 SpringMVC 的请求流转:前端请求如何在三层间走一圈

SpringMVC是SSM里的“交通警察”,所有前端请求都先到DispatcherServlet,再由它分发给对应Controller。在风俗文化管理系统中,一个典型的请求路径是:用户点击“查看春节习俗详情” → 浏览器发送GET /custom/detail?id=5→ DispatcherServlet根据@RequestMapping匹配到CustomController.detail()方法 → Controller调用CustomService.getById()→ Service调DAO → MyBatis执行SQL返回Custom对象 → 数据塞进ModelAndView→ JSP渲染后返回浏览器。

@Controller @RequestMapping("/custom") public class CustomController { @Autowired private CustomService customService; @RequestMapping("/detail") public String detail(Integer id, Model model) { Custom custom = customService.getById(id); model.addAttribute("custom", custom); return "custom/detail"; // 对应 /WEB-INF/views/custom/detail.jsp } }

这段Controller有三个坑要提醒:一是Integer id这种参数绑定要求请求参数名与形参名一致,前端传的是customId就绑定不上,得加@RequestParam("customId");二是返回的字符串是逻辑视图名,真正的JSP路径由InternalResourceViewResolver的前后缀拼接而成,配置里如果写了/WEB-INF/views/前缀,那JSP必须放对位置,否则白屏;三是Model里塞的对象,在JSP里用${custom.name}就能取到,但前提是JSP页面头部引入了JSTL标签库。

2.3 MyBatis 的持久层设计:SQL 写的明白,性能才靠得住

MyBatis在SSM中负责数据库交互,定位是“半自动ORM框架”。它不像Hibernate那样完全由框架生成SQL,而是把SQL语句交给你写、把参数映射和结果映射交给框架处理。在风俗文化系统里,民俗条目的多条件分页查询是最典型的场景:按名称模糊搜、按类型过滤、按地区统计,这种动态SQL在MyBatis里有专门的支持。

<!-- CustomMapper.xml 核心查询片段 --> <select id="selectByCondition" parameterType="map" resultType="com.culture.system.entity.Custom"> SELECT c.id, c.name, c.type, c.region, c.content, c.create_time FROM custom c <where> <if test="name != null and name != ''"> AND c.name LIKE CONCAT('%', #{name}, '%') </if> <if test="type != null and type != ''"> AND c.type = #{type} </if> <if test="region != null and region != ''"> AND c.region = #{region} </if> </where> ORDER BY c.create_time DESC LIMIT #{offset}, #{pageSize} </select>

<where>标签是MyBatis的特色,它自动处理第一个条件前的AND关键字,避免你手写WHERE 1=1这种尴尬写法。#{}预编译参数能防止SQL注入,${}则是字符串拼接,一般只用于动态列名或排序字段。分页用的LIMIT #{offset}, #{pageSize}适合数据量在万级以内的系统,如果民俗条目超过十万条,就得换PageHelper插件或者基于游标的分页方案——但作为课程设计级别的系统,这个写法足够且直观。参数说明:parameterType="map"表示Controller层传入的是一个HashMap,里面放name、type、region、offset、pageSize五个键,MyBatis会自动按#{}里的键名取值,键名必须与其中内容完全一致,否则会报There is no getter for property named xxx

3. 数据库表设计与人核心业务建模:民俗数据怎么落到 MySQL 里

3.1 核心实体关系:民族风俗、节庆活动与资源文件的关联

风俗文化管理系统的数据库设计,核心是回答“有哪些实体、实体之间什么关系”。一般来说至少五张主表:用户表(管理员与普通用户)、民族风俗表(各个少数民族的习俗条目)、节庆活动表(节日、庆典、仪式)、场地表(文化广场、宗祠、博物馆)、非遗项目表(手工艺、戏曲、技艺)。外键关系上,民族风俗表与节庆活动表是多对多——一个节日可以有多个习俗,一个习俗也出现在多个节日里,因此需要一张关联表来解耦。

CREATE TABLE custom ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT '习俗名称', type VARCHAR(50) COMMENT '习俗类型:饮食/服饰/婚嫁/祭祀等', region VARCHAR(100) COMMENT '分布地区', content TEXT COMMENT '详细描述', cover_image VARCHAR(255) COMMENT '封面图片路径', view_count INT DEFAULT 0 COMMENT '浏览次数', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='民族风俗信息表'; CREATE TABLE custom_festival_rel ( custom_id INT NOT NULL, festival_id INT NOT NULL, PRIMARY KEY (custom_id, festival_id), CONSTRAINT fk_cf_custom FOREIGN KEY (custom_id) REFERENCES custom(id), CONSTRAINT fk_cf_festival FOREIGN KEY (festival_id) REFERENCES festival(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='风俗与节庆关联表';

utf8mb4而不是utf8,是因为民俗内容里大量出现生僻字和特殊符号(比如古壮文、彝文转写符号),MySQL的utf8最多存3字节,遇到4字节字符就报Incorrect string value错误。COMMENT给字段加注释,这是团队协作的保命习惯——半年后你回来看SQL,没有注释的字段名和天书没什么两样。外键约束在中小系统里建议保留,它保证关联表不会出现悬空引用,但要注意:线上高并发场景外键是性能杀手,很多团队会去掉外键在业务层做校验。这个系统属于管理类系统,保留外键反而更安全。

3.2 权限模型:两张表还是三张表

用户权限设计是这个系统另一个关键点。常见做法是RBAC模型:用户表、角色表、用户角色关联表。因为需求里管理员和普通用户两个角色,有的项目偷懒只用一张用户表加role字段区分,这在小规模场景下没毛病,但一旦以后加“内容审核员”“数据录入员”角色,就要改表结构加字段,并改动所有涉及权限的查询。我的建议是第一次就按三张表设计,成本低、扩展空间大。

CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密后的密文', nickname VARCHAR(50), role VARCHAR(20) DEFAULT 'ROLE_USER' COMMENT 'ROLE_ADMIN / ROLE_USER', status TINYINT DEFAULT 1 COMMENT '1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sys_role ( id INT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(20) NOT NULL UNIQUE, role_name VARCHAR(50) NOT NULL ); CREATE TABLE sys_user_role ( user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id) );

密码字段用BCrypt加密后的密文做注释,这就引出一个SSM项目的常见安全问题:很多老项目的密码是MD5原始摘要存储,一旦数据库泄漏,彩虹表直接跑出明文密码。Spring Security框架里自带BCrypt实现,但SSM项目不一定集成了Security——很多用Shiro甚至自定义拦截器。不管哪种方案,数据库里绝不能存明文密码,BCrypt或至少加盐的SHA-256是底线。此外status字段用于账号禁用,这个细节在实际运维中非常实用,比直接删除用户能保留操作日志的完整性。

3.3 文件表与路径策略:图片上传后的数据落点

民俗文化系统肯定要传图片——习俗照片、活动海报、非遗作品图。文件有两种存法:存数据库的BLOB字段,或存磁盘/OSS、数据库只存路径。管理类系统建议后者,理由很实际:BLOB字段会导致表体积膨胀,备份和查询都变慢,而且展示时要额外写流输出的代码。路径策略上,我建议按日期分目录存储,避免单目录文件数过多:

/upload/2025/05/17/20250517103025_001.jpg

对应数据库字段存的就是/upload/2025/05/17/20250517103025_001.jpg。这串路径由日期加时间戳加序号拼接,可以保证唯一。部署到Tomcat时,上传根目录有两种配置方式:一是放在WebContent/upload下随应用部署,简单但war包重发会丢文件;二是配置在Tomcat的server.xml里用虚拟目录映射到外部磁盘。

<!-- 在Tomcat的server.xml的Host节点下配置 --> <Context path="/upload" docBase="/data/culture_upload" />
// Controller里保存文件的核心代码 String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String datePath = new SimpleDateFormat("yyyy/MM/dd").format(new Date()); String fileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "_" + System.nanoTime() % 1000 + ext; String realPath = "/data/culture_upload/" + datePath + "/" + fileName; File dest = new File(realPath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest);

这段代码里有三个细节值得推敲:originalFilename.lastIndexOf(".")取出文件后缀,防止用户上传带多个点号的文件名导致后缀截取错乱;System.nanoTime() % 1000加三位纳秒取模,有效降低同一秒内重名覆盖概率;mkdirs()创建多级目录,保证日期目录不存在时报错。文件后缀的校验务必要做白名单:.jpg/.jpeg/.png/.gif,否则用户上传一个.jsp文件就能在Tomcat下直接执行,这是高危漏洞。

4. 核心功能模块实现:从登录鉴权到民俗内容发布的完整代码走读

4.1 登录拦截与用户会话处理:拦截器配置比 Security 更轻

SSM项目做登录鉴权,最常见的是用SpringMVC的HandlerInterceptor定义拦截器,在spring-mvc.xml里注册拦截路径。这比引入Spring Security轻量得多,对于只有管理员和普通用户两个角色的系统完全够用,代码也更直观。

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("loginUser"); String uri = request.getRequestURI(); // 放行静态资源和登录请求本身 if (uri.contains("/login") || uri.endsWith(".css") || uri.endsWith(".js") || uri.endsWith(".jpg") || uri.endsWith(".png")) { return true; } if (user == null) { // AJAX请求返回JSON提示,普通请求重定向到登录页 if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录或会话已过期\"}"); } else { response.sendRedirect(request.getContextPath() + "/login"); } return false; } return true; } }
<!-- spring-mvc.xml 里注册拦截器 --> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/login/**" /> <mvc:exclude-mapping path="/resources/**" /> <mvc:exclude-mapping path="/upload/**" /> <bean class="com.culture.system.interceptor.LoginInterceptor" /> </mvc:interceptor> </mvc:interceptors>

拦截器有个新手必踩的坑:把CSS、JS、图片也拦截了。你登录成功后页面刷新,样式全丢,查看请求发现.css被重定向到/login。解决方案就是上面代码里的两层放行逻辑——XML配置里排除静态资源目录,Java代码里再兜底判断后缀名。还有一种情况是AJAX请求被拦截后返回了登录页HTML,前端解析JSON直接报错,所以必须区分普通请求和异步请求,让异步请求拿到code:401后自行跳转登录页而不是整个页面被替换。

4.2 民俗内容的增删改查:参数校验与事务控制的取舍

内容管理模块是系统的重头戏,这里给出新增民俗信息的完整链路,包含前端提交、后端校验、Service层事务、DAO层插入四个环节。由于涉及代码较多,我拆成前后端两个片段说明。先看Service层实现:

@Service public class CustomServiceImpl implements CustomService { @Autowired private CustomMapper customMapper; @Autowired private CustomFestivalRelMapper relMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean addCustom(Custom custom, List<Integer> festivalIds) { // 参数基础校验 if (custom.getName() == null || custom.getName().trim().isEmpty()) { throw new ServiceException("习俗名称不能为空"); } if (custom.getContent() != null && custom.getContent().length() > 65535) { throw new ServiceException("内容描述过长,请精简后提交"); } // 插入主表 int insertCount = customMapper.insert(custom); if (insertCount != 1) { throw new ServiceException("新增民俗信息失败"); } // 维护和节庆活动的多对多关联 if (festivalIds != null && !festivalIds.isEmpty()) { for (Integer festivalId : festivalIds) { relMapper.insertRel(custom.getId(), festivalId); } } return true; } }

@Transactional注解是SSM项目数据库操作的核心防线——它的含义是:当方法内抛出任何RuntimeException时,整个事务回滚。加到方法上后,如果主表插入成功但关联表插入失败,数据库会回到调用该方法前的状态,避免出现“有风俗条目但没有关联任何节庆”的数据孤儿。参数说明:rollbackFor = Exception.class指定只有Exception及其子类触发回滚,这个参数默认值其实只回滚运行时异常,检测异常不会回滚,所以显式声明更稳妥。custom.getId()之所以能拿到自增主键,是因为MyBatis的insert标签里配置了useGeneratedKeys="true" keyProperty="id",这个配置经常被遗漏,导致插入后拿不到新记录的ID。

4.3 首页轮播与推荐位:一张表实现后台可配置

很多SSM项目的首页会展示热门民俗、推荐活动,数据来源无非两种:写死的SQL查询(比如按浏览量排序取前5条),或者通过配置表让管理员在后台调整。后者体验明显更好——不用改代码就能控制首页展示哪几条。配置表设计如下:

private Integer configId; // 主键 private String configType; // 类型:banner/hot_custom/hot_festival private Integer targetId; // 关联的业务表主键 private Integer sortOrder; // 排序值,越小越靠前 private Integer status; // 状态:1显示 0隐藏

查询时按configTypestatus过滤,按sortOrder升序排列,再根据targetId去业务表里取完整数据。这个设计的好处是前后端完全解耦:新增一个推荐位,只要在数据库里插一条配置,前端刷新就能看到,不用重新部署。坏处是查询时要多一次关联查询,但对管理类系统来说性能完全可忽略。

5. 部署与运行的典型问题排查:本地跑通这套系统的五个翻车点

5.1 启动就报ClassNotFoundException:JDK 版本与依赖冲突的玄学

现象:Tomcat一启动,控制台抛出java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet,或者更隐蔽的NoClassDefFoundError,但检查pom.xml里明明引入了Spring相关依赖。

原因:99%的情况是项目依赖没有正确发布到Tomcat的WEB-INF/lib目录。尤其是用IDEA开发时,默认情况下新建的Web项目不会自动把Maven依赖加入war包,需要在Project Structure(项目结构)里把Maven依赖添加为Web应用库,或者右键项目选择添加为Maven项目。另一个隐蔽原因是JDK版本:本地用JDK 17编译,但SSM老项目很多基于JDK 8,在pom.xml里用maven-compiler-plugin指定source和target为1.8即可解决,否则编译产物用了新字节码格式,Tomcat 8.5以下的低版本识别不了。

解决:在IDEA中File → Project Structure → Artifacts,找到对应war包,在可用元素里找到,双击把Maven依赖加进WEB-INF/lib。同时在pom.xml加入编译插件配置,固定JDK版本。

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin>

5.2 数据库连接报Public Key Retrieval is not allowed

现象:系统启动时能加载Spring,但第一次连接MySQL就抛com.mysql.cj.jdbc.exceptions.CommunicationsException: Public Key Retrieval is not allowed,或者提示Client does not support authentication protocol requested by server

原因:MySQL 8.x默认使用caching_sha2_password插件做认证,MySQL Connector/J 8.0.x客户端默认不允许在非SSL连接下获取服务端公钥来加密密码传输。SSM老项目的数据库连接URL里通常只写jdbc:mysql://localhost:3306/xxx,缺少allowPublicKeyRetrieval=trueuseSSL=false这组参数。

解决:在JDBC URL后面追加参数,改完重启Tomcat就能解决。

jdbc:mysql://localhost:3306/culture_db?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8

这里有三个参数是MySQL 8.x连接必备的:useSSL=false关闭SSL传输;allowPublicKeyRetrieval=true允许客户端从服务端获取公钥;serverTimezone=Asia/Shanghai指定时区。漏了最后一个会看到The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized,这是MySQL时区信息与Java默认时区不一致导致的时区换算失败。顺便说一句,老项目如果用的MySQL 5.7,这个坑就不存在,问题反而出在驱动版本上。

5.3 页面能开但图片全部裂开:上传目录与虚拟路径的配置脱节

现象:本地跑通后,新增民俗时上传的图片在后台列表里能正常显示,但把项目部署到正式Tomcat后,所有上传的图片都404。

原因:开发和部署的环境根路径不同。开发时IDEA的Tomcat把上传目录指向了target/classes/upload,一旦执行maven clean或重新部署,目录被清空,图片全部消失。另一个常见原因是直接访问localhost:8080/upload/xxx.jpg走的路径解析不到外置磁盘目录,因为Tomcat默认只把请求映射到Web应用的webapps目录内部。

解决:分两步走。先在系统根目录下统一建外部存储目录,比如/data/culture_upload;再修改Tomcat的conf/server.xml,在<Host>节点内增加:

<Context path="/upload" docBase="/data/culture_upload" reloadable="true" />

改完后重启Tomcat。注意:如果项目里用Nginx做静态资源分发,那Nginx的location /upload/也要相应配置alias指向磁盘目录,这个常常被忽略,导致图片在服务器本地能看、外网访问404。

5.4 表单提交中文乱码:过滤器顺序与编码不一致的经典组合

现象:用户在前端输入“火把节”,提交后数据库存的是“ç«ææ”,页面显示问号。

原因:三层编码不一致——请求过滤器没配、数据库表字符集不是utf8mb4、JDBC URL里没加characterEncoding=utf8。SSM项目里如果只在web.xml配置了一个CharacterEncodingFilter,但把它放在所有过滤器的最后一位,拦截不到项目自定义过滤器发出的请求,照样乱码。

解决:先检查web.xml中编码过滤器是否在第一位,然后检查数据库建表的默认字符集,最后用SQL直接插入中文做测试验证。

<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> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

forceEncoding参数值为true很关键,它表示强制请求和响应都用指定编码——只为true时才会覆盖掉请求头里已有的编码。有人只配了encoding=UTF-8没配forceEncoding,结果GET请求的中文正常、POST请求的乱码依旧,就是这个参数在起作用。

5.5 MyBatis 报BindingException: Invalid bound statement (not found)

现象:启动不报错,一调用某个Mapper方法就抛org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.culture.system.dao.CustomMapper.selectByCondition

原因:Mapper接口里有方法,但XML映射文件里没有对应的statement,或XML文件名与接口名不一致、XML文件没编译到target目录。在SSM项目里,Java接口编译输出到classes/com/culture/system/dao/,XML必须放在同目录或由mapperLocations明确指定的目录。如果用Maven结构,XML放在src/main/java下会被Maven默认的编译行为排除——Maven只把xml后缀的文件当资源时才会拷贝,不配置的话编译完target里根本没有XML文件。

解决:在pom.xml里补充资源过滤配置,让Maven把XML也当作资源处理。

<resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources>

配置里directory指定源目录,includes明确哪些后缀的文件要进target。这个配置改了以后,重新maven cleanpackage。另一个隐蔽场景:Mapper接口所在包和XML文件里namespace声明的包必须完全一致,大小写敏感,com.culture.system.dao.CustomMapper写成com.culture.system.Dao.CustomMapper也是同样的报错。

6. 从跑通到能交付:数据初始化脚本与性能验证清单

把zip解开到能正常点击操作,只是完成了第一步。真正能交付给文化馆或作为结课验收的项目,还要过一遍数据初始化和性能验证的流程。我习惯把数据库初始化做成交互式脚本,既能保证新环境一键搭建,也能给评委展示时加分。

#!/bin/bash # init_db.sh 数据库初始化脚本 MYSQL_CMD="mysql -uroot -p" echo "正在创建数据库..."; $MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS culture_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" echo "正在导入表结构..."; $MYSQL_CMD culture_db < sql/schema.sql echo "正在导入基础数据..."; $MYSQL_CMD culture_db < sql/data_seed.sql echo "正在导入演示数据(含图片路径)..."; $MYSQL_CMD culture_db < sql/data_demo.sql echo "初始化完成!";

脚本用CREATE DATABASE IF NOT EXISTS避免重复执行报错,DEFAULT CHARACTER SET utf8mb4在建库时就锁定字符集,避免后面建表时带出中文乱码。三个SQL文件的分工值得推敲:schema.sql是纯表结构,data_seed.sql是管理员账号、角色等最基础数据,data_demo.sql是带图片URL的演示民俗条目。分三个文件的好处是出问题时能精准重跑某一个,不用全量再导一遍。COLLATE utf8mb4_unicode_ci指明排序规则,unicode_ci对中文拼音排序支持更好,民俗名称按类型列表展示时不会出现顺序错乱的问题。

数据初始化之后,做一轮性能验证很有必要,这里给出三个贴近真实场景的指标供参考:

  • 登录并发:用JMeter模拟50个用户同时登录,服务器CPU占用率低于30%,且过半请求响应时间低于1秒是合格线。SSM系统如果在这项测试中出现大量连接超时,先检查Tomcat的server.xmlmaxThreads和数据库连接池上限。老项目常见的BasicDataSource默认maxActive只有8,50个并发有一大半等在连接获取上。
  • 大文本查询:民俗详情表的content字段是TEXT类型,单条记录可能10KB以上,查询列表时如果误把contentSELECT出来,回传数据量大导致接口变慢。解决办法是列表查询时只查id, name, type, region,详情页再查完整字段。
  • 图片加载:检查首页轮播图是否有缓存响应头。本地部署时如果图片每次都从Tomcat读,5张轮播图加起来可能3MB,首次打开慢是可以接受的,但第二次打开还慢就不正常——大概率是Tomcat默认没有开启静态资源的缓存头,需要在响应里配置Cache-Control。可以用Nginx前置做静态资源缓存,配置一行就够,效果立竿见影。

说到实际交付的技术习惯,我最后想分享一个自己踩过的坑:几乎所有zip项目里都会带一个readme.txt或部署说明文档,但我的习惯是不信它,永远自己从头捋一遍web.xml的加载顺序和spring系列配置文件。因为这个zip解出来给你的人,可能已经删掉了本地的上传图片目录、改过数据库密码、把数据源指向了一个你连不通的地址。把这个系统从“能跑”变成“能交付”,最核心的动作是把所有的外部依赖——数据库、上传目录、端口——全部列成清单,自己重新初始化一遍,这比任何花哨的功能都能证明你真正理解了这个系统。希望这篇笔记能帮你在拿到这个zip时少走几步弯路,一次把它跑顺。

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

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

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

立即咨询