ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSM大学生心理健康平台毕设开发全指南:框架搭建到部署避坑

SSM大学生心理健康平台毕设开发全指南:框架搭建到部署避坑 简介这是一份基于SSMSpringSpringMVCMyBatis框架的大学生心理健康平台项目源码面向Java毕业设计、课程设计及SSM初学者完整呈现了大学生、心理咨询师、管理员三类角色的在线预约与健康知识管理场景。平台涵盖大学生管理、心理咨询师管理、站内新闻、心理测试、健康知识、心理咨询、心灵驿站等模块后台采用Java与MySQL实现数据持久化前台辅以Vue、JavaScript、HTML/CSS构建交互页面。压缩包共1620个文件大小80.62MB以js、svg、java、class、vue、css、html等为主同时包含sql数据库脚本、xml/yml配置文件、maven相关文件及可执行bat脚本便于直接导入IDE运行与二次开发。内容中还提供心理测试、健康知识专题、心灵驿站发帖等业务逻辑参考可帮助深入理解SSM整合、用户权限控制、心理咨询预约流程及数据表设计等关键实现。当前已有290人学习下载适合作为毕业设计选题参考、课程设计模板或SSM项目实战练手资源。1. SSM大学生心理健康平台这个毕设题背后到底在考察什么每年毕业季不少学生选题选了“基于SSM的大学生心理健康平台”却不知道从哪下手。这个题目表面是一个项目本质是考察你能不能跑通一套经典Java Web开发流程——SSM三个框架的组合、前后端交互、以及心理健康业务的数据建模。心理平台要处理的场景很清晰学生做心理测评、看评估结果、预约咨询师管理员维护咨询师信息、发布文章、看测评统计。难点不在某个功能多复杂而在于整个链路从数据库表设计到Controller路由从MyBatis映射到权限拦截任何一个环节断掉项目就跑不起来。适合谁做计算机相关专业的学生在毕设或课计上选了这个题或者想熟悉SSM框架实际用法的初级开发都值得做——一周内就能摸清主流Java Web项目的基本骨架之后切Spring Boot也不发憷。2. 为什么选SSM框架组合技术选型逻辑与项目骨架搭建2.1 SSM框架的分工逻辑Spring容器、SpringMVC路由与MyBatis持久层首先要回答一个问题心理平台这种项目为什么要把SSM当成默认组合因为SSM是Java Web开发中一套久经考验的“三件套”SSM框架三个成员各管一段Spring负责管理对象也就是Bean解决业务对象之间的依赖关系SpringMVC负责接收HTTP请求把用户点击映射到对应的Controller方法MyBatis负责和MySQL打交道把数据库里的记录转换成Java对象。常见做法是把业务分成三层Controller层接收请求并返回页面或JSONService层写业务逻辑Mapper层操作数据库。以“学生提交心理测评问卷”为例请求先到ControllerController调ServiceService调MapperMapper执行SQL把问卷内容写入测评记录表。这套分层逻辑不只在SSM里成立在Spring Boot项目里也是一样——这也是为什么很多公司招聘时还愿意问SSM因为它能反映出你对分层架构的基本理解。和Spring Boot对比SSM的配置过程更繁琐但好处是每一步都看得见数据源配置、事务管理、拦截器注册全都在XML或注解里显式出现。这种“看得到配置”的特点在教学和毕设答辩里很有价值因为你能讲清楚每个配置的作用。下面列出了两者的核心差别。对比项SSM框架Spring Boot配置方式XML 注解混合注解为主自动配置部署形态打包成War丢进Tomcat内嵌TomcatJar直接跑起步门槛需要自己理解框架装配起步快但容易只知其然毕设评审能讲清楚底层机制偏工程化原理被隐藏这个表不是说Spring Boot不好而是提醒你在选型的时候别盲目追新要看你手上的项目环境。如果是课程设计和毕业设计用SSM反而更“耐问”评委常问的IoC、AOP、动态代理都能在项目里找到落脚点。2.2 用Maven搭建项目骨架目录结构与pom.xml关键依赖确定了SSM框架组合之后第一步不是写Controller而是把你的项目目录结构立起来。我用的是Maven标准目录src/main/java放Java源码src/main/resources放配置文件src/main/webapp放JSP页面和静态资源。这个结构是约定同时也决定了后面配置文件的扫描路径一开始就摆正能省掉很多后期调整的麻烦。常见做法是先在IDEA里创建Maven项目然后手动补上web目录结构。下面是搭建好的项目根目录结构你可以照着逐级创建ssm-psychology/ ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── com/example/psy │ │ │ ├── controller │ │ │ ├── service │ │ │ ├── mapper │ │ │ ├── entity │ │ │ └── interceptor │ │ ├── resources │ │ │ ├── jdbc.properties │ │ │ ├── spring-mybatis.xml │ │ │ ├── spring-mvc.xml │ │ │ └── com/example/psy/mapper │ │ │ ├── StudentMapper.xml │ │ │ └── QuestionnaireMapper.xml │ │ └── webapp │ │ ├── WEB-INF │ │ │ ├── web.xml │ │ │ └── jsp │ │ └── static │ │ ├── css │ │ ├── js │ │ └── images这个目录结构对应了SSM的最基本分层controller放SpringMVC的处理器service放业务逻辑mapper放MyBatis接口与XML映射entity放数据表对应的实体类。很多人栽在resources目录下的mapper子目录和java目录下的mapper包之间——它们都叫mapper但前者放的是XML后者放的是Java接口两者必须保持同名同路径才能被MyBatis自动扫描绑定。目录建好之后pom.xml是项目的心脏。下面是本次SSM项目的最小依赖集合包含了Spring全家桶、SpringMVC、MyBatis以及MySQL驱动和Druid连接池dependencies !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.23/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.23/version /dependency !-- MyBatis与Spring整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.0/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.31/version /dependency !-- 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.16/version /dependency !-- 视图解析JSTL -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency /dependencies这段pom.xml里有几个参数值得记一下spring-webmvc和spring-jdbc的版本必须保持一致否则容易出现NoSuchMethodError这类隐蔽的兼容性问题mysql-connector-java建议用8.x因为8.0之后驱动类名改成了com.mysql.cj.jdbc.Driver用老的5.x驱动连接新版MySQL会直接报Failed to load driverDruid的版本没有硬性要求但1.2.x是当前维护线建议跟着小版本走。还有一点容易翻车javax.servlet-api的scope是provided意思是编译时需要、但部署时不打包因为Tomcat自带了Servlet容器。如果你把它漏掉本地跑的时候依赖缺失会报错如果你把scope改成了compile部署到Tomcat时会和容器自带的Servlet类冲突出现java.lang.LinkageError。这个细节不致命但每次遇到都会浪费半小时排查。3. 心理健康平台的核心数据模型表结构设计与MyBatis映射3.1 5张核心表的设计思路学生、咨询师、测评问卷、预约记录、测评结果心理平台的数据模型决定了后续所有功能能否顺畅实现。我的经验是不要一开始就设计一大堆表先把业务主链路拆出来。这个系统里最核心的流程是“学生测评→生成结果→预约咨询师→记录咨询结果”所以我们至少需要学生表、咨询师表、测评问卷表、测评结果表、预约记录表这五张表。先看学生和咨询师两张基础的账号表通常用角色字段来区分权限。下面是精简版的学生表建表SQL省略了部分扩展字段以保持清晰CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 学生ID, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, password VARCHAR(64) NOT NULL COMMENT 登录密码(MD5加密), name VARCHAR(32) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 0未知 1男 2女, college VARCHAR(64) DEFAULT NULL COMMENT 学院, grade VARCHAR(16) DEFAULT NULL COMMENT 年级, phone VARCHAR(11) DEFAULT NULL COMMENT 手机号, status TINYINT DEFAULT 1 COMMENT 账号状态 1正常 0冻结, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生表;这条建表语句的细节都值得讲。password字段长度设为64是给MD5之后的32位密文和未来的SHA-256升级留余量status字段设成TINYINT而不是VARCHAR是因为程序里更方便写判断逻辑create_time用DATETIME DEFAULT CURRENT_TIMESTAMP让数据库自动维护创建时间避免每个新增接口都写一遍当前时间。测评问卷表需要支持选择题目的通用结构。常见做法是question表存题目option表存选项answer表存学生答案但这会让表数量膨胀。考虑到毕设场景我建议用两张表一张questionnaire表记录测评基本信息一张question表记录题目内容、选项JSON和计分规则。下面是question表的简化结构CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT, questionnaire_id INT NOT NULL COMMENT 所属问卷ID, content VARCHAR(255) NOT NULL COMMENT 题目内容, options JSON NOT NULL COMMENT 选项格式[{label:A,score:1}], score_type TINYINT DEFAULT 1 COMMENT 1正向计分 2反向计分, sort_order INT DEFAULT 0 COMMENT 排序号, KEY idx_questionnaire (questionnaire_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT测评题目表;这里用JSON类型存选项是一种取舍好处是写起来简单一条记录就是一整道题坏处是MyBatis取出来要手动转换成Java对象。常见的处理是写一个TypeHandler或者在Mapper里直接查出来当字符串处理然后在Service层用Fastjson解析。对于毕设项目我倾向于后者少一层配置就少一个坑。预约记录表则需要考虑一个关键参数预约状态。它必须能表达“已提交、已受理、已完成、已取消”这四种状态否则学生在测评后预约了咨询师管理员根本不知道下一步该做什么。CREATE TABLE appointment ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL COMMENT 学生ID, counselor_id INT NOT NULL COMMENT 咨询师ID, appointment_date DATE NOT NULL COMMENT 预约日期, time_slot VARCHAR(10) NOT NULL COMMENT 时段 如09:00-10:00, reason VARCHAR(255) DEFAULT NULL COMMENT 咨询缘由, status TINYINT DEFAULT 0 COMMENT 0已提交 1已受理 2已完成 3已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_student_date (student_id, appointment_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预约记录表;status用数字而不是字符串是为了索引效率和程序判断简单。要注意并发场景下的坑如果两个学生同时预约同一个咨询师的同一个时间槽程序层面没有做唯一约束就会出现“座位超卖”。最稳妥的方案是在appointment表上建唯一索引把counselor_id、appointment_date、time_slot三个字段联合起来设为UNIQUE KEY这样数据库层面就能挡住重复预约。3.2 MyBatis动态SQL场景查询测评结果列表与状态统计表结构确定后进入MyBatis映射阶段。心理平台里最常见的查询是“按学生ID查他的历史测评结果和对应问卷名称”这涉及两张表的联查。我一般在Mapper接口里定义方法在XML里写联查SQL而不是在实体类上叠加太多注解因为XML更容易排查SQL问题。先写Mapper接口package com.example.psy.mapper; import com.example.psy.entity.ResultVO; import org.apache.ibatis.annotations.Param; import java.util.List; public interface ResultMapper { // 查某学生的历史测评结果包含问卷标题、总分、测评时间 ListResultVO selectResultsByStudent(Param(studentId) Integer studentId); }对应的XML文件里用join把测评结果表和问卷表关联起来mapper namespacecom.example.psy.mapper.ResultMapper select idselectResultsByStudent resultTypecom.example.psy.entity.ResultVO SELECT r.id AS resultId, q.title AS questionnaireTitle, r.total_score AS totalScore, r.level AS level, r.create_time AS createTime FROM assessment_result r INNER JOIN questionnaire q ON r.questionnaire_id q.id WHERE r.student_id #{studentId} ORDER BY r.create_time DESC /select /mapper这里有个MyBatis中非常关键的参数绑定细节方法参数上加了Param(studentId)XML里就用#{studentId}引用。如果方法只有一个参数Param可以省略一旦方法里出现两个及以上参数强烈建议每个都加上Param否则MyBatis只能用arg0、param1这样的别扭方式取值代码可读性直线下降。resultType使用的是ResultVO对象它是一个专门给前端展示用的视图对象字段名和SQL的别名一一对应。这比直接查实体类更干净因为实体类里有password之类不该返回给前端的字段。写MyBatis的常见习惯是查询多表只用返回少量字段时不去改动实体类而是新建一个VO类保证各层职责清晰。如果你的查询条件里需要支持“管理员按日期筛选测评记录”这时SQL里就要用动态SQL标签了。MyBatis的if和where组合是动态查询的主力这里的sortOrder字段是个易错点select idselectResultsByCondition resultTypecom.example.psy.entity.ResultVO SELECT r.id AS resultId, s.name AS studentName, q.title AS questionnaireTitle, r.total_score AS totalScore, r.create_time AS createTime FROM assessment_result r LEFT JOIN student s ON r.student_id s.id LEFT JOIN questionnaire q ON r.questionnaire_id q.id where if teststudentName ! null and studentName ! AND s.name LIKE CONCAT(%, #{studentName}, %) /if if testlevel ! null AND r.level #{level} /if if teststartTime ! null AND r.create_time gt; #{startTime} /if if testendTime ! null AND r.create_time lt; DATE_ADD(#{endTime}, INTERVAL 1 DAY) /if /where ORDER BY r.create_time DESC /select这份动态SQL里有三个值得记忆的参数处理习惯。第一LIKE查询的模糊匹配需要显式拼CONCAT(%, #{studentName}, %)而不是直接写成%#{studentName}%后者会被MyBatis当成字符串字面量百分号里包着的是参数名本身查出来永远是空结果。第二时间范围查询里用DATE_ADD(#{endTime}, INTERVAL 1 DAY)这是为了把结束日期往前延一天保证用户选的当天记录也能被查出来——这个问题在MySQL里很隐蔽日期类型和datetime类型比较时如果你传入的是2024-05-20而库里存的是2024-05-20 15:30:00直接用是查不到的。第三XML里的小于号必须写成因为XML解析器会把识别成标签开始直接写会报org.xml.sax.SAXParseException。4. 从登录到测评预约用SpringMVC把业务串成完整闭环4.1 Controller层设计URL规划与参数绑定技巧SSM项目的Controller不仅负责把请求转发给Service还承担了URL规划和参数校验的职责。以心理健康平台为例常见的URL风格是/res/student/list、/res/student/detail等在页面和接口之间保持一致的命名规范。我会把Controller分成两类一类是页面跳转Controller返回JSP视图名另一类是数据接口Controller返回JSON配合页面上的AJAX请求使用。以“学生提交测评答案”为例前端把问卷答案组装成JSON字符串通过AJAX POST给后端。Controller接收参数时常见做法是定义一个ReqVO来承接package com.example.psy.controller; import com.example.psy.service.AssessmentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; Controller RequestMapping(/assessment) public class AssessmentController { Autowired private AssessmentService assessmentService; // 浏览器访问测评页面返回JSP视图 GetMapping(/page) public String page() { return assessment/index; } // 接收前端提交的测评答案 PostMapping(/submit) ResponseBody public MapString, Object submit(RequestBody AssessmentReqVO reqVO) { MapString, Object result new HashMap(); if (reqVO.getAnswers() null || reqVO.getAnswers().isEmpty()) { result.put(code, 400); result.put(msg, 测评答案不能为空); return result; } int resultId assessmentService.submitAssessment(reqVO); result.put(code, 200); result.put(msg, 提交成功); result.put(data, resultId); return result; } }这段代码里有几个SpringMVC绑定的关键点。RequestBody注解要求前端请求头里带的Content-Type是application/json如果你的前端用表单方式提交这个注解会直接报HttpMediaTypeNotSupportedException改成用RequestParam接单个字段或者定义一个表单对象接收才行。ResponseBody注解负责把返回的Map序列化成JSON字符串少了它SpringMVC会尝试去找名为“result”的JSP视图然后给你一个404。返回结果里我统一用code、msg、data三个字段这是前后端分离场景里最常见的约定。前端拿到响应后先判断code是否等于200再决定是跳转还是弹出提示。这种规范不需要额外引依赖自己用Map组装就够了代码量也不大。页面跳转方法返回的是字符串类型SpringMVC会交给视图解析器处理。在spring-mvc.xml中配置InternalResourceViewResolver时前缀设为/WEB-INF/jsp/后缀设为.jsp这样return assessment/index实际指向的是/WEB-INF/jsp/assessment/index.jsp。把JSP放在WEB-INF目录下面是一个安全习惯因为用户无法通过URL直接访问WEB-INF内的文件所有页面都必须走Controller转发这在心理平台这种含敏感数据的系统里更护得住隐私。4.2 登录会话与权限拦截拦截器配置里的两个必调参数心理平台有三个角色学生、咨询师、管理员。如果不做权限控制任何登录用户都能看到别人的测评结果这在心理健康场景里属于严重的数据泄漏。SSM框架里做权限控制最直接的手段就是SpringMVC拦截器它能在请求进入Controller之前先判断当前用户是否登录、角色是否匹配。下面是一个登录拦截器的核心实现package com.example.psy.interceptor; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从session中获取登录用户 Object user request.getSession().getAttribute(loginUser); // 如果未登录重定向到登录页 if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } // 已登录放行请求 return true; } }preHandle方法返回true则放行返回false则拦截同时可以在这个方法里做重定向。把loginUser存进session的动作发生在登录Controller中——User user userService.login(username, password); request.getSession().setAttribute(loginUser, user); 这套逻辑是SSM的经典会话管理模式比用Cookie存用户ID要安全得多因为sessionId是HttpOnly的脚本拿不到。光有拦截器类还不行必须注册到SpringMVC的配置里否则拦截器就是一团死代码。下面是spring-mvc.xml里的拦截器注册片段mvc:interceptors mvc:interceptor !-- 拦截所有请求 -- mvc:mapping path/**/ !-- 放行登录接口和静态资源 -- mvc:exclude-mapping path/login/ mvc:exclude-mapping path/css/**/ mvc:exclude-mapping path/js/**/ mvc:exclude-mapping path/images/**/ /mvc:interceptor /mvc:interceptors这个配置里有几个容易踩坑的路径参数。path/表示拦截所有路径而exclude-mapping的路径模式必须精确匹配实际访问路径假如页面通过/static/css/style.css访问样式但你写成/css/静态资源照样会被拦截页面就会彻底失去样式。另一个细节是exclude-mapping如果不放开/login这个路径用户访问登录页时会被循环重定向——因为在登录页的请求也被拦截了。如果你的项目同时存在多个角色还需要在拦截器里判断角色。做法是session里再存一个role字段request.getSession().setAttribute(role, user.getRole()); 然后在拦截器中检查当前路径前缀与角色是否匹配。比如所有/admin/**开头的请求要求role为1所有/student/**开头的请求要求role为2。这种传统做法简单直接逻辑都写在拦截器里非常适合毕设答辩时展示你对权限控制的理解。更细的粒度控制比如每个按钮的权限在三方框架Shiro或Spring Security里做得更好但SSM项目里引入Shiro会让配置量翻倍收益在你的场景里并不大。5. SSM项目避坑指南从部署到运行的5个高频翻车现场5.1 现象一页面中文乱码所有姓名和测评内容变成问号这个坑在SSM项目的开发期几乎必现。表现是浏览器里看到的全是“????”或“汉嗔但数据库里存的是正常中文。原因有两个层面一是MySQL连接串里没指定字符集导致Java到数据库的连接用的是latin1二是在SpringMVC的配置里缺了CharacterEncodingFilter导致请求和响应没有走UTF-8。解决办法分两步。第一步在jdbc.properties里的连接串末尾加上characterEncodingutf8和useSSLfalsejdbc.urljdbc:mysql://localhost:3306/psy?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai第二步在web.xml里加Spring自带的字符集过滤器并且让它对全部路径生效filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这两个配置缺一不可。连接串的serverTimezone参数在MySQL 8.x下必须写否则连数据库时会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这个报错很多人第一眼都以为是自己数据库坏了其实是驱动不认识系统时区。5.2 现象二Tomcat启动报ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet启动完Tomcat控制台直接抛出ClassNotFoundException提示找不到DispatcherServlet。原因基本可以锁定为SpringMVC相关的jar包没有部署到Tomcat的运行时环境里也就是说IDEA的Artifacts配置中项目的依赖没有输出到WEB-INF/lib目录。检查步骤是打开IDEA的Project Structure - Artifacts看你的war exploded归档下有没有lib目录。如果只有output目录而没有lib你需要手动点开“Available Elements”面板把项目依赖的jar包全部拖进WEB-INF/lib。这里的底层原因是Maven只负责把依赖下载到本地仓库但IDEA打包时如果不显式把依赖放进web应用的libTomcat就找不到这些类。如果你不想每次都在IDEA里手动操作可以在pom.xml里加maven-war-plugin的配置让Maven自动把依赖打包进war文件。但这个方法只对最终打包阶段有效本地Debug调试时还是需要IDEA的Artifacts配合两处都要配置好。5.3 现象三MyBatis的Mapper接口与XML绑定失败运行后第一次调用Dao层方法报Invalid bound statement (not found)。这表示MyBatis没有找到和Mapper接口对应的XML文件。典型原因是XML文件放错了位置或者没有做资源扫描。在Maven项目里Java接口在src/main/java/com/example/psy/mapper下但XML文件也要放在src/main/resources/com/example/psy/mapper下保持包结构完全一致否则MyBatis扫描不到。如果你把XML直接丢在src/main/java里Maven默认不会把非.java文件编译进target目录你打开target/classes目录看一眼就明白了——那里根本没有XML文件。解决办法是在spring-mybatis.xml里显式指定mapper位置property namemapperLocations valueclasspath:com/example/psy/mapper/*.xml/同时别忘了在MyBatis的全局配置里把mapper接口的包名告诉它这样接口和XML才能配对bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.psy.mapper/ /bean检查顺序是先看target/classes里有没有XML再看spring-mybatis.xml的mapperLocations路径是否匹配最后看XML的namespace是否写成了Mapper接口的全限定名。这个三角关系任何一环断了都会出现同样的Invalid bound statement报错所以排错要按这个顺序来。5.4 现象四登录后跳转正常但刷新页面就退出登录功能上能登录但每次刷新页面都跳回登录页session永远存不住用户。检查发现session确实set进去了也没有超时排除法查到最后是Cookie的context path问题。Tomcat里应用默认的Cookie Path是应用的context path。如果你的应用部署在http://localhost:8080/ssm-psy/下session的Cookie Path就应该是/ssm-psy/。但如果有人在web.xml里手动配置了session超时时间或者你在IDE的Deployment里把Application context路径改了而浏览器里旧的JSESSIONID还留在旧路径下就会出现“登录成功但刷新即失效”的诡异状态。解决方法是先把浏览器缓存Cookie清掉再用无痕模式测试一遍。如果问题消失说明是开发环境反复改动路径导致的旧Cookie残留。生产部署时建议直接用ROOT路径部署也就是把war包改名为ROOT.war这样Cookie Path就是/省去一堆路径不一致的麻烦。5.5 现象五数据库连接池把Tomcat拖到崩溃系统多跑一会儿Tomcat的日志里开始出现Connection is not available, request timed out随后整个应用无响应。这是一个连接池耗尽的问题典型的连环踩坑代码里用了JDBC的Connection没有关闭或者事务配置失效导致连接一直不被释放。排查方式是先用MySQL客户端执行show processlist看看当前连接数。如果发现sleep状态的连接大量堆积问题通常出在Service层的方法没有交给Spring事务管理。在SSM里事务是放在Service实现类上的Service public class AssessmentServiceImpl implements AssessmentService { Autowired private ResultMapper resultMapper; Override Transactional public int submitAssessment(AssessmentReqVO reqVO) { // 插入测评结果 resultMapper.insertResult(reqVO); // 更新学生状态 resultMapper.updateStudentStatus(reqVO.getStudentId()); return reqVO.getResultId(); } }Transactional注解默认只回滚RuntimeException如果你在业务里抛的是异常是new Exception(xxx)那么事务不会回滚连接也不会正常释放最终把连接池耗尽。所以组件内统一使用运行时异常或者在注解上显式加rollbackFor Exception.class。补充一个Druid连接池的参数建议initialSize设置5maxActive设置20minIdle设置5maxWait设置60000。对毕设项目来说20个并发连接足够支撑测评高峰期设置过大会白白消耗MySQL的资源。6. 部署验证的最后一公里本地打War包与服务器部署的实用技巧6.1 用Maven打包并检查包体完整性项目开发完毕下一步是打成War包部署到服务器。在项目根目录执行mvn clean package打包完成后打开target目录看看生成的war文件名是否为项目名比如ssm-psy.war。我一般会用unzip -l命令检查包里的lib目录是否存在以及spring-mvc.xml等配置是否打进去。如果发现lib目录是空的多半是pom.xml的打包配置没生效回到IDEA的Artifacts设置补上依赖。然后把这个war包丢到Tomcat的webapps目录下启动Tomcat后它会自动解压部署。部署的路径默认为war包文件名例如http://localhost:8080/ssm-psy/。如果你想直接通过根路径访问可以把war改名为ROOT.war再放进去这样URL里的项目名就消失了登录跳转时也能少一层路径拼接的烦恼。6.2 线上部署后的验证清单部署到服务器后打开浏览器按这个清单过一遍学生端能否正常登录并提交测评右侧的测评历史能否正确显示结果预约咨询师时同一天同一时段重复提交是否被数据库挡下管理员端能否看到测评统计和预约状态。我习惯在本地和线上各跑一遍相同的测评流程然后对比数据库里的记录数确认线上没有任何格式或路径的偏差。这个习惯已经帮我拦住过好几次“本地能用线上却报错”的翻车。还有一个验证点是日志打开Tomcat的logs/catalina.out看有没有ClassNotFoundException或SQLSyntaxErrorException尤其是首次部署时最容易出现Linux下数据源连接失败的问题——检查jdbc.properties里的localhost是否要改成服务器的内网IP。如果线上时间不对记得在启动参数里加-Duser.timezoneAsia/Shanghai否则预约记录里的时间戳会差8个小时。建议你自己加一个小的观察点把每个请求的耗时打到日志里当学生反馈“测评提交后转圈很久”时能快速定位是数据库查询慢还是网络慢而不是毫无头绪地猜。这个习惯对我帮助很大。希望帮到你。本文还有配套的精品资源点击获取
返回列表