
简介这份资源是面向高校计算机相关专业学生与Java初学者的一套毕业设计/课程设计完整项目选题为基于SSM框架的广告管理系统采用Java语言、SSMVue技术栈与B/S架构数据库使用MySQL适合需要完成毕设或课程作业、想参考前后端分离实现思路的读者。压缩包共540个文件约83.42MB其中135个java文件承载后端业务逻辑50个vue文件构成前端页面另有23个xml、19个js及svg、jpg、png等静态资源与2个sql脚本覆盖配置、界面与建库各环节。系统划分管理员、广告客户、广告商、广告制作商四类角色功能涉及广告投放渠道、广告需求、广告提案、设计预览、调整信息、签署与结算等模块业务链条较为完整。目前已有150人学习下载可帮助读者快速理解多角色权限设计与广告业务流转并对照源码梳理SSM整合、Vue页面组织与数据库表结构为二次开发或答辩准备提供参考。1. 从一份 .rar 说起SSM 广告管理系统到底在解决什么问题如果你正在搜「Java毕业设计」或者「SSM框架 广告管理系统」大概率你手里已经有一个.rar压缩包或者正准备从零做一个能过答辩、能写进简历、能跑起来的广告投放后台。广告管理系统听起来像大厂才用的东西但落到毕业设计这个场景它的本质其实很朴素把「广告位、广告内容、投放计划、点击统计」这几张表管起来让运营能增删改查、能按条件筛选、能看到一条广告从创建到上线的完整状态流转。SSM 就是 Spring SpringMVC MyBatis 这套经典组合虽然现在新项目更多用 SpringBoot但大量高校课程设计、课程设计案例源码、java课程设计案例源码仍然以 SSM 为标准模板因为它的分层清晰、配置文件多、能体现你对 IOC、AOP、事务、MVC 这些概念的理解答辩老师也熟悉这套东西。这个系统适合谁适合 java基础已经过了一遍、能写简单 CRUD、但还没独立搭过一个完整 Web 项目的同学也适合工作一两年、想回头补一补传统 SSM 分层和 MyBatis 映射细节的人。我见过太多人拿到压缩包后直接双击运行结果 Tomcat 起不来、数据库连不上、页面 404然后就卡住了。这篇不打算给你灌 java面试八股文也不聊 java学习路线就顺着这个标题把「怎么让这套 SSM 广告管理系统在你机器上真正跑起来、跑对、跑稳」讲清楚顺带把几个最容易翻车的地方提前给你标出来。2. 环境与依赖让 SSM 广告管理系统在本地跑起来的第一步2.1 先确认你的 JDK、Tomcat、Maven 版本组合SSM 项目对版本比 SpringBoot 敏感得多尤其是 JDK 和 Tomcat。很多压缩包里用的是 JDK 8 Tomcat 8.5 Maven 3.6 这套组合如果你本地装的是 JDK 17编译时大概率会看到java: 警告: 源发行版 17 需要目标发行版 17这类提示或者更直接的Unsupported class file major version。这不是代码问题是版本没对齐。我一般会先做三件事确认java -version输出是 1.8确认mvn -v里 JDK 也是 1.8确认 Tomcat 是 8.5 或 9.0不要用 Tomcat 10因为 Tomcat 10 把javax.servlet换成了jakarta.servletSSM 老项目里的web.xml和依赖会直接不认。# 检查 JDK 版本必须是 1.8 java -version # 检查 Maven 用的 JDK如果和上面不一致需要改 JAVA_HOME mvn -v # 检查 Tomcat 版本建议 8.5.x 或 9.0.x # 进入 Tomcat 的 bin 目录后执行 ./version.sh # Linux/Mac version.bat # Windows逻辑说明java -version看的是系统默认 JDKmvn -v看的是 Maven 实际调用的 JDK两者不一致时以 Maven 为准因为编译是 Maven 触发的。参数上JAVA_HOME指向 JDK 8 的安装目录PATH里把%JAVA_HOME%\bin放在最前面避免系统里多个 JDK 互相抢。提示如果压缩包里带了.idea或.settings目录先删掉再导入否则 IDE 会沿用别人的 JDK 和 Tomcat 路径你改半天都改不对。2.2 数据库建库建表别直接拿 .sql 一把梭广告管理系统的表通常不多常见的是ad_user、ad_position、ad_content、ad_plan、ad_stat这几张。压缩包里一般会有一个db.sql或者ad.sql但直接导入经常报错原因是字符集和引擎不匹配。我一般会先手动建库指定utf8mb4再导入表结构-- 建库字符集用 utf8mb4排序规则用通用型 CREATE DATABASE ad_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 切到该库 USE ad_system; -- 再执行压缩包里的建表语句 -- 注意如果原 sql 里有 DROP TABLE IF EXISTS先确认没有同名库逻辑说明utf8mb4比utf8多支持 emoji 和部分生僻字广告内容里如果出现特殊符号不会乱码。排序规则用utf8mb4_general_ci而不是unicode_ci是因为老项目里的外键和索引在general_ci下兼容性更好。参数上如果原 sql 里写的是ENGINEInnoDB就保留不要改成 MyISAM因为后面事务和行锁都依赖 InnoDB。导入后执行SHOW TABLES;确认表都在再执行SELECT COUNT(*) FROM ad_position;看有没有初始数据。没有初始数据的话登录页面可能进不去因为用户表是空的。2.3 改配置文件jdbc.properties 和 applicationContext.xml 的对应关系SSM 项目里数据库连接通常写在jdbc.properties然后在applicationContext.xml里用context:property-placeholder加载。常见翻车点是路径写错或者 key 名对不上。# jdbc.properties 示例 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ad_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码逻辑说明com.mysql.cj.jdbc.Driver是 MySQL 8 的驱动类如果你用的是 MySQL 5.7可以改成com.mysql.jdbc.Driver但建议统一用 8 的驱动加serverTimezone否则会报时区错误。characterEncodingutf8和建库时的utf8mb4不冲突驱动层会自动处理。参数上serverTimezoneAsia/Shanghai必须加不然插入时间会差 8 小时。然后在applicationContext.xml里确认!-- 加载 jdbc.properties -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 数据源用 Druid 或 DBCP 都行看压缩包里带的哪个 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean逻辑说明${jdbc.driver}这种占位符必须和jdbc.properties里的 key 完全一致大小写敏感。如果启动时报Could not resolve placeholder就是 key 名写错了或者文件没被加载到。参数上Druid 的initialSize、maxActive可以用默认值毕业设计并发不高不用调。3. 分层与映射广告管理系统里 MyBatis 和 Spring 怎么配合3.1 从 Controller 到 Mapper一次广告查询的完整链路SSM 的分层是 Controller → Service → Mapper → 数据库。广告管理系统里最典型的一条链路是「按广告位查广告列表」。Controller 接收前端传来的positionIdService 做业务判断Mapper 执行 SQL。// AdController.java Controller RequestMapping(/ad) public class AdController { Autowired private AdService adService; RequestMapping(/list) public String list(RequestParam(positionId) Integer positionId, Model model) { // 调用 Service 查广告列表 ListAdContent ads adService.getAdsByPosition(positionId); model.addAttribute(ads, ads); return ad_list; } }逻辑说明Controller和RequestMapping是 SpringMVC 的注解Autowired注入 Service。RequestParam绑定前端参数如果前端没传会报 400。参数上positionId用Integer而不是int是为了能接收 null 并做后续判断。// AdServiceImpl.java Service public class AdServiceImpl implements AdService { Autowired private AdContentMapper adContentMapper; Override public ListAdContent getAdsByPosition(Integer positionId) { // 简单业务判断positionId 为空时返回空列表 if (positionId null) { return new ArrayList(); } return adContentMapper.selectByPositionId(positionId); } }逻辑说明Service标记业务层Autowired注入 Mapper。这里加了一个空判断避免 Mapper 收到 null 后拼出WHERE position_id null这种查不到任何数据的 SQL。参数上返回空列表而不是 null是为了让前端遍历时不会空指针。!-- AdContentMapper.xml -- select idselectByPositionId resultTypecom.ad.entity.AdContent SELECT id, title, img_url, link_url, position_id, status, create_time FROM ad_content WHERE position_id #{positionId} AND status 1 ORDER BY create_time DESC /select逻辑说明#{positionId}是预编译占位符能防 SQL 注入。status 1表示只查上线的广告。resultType里的实体类路径必须和项目里一致否则报ClassNotFoundException。参数上ORDER BY create_time DESC让最新的广告排在前面符合运营习惯。3.2 事务配置广告投放和统计更新必须一起成功广告管理系统里有一个典型场景用户点击广告后要同时写点击记录和更新广告的点击总数。这两步必须在一个事务里否则会出现「记录写了但总数没加」或者反过来。!-- applicationContext.xml 里开启事务注解 -- tx:annotation-driven transaction-managertransactionManager/ bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean逻辑说明tx:annotation-driven让Transactional注解生效transactionManager绑定数据源。参数上dataSource必须和前面配的数据源是同一个 bean否则事务不生效。// AdStatServiceImpl.java Service public class AdStatServiceImpl implements AdStatService { Autowired private AdStatMapper adStatMapper; Autowired private AdContentMapper adContentMapper; Override Transactional(rollbackFor Exception.class) public void recordClick(Integer adId) { // 第一步插入点击记录 adStatMapper.insertClick(adId); // 第二步更新广告点击总数 adContentMapper.incrementClickCount(adId); } }逻辑说明Transactional(rollbackFor Exception.class)表示任何异常都回滚默认只回滚运行时异常受检异常不回滚所以显式写上更安全。参数上rollbackFor用Exception.class而不是RuntimeException.class是为了覆盖所有异常场景。注意如果你在 Service 里自己 try-catch 了异常然后没往外抛事务不会回滚。这是最常见的「事务失效」原因血泪经验。4. 避坑与排查SSM 广告管理系统最常见的 5 个翻车现场4.1 启动报错NoClassDefFoundError或ClassNotFoundException现象Tomcat 启动时控制台刷一堆NoClassDefFoundError或者访问页面时报ClassNotFoundException提示某个类找不到。原因Maven 依赖没下载全或者pom.xml里 scope 写成了provided但 Tomcat 里没有对应的 jar。SSM 项目里常见的是spring-webmvc、mybatis-spring、mysql-connector-java这几个包缺失。解决在项目根目录执行mvn clean package -DskipTests看 BUILD 是否成功。如果失败看具体是哪个依赖下载不下来换阿里云镜像。如果成功但 Tomcat 还报错检查WEB-INF/lib下有没有对应的 jar没有的话在 IDEA 的 Project Structure 里把 Maven 依赖加到 Artifact 里。4.2 页面 404Controller 没被扫描到现象Tomcat 起来了访问http://localhost:8080/ad/list返回 404但控制台没有明显报错。原因SpringMVC 的组件扫描路径没覆盖到 Controller 所在的包或者web.xml里DispatcherServlet的contextConfigLocation配错了。解决检查spring-mvc.xml里context:component-scan base-packagecom.ad.controller/的包名是否和实际目录一致。再检查web.xml里init-param的contextConfigLocation是否指向了classpath:spring-mvc.xml。如果用了classpath*:和classpath:混用也可能导致加载不到。4.3 数据库连接池报Too many connections现象运行一段时间后页面卡死日志里出现Too many connections或者Communications link failure。原因Druid 或 DBCP 的maxActive设得太大或者代码里有地方拿了连接没关。SSM 里 MyBatis 和 Spring 整合后连接由 Spring 管理一般不会泄漏但如果你在 Service 里手动getConnection()没关就会泄漏。解决把maxActive调到 20 以内毕业设计够用。再检查代码里有没有手动操作Connection的地方有的话改成用 Mapper。另外MySQL 的wait_timeout默认 8 小时连接池的minEvictableIdleTimeMillis要小于它避免拿到失效连接。4.4 中文乱码广告标题存进去变成问号现象在页面上输入中文广告标题保存后数据库里显示???或者页面回显乱码。原因三个环节都可能出问题——数据库字符集、连接 URL 字符集、Tomcat 的URIEncoding。常见的是数据库建库时用了latin1或者连接 URL 没加characterEncodingutf8。解决先确认数据库和表的字符集是utf8mb4再确认jdbc.url里有characterEncodingutf8最后在 Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8。三个都对了中文就不会乱。4.5 事务不回滚点击记录写了但总数没加现象调用记录点击的接口后ad_stat表有新记录但ad_content的click_count没变。原因Transactional没生效或者异常被 catch 了没抛出去或者事务管理器绑定的数据源和 Mapper 用的不是同一个。解决先确认applicationContext.xml里tx:annotation-driven和transactionManager都配了。再确认 Service 类是被 Spring 管理的有Service而不是自己new出来的。最后确认没有在 Service 里 try-catch 后吞掉异常。如果用的是多数据源事务管理器要指定对应的数据源。5. 进阶技巧用 AOP 给广告管理系统加一层操作日志5.1 为什么用 AOP 而不是在每个 Service 里写日志广告管理系统里运营会频繁地新增、修改、删除广告。如果每个方法里都写一遍log.info(用户{}修改了广告{}, userId, adId)代码会非常臃肿而且容易漏。AOP 的思路是定义一个切点匹配所有 Service 的增删改方法在方法执行后自动记录操作人和操作内容。// LogAspect.java Aspect Component public class LogAspect { Autowired private AdLogMapper adLogMapper; // 切点匹配 com.ad.service 包下所有以 add、update、delete 开头的方法 Pointcut(execution(* com.ad.service.*.add*(..)) || execution(* com.ad.service.*.update*(..)) || execution(* com.ad.service.*.delete*(..))) public void logPointcut() {} // 环绕通知在方法执行后记录日志 Around(logPointcut()) public Object around(ProceedingJoinPoint joinPoint) throws Throwable { // 先执行原方法 Object result joinPoint.proceed(); // 方法名 String methodName joinPoint.getSignature().getName(); // 参数 Object[] args joinPoint.getArgs(); // 写入日志表 AdLog log new AdLog(); log.setMethod(methodName); log.setParams(Arrays.toString(args)); log.setCreateTime(new Date()); adLogMapper.insert(log); return result; } }逻辑说明Aspect和Component让 Spring 识别这个切面。Pointcut用 execution 表达式匹配方法名add*、update*、delete*覆盖了常见的增删改操作。Around先执行原方法再记录日志这样如果原方法抛异常日志不会写符合「操作成功才记录」的逻辑。参数上joinPoint.getArgs()拿到的是方法参数数组Arrays.toString转成字符串存库方便排查。5.2 日志表设计和查询优化日志表不用太复杂但索引要加对。我一般会建这样的表CREATE TABLE ad_log ( id INT PRIMARY KEY AUTO_INCREMENT, method VARCHAR(64) NOT NULL, params TEXT, create_time DATETIME NOT NULL, INDEX idx_create_time (create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明method存方法名params存参数create_time存时间。索引加在create_time上因为查日志通常是按时间范围查。参数上params用TEXT而不是VARCHAR(255)因为参数可能很长比如批量删除的 id 列表。查询的时候用分页不要一次查全部SELECT id, method, params, create_time FROM ad_log WHERE create_time BETWEEN #{start} AND #{end} ORDER BY create_time DESC LIMIT #{offset}, #{limit};逻辑说明BETWEEN走索引LIMIT做分页。参数上offset和limit由前端传页码和每页条数Service 层算好再传给 Mapper。5.3 一个我踩过的坑AOP 切不到 Controller一开始我把切点写成execution(* com.ad.controller.*.*(..))想记录所有 Controller 的请求结果发现日志表里一条记录都没有。原因是 SpringMVC 的 Controller 和 Spring 的 AOP 代理不在同一个容器里spring-mvc.xml和applicationContext.xml是父子容器关系AOP 配置在父容器里切不到子容器的 Controller。后来我把切点改到 Service 层问题就解决了。如果你确实想记录 Controller 层的请求可以在spring-mvc.xml里也开启aop:aspectj-autoproxy/但更推荐的做法是加一个拦截器Interceptor在preHandle里记录请求信息。这个坑我花了两个小时才定位到希望帮到你。做 SSM 项目父子容器的边界一定要心里有数不然很多「配置明明写了却不生效」的问题都会找上门。本文还有配套的精品资源点击获取