ARTICLE DETAIL

资讯详情

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

SSM+JSP健身房管理系统全解析:从数据库设计到部署避坑

SSM+JSP健身房管理系统全解析:从数据库设计到部署避坑 简介这是一套基于SSMSpringSpringMVCMyBatis与JSP技术栈开发的健身房管理系统完整源码主要面向Java初学者、课程设计及毕业设计人群可帮助快速掌握企业级Web项目的前后端整合思路。压缩包共1277个文件包含120个Java后端类、109个JSP页面、364个JavaScript脚本以及CSS、图片、字体等前端静态资源同时提供MySQL数据库脚本与MAVEN工程配置整体大小仅15.74MB目录结构清晰便于按模块检索阅读。已有113人学习下载。源码不仅可直接运行还展示了SSM三层架构的典型实现控制层处理请求、业务层封装逻辑、持久层交互数据库前端JSP配合JavaScript完成页面渲染与异步交互适合框架实战练习、二次开发或作为毕业设计参考有助于深入理解SSM整合流程、JSP语法及应用部署细节。1. 这套 SSMJSP 健身房管理系统课程设计与企业实习都要用的那套东西一位刚毕业的 Java 开发面试官问他做过什么项目他说“健身房管理系统”。十有八九就是这套 SSMJSP 的框架组合——Spring 管对象、SpringMVC 收请求、MyBatis 操作数据库、JSP 负责把数据渲染成页面。它不新但结构完整到能直接支撑一个中小型健身房的会员、课程、教练、器材预约等日常业务。这套系统的意义在于它几乎是 Java Web 后端入门最典型的一条完整链路。你不需要会微服务、不需要懂分布式只要把 SSM 三层架构和 JSP 页面的数据流转跑通就已经覆盖了后端开发日常 80% 的工作方式。本文从零把这套系统的数据库设计、分层代码、常见部署问题全部走一遍按步骤能复现按经验能避坑。如果你正面临 Java 课程设计、毕业设计或者是想从“只会写 Java SE 题”过渡到“能跑一个 Web 项目”这套架构是性价比最高的选择。它不像 Spring Boot 那样把配置都帮你藏起来反而让你把每个注解、每段 XML、每条 SQL 都看得清清楚楚——这才是面试时说得出口的东西。2. 技术选型和目录结构为什么 SSMJSP 到今天还有人在用2.1 SSM 三个框架各自管什么以及它们的集成方式很多新手对 SSM 的认知是一个模糊的整体但写代码和面试时你必须能一句话说清每个框架的职责边界。Spring 是容器管理所有对象的创建和依赖关系。你的 Service、Mapper、Controller 在传统写法里都得自己 newSpring 里通过bean配置或注解来托管调用方只需要注入不需要关心对象怎么创建。这套系统里我们会对 Service 层加上事务控制保证一次操作要么全成功要么全回滚。SpringMVC 是前端控制器负责接收 HTTP 请求、解析参数、调用 Service、再把返回值交给视图渲染。它的核心是 DispatcherServlet所有请求先经过它再分发到不同的 Controller 方法。这套系统里登录请求、会员列表请求、预约操作都是由它来路由。MyBatis 是持久层框架解决 Java 对象和数据库记录之间的映射。对比 JDBC 手写PreparedStatementMyBatis 让你把 SQL 写在 XML 或注解里参数自动绑定结果集自动映射成对象。比如查出一条会员记录自动变成Member对象不需要一行行rs.getInt()。三个框架的集成方式常见的是web.xml 里配置 Spring 的 ContextLoaderListener 加载 Spring 根容器SpringMVC 的 DispatcherServlet 加载自己的子容器MyBatis 的 SqlSessionFactory 作为 Bean 放进 Spring 容器Mapper 接口通过扫描注册成 Bean。这套配置看起来头大但写一次以后所有项目都是同一个套路。2.2 标准目录结构Maven 工程怎么组织才不乱我见过太多课程设计项目把所有类都塞在默认包下面跑起来没问题但检查代码的老师一眼就能看出问题。SSM 项目标准结构按 Maven 约定来分为主代码目录和资源目录。项目骨架大致是gym-manager/ ├── pom.xml ├── src/main/java │ └── com/gym │ ├── controller │ ├── service │ │ ├── impl │ ├── mapper │ ├── entity │ └── common ├── src/main/resources │ ├── jdbc.properties │ ├── spring-mybatis.xml │ └── mapper └── src/main/webapp ├── WEB-INF │ ├── web.xml │ └── jsp ├── static └── index.jspcontroller 放控制器service 放接口、impl 放实现类mapper 放 MyBatis 接口entity 放实体类common 放分页工具类、统一返回结果类等。resources 下放 Spring 配置和 MyBatis 的 mapper XML。webapp 下是 JSP 页面和静态资源。这个结构不是硬性规定但按这个约定去组织团队成员互相看代码成本最低。特别是 mapper 接口和 mapper XML 的包名必须一致不然 MyBatis 扫描会报绑定异常这是后面要重点讲的坑。2.3 数据库表设计六张表怎么撑起完整业务健身房管理系统核心围绕“人”和“活动”来建模会员、教练是核心实体课程是载体预约是连接关系公告是信息补充管理员是后台入口。按这个思路拆出六张核心表。表名关键字段作用adminid, username, password, real_name后台登录账号memberid, name, phone, gender, age, card_type, create_time会员基本信息与卡类型trainerid, name, specialty, phone, hire_date教练档案courseid, name, trainer_id, course_date, start_time, end_time, capacity课程排期reservationid, member_id, course_id, reserve_time, status会员预约课程记录announcementid, title, content, publish_time公告信息会员表里的 card_type 可以存字符串如“月卡”“年卡”也可以在后续迭代里拆成卡类型字典表课程表的 trainer_id 关联教练表。预约表是典型的关联表会员和课程是多对多通过预约表解耦。值得注意的细节是时间字段统一用datetime而不是varchar否则后面做“统计本月新增会员数”这类 SQL 时会非常痛苦。性别字段用tinyint存储页面显示时再映射成“男/女”比直接用中文更规范。数据库字符集用utf8mb4不然以后会员备注里存个 emoji 就插入报错。3. 从零搭建项目环境版本、配置文件和数据库初始化3.1 环境选型和版本搭配这一步定错会连锁翻车SSM 项目对版本非常敏感尤其是 Spring 和 MyBatis 之间的兼容性。常见的稳定搭配是 JDK 1.8、Maven 3.6 以上、Tomcat 8.5 或 9.0、MySQL 5.7。这套组合经过了大量项目的验证网上遇到问题时能搜到的解决方案也最多。pom.xml 里引入核心依赖的参考写法如下properties spring.version4.3.18.RELEASE/spring.version mybatis.version3.4.6/mybatis.version maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependencies注意几个参数的含义Spring 的版本统一用 4.3.x不要混用 4.x 和 5.x 的 jar 包mybatis-spring是 Spring 整合 MyBatis 的桥接包版本必须和 Spring 4.x 兼容mysql 驱动用 5.1.47如果本地是 MySQL 8.x 的话驱动要换成com.mysql.cj.jdbc.Driver并加时区参数。这些版本号不是我拍脑袋写的每一组都是踩过坑之后沉淀下来的可运行组合。3.2 web.xml 和 Spring 配置文件请求是怎么分发到 Controller 的配置从 web.xml 开始它是整个 Web 应用的入口。它负责两件事加载 Spring 根容器以及配置 SpringMVC 的前端控制器。context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mybatis.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mapping这里有一个容易理解错的地方ContextLoaderListener加载的是根容器主要管 Service、Mapper 这些业务对象DispatcherServlet加载的是子容器主要管 Controller。子容器能访问父容器的 Bean反过来不行。所以如果你的 Controller 想注入 ServiceSpringMVC 容器里找不到时会去父容器找这是可行的。实际开发中为了避免扫描重复Spring 的配置只扫描 service、mapper、entitySpringMVC 的配置只扫描 controller两个容器各管各的边界清晰。3.3 SpringMVC 配置和 MyBatis 整合连接池、事务和视图解析spring-mybatis.xml 是整套配置的核心负责数据源、MyBatis 的 SqlSessionFactory、Mapper 扫描和事务管理。!-- 读取数据库配置 -- context:property-placeholder locationclasspath:jdbc.properties/ !-- 数据源C3P0 -- bean iddataSource classcom.mchange.v2.c3p0.ComboPooledDataSource property namedriverClass value${jdbc.driver}/ property namejdbcUrl value${jdbc.url}/ property nameuser value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialPoolSize value5/ property namemaxPoolSize value20/ property nameminPoolSize value1/ property nameacquireIncrement value2/ /bean !-- 整合 MyBatis -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.gym.entity/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.gym.mapper/ /bean !-- 事务管理 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/这段配置里mapperLocations指向 XML 文件的位置MyBatis 启动时会把 XML 里的 SQL 和接口方法绑定起来路径写错就会报绑定异常。typeAliasesPackage设置了实体类的别名这样 mapper XML 的resultType可以直接写Member而不必写全限定名。MapperScannerConfigurer会自动扫描指定包下的接口生成代理对象注册进 Spring 容器。!-- spring-mvc.xml -- context:component-scan base-packagecom.gym.controller/ mvc:annotation-driven/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static// mvc:default-servlet-handler/视图解析器的意思是Controller 返回member/list实际渲染的是/WEB-INF/jsp/member/list.jsp。把 JSP 放在 WEB-INF 下是为了防止用户直接通过 URL 访问页面文件必须经过 Controller 跳转。最后两行是静态资源放行配置CSS、JS、图片都要靠这两行才能正常加载。jdbc.properties 文件里维护数据库连接信息注意用户名和密码不要写死成 root 明文至少用环境变量替换比如${DB_PASSWORD}。4. 核心功能代码实现登录、会员管理、课程预约的完整链路4.1 实体类和 Mapper 层SQL 是怎么和对象互相转换的先看 Member 实体类注意字段和数据库列名的映射规则。public class Member { private Integer id; private String name; private String phone; private Integer gender; private Integer age; private String cardType; private Date createTime; // getter/setter 省略 }MyBatis 默认开启驼峰命名转换后数据库的card_type列能自动映射到 Java 的cardType属性。这个开关需要在 mybatis 配置里开启setting namemapUnderscoreToCamelCase valuetrue/。如果不开启要么给每列写resultMap要么把 SQL 里都用别名写成和属性一致。Mapper 接口的定义方式public interface MemberMapper { Member selectById(Integer id); ListMember selectAll(); ListMember selectPage(Param(offset) int offset, Param(pageSize) int pageSize); int insert(Member member); int update(Member member); int deleteById(Integer id); int countAll(); }对应的 mapper XMLmapper namespacecom.gym.mapper.MemberMapper select idselectPage resultTypeMember SELECT id, name, phone, gender, age, card_type, create_time FROM member ORDER BY id DESC LIMIT #{offset}, #{pageSize} /select insert idinsert parameterTypeMember useGeneratedKeystrue keyPropertyid INSERT INTO member(name, phone, gender, age, card_type, create_time) VALUES(#{name}, #{phone}, #{gender}, #{age}, #{cardType}, NOW()) /insert /mapper解释一下几个关键点Mapper 接口的Param注解是为了让 XML 里能用#{offset}引用参数不写的话 MyBatis 取不到名。useGeneratedKeys配合keyProperty插入后数据库自增的 id 会自动回填到对象的 id 属性这样新增完会员后可以直接拿到他 id 去给别的表用。parameterType不写也能通过反射推断但建议写清楚可读性更好MyBatis 解析时也能少走弯路。4.2 Service 层事务只加在该加的地方Service 层是业务逻辑所在典型的一段代码是预约课程。这里涉及两个操作插入预约记录同时把课程的剩余名额减一。这两个操作必须在一个事务里要么都成功要么都失败。Service public class ReservationServiceImpl implements ReservationService { Autowired private ReservationMapper reservationMapper; Autowired private CourseMapper courseMapper; Override Transactional(rollbackFor Exception.class) public boolean reserve(Reservation reservation) { // 1. 校验该会员是否已预约过同一节课 int count reservationMapper.countByMemberAndCourse( reservation.getMemberId(), reservation.getCourseId()); if (count 0) { throw new BusinessException(不能重复预约同一课程); } // 2. 校验课程是否还有名额 Course course courseMapper.selectById(reservation.getCourseId()); if (course.getCapacity() course.getReservedCount()) { throw new BusinessException(课程已满); } // 3. 插入预约记录 reservationMapper.insert(reservation); // 4. 课程已预约人数 1 courseMapper.incrementReservedCount(reservation.getCourseId()); return true; } }事务注解Transactional默认只回滚 RuntimeException用rollbackFor Exception.class的意思是所有异常都触发回滚。常见的一种错误是写了Transactional但内部 catch 住了异常打日志事务就不会回滚。因为 Spring 事务是通过 AOP 代理实现的异常必须穿透到代理层才会触发回滚逻辑。这里捕获异常后再抛出即可代码逻辑没问题但要注意这个坑。Service 层里尽量不出现HttpServletRequest、HttpServletResponse这类 Web 层对象保持业务逻辑独立将来从 JSP 换成前后端分离接口时 Service 不需要改。4.3 Controller 层参数接收和响应跳转Controller 层是 MVC 里的 C它的核心是接收前端参数、调用 Service、跳转视图。Controller RequestMapping(/member) public class MemberController { Autowired private MemberService memberService; RequestMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, Model model) { PageResultMember page memberService.findPage(pageNum, pageSize); model.addAttribute(page, page); return member/list; } RequestMapping(/add) public String add(Member member) { memberService.add(member); return redirect:/member/list; } }RequestParam(defaultValue 1)保证请求不带 pageNum 参数时不会报 400兜底成第一页。Model 是 SpringMVC 提供的视图数据容器addAttribute放入的数据可以在 JSP 里用${page.records}取出。redirect:前缀表示重定向避免表单重复提交——用户刷新页面时不会再次提交上一次的表单数据。这是 PRG 模式Post-Redirect-Get的标准做法。在 Java 面试题里经常被问到的RequestBody、ResponseBody这套 JSP 项目里用得少但你要知道它们存在的意义ResponseBody是把方法的返回值直接写成 JSON 响应给前端而不是当成视图名去解析。如果面试官问你“SSM 项目怎么返回 JSON”就是这两个注解配合实现。4.4 JSP 页面EL 表达式和 JSTL 渲染数据JSP 页面里最核心的工作是通过 EL 表达式把后台数据渲染到 HTML 上。这是 JSP 相比 Freemarker、Thymeleaf 的老派之处但逻辑一致模板变量替换。以会员列表页的关键代码为例% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table tr thID/th th姓名/th th手机号/th th性别/th th操作/th /tr c:forEach items${page.records} varm tr td${m.id}/td td${m.name}/td td${m.phone}/td td${m.gender 1 ? 男 : 女}/td td a href${pageContext.request.contextPath}/member/edit?id${m.id}编辑/a a href${pageContext.request.contextPath}/member/delete?id${m.id} onclickreturn confirm(确定删除吗)删除/a /td /tr /c:forEach /table /body /htmlJSP 两个容易出错的地方pageContext.request.contextPath是获取当前项目上下文路径。项目部署时如果不是部署在 Tomcat 根路径URL 必须带上下文路径。另一个是c:forEach的 items 必须是一个可遍历的集合如果从后台传过来的对象不是 List 就会直接 500 报错。4.5 分页查询MySQL 的 LIMIT 参数怎么动态传分页是列表页的标配需求上面 MemberController 里引用了PageResult这里把实现展开。public class PageResultT { private ListT records; private int pageNum; private int pageSize; private long total; private int totalPages; // getter/setter 省略 }Service 实现里分页查询的写法public PageResultMember findPage(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; ListMember records memberMapper.selectPage(offset, pageSize); long total memberMapper.countAll(); PageResultMember result new PageResult(); result.setRecords(records); result.setPageNum(pageNum); result.setPageSize(pageSize); result.setTotal(total); result.setTotalPages((int) Math.ceil((double) total / pageSize)); return result; }这里的offset计算是一个典型边界点。pageNum 1时 offset 为 0LIMIT 0, 10取第一页这个推导要自己验算一遍很多新人第 2 页拿到的是第 1 页重复数据就是因为 offset 写成了pageNum * pageSize。JSP 页面里还需要翻页导航用 JSTL 实现c:if test${page.pageNum 1} a href?pageNum${page.pageNum - 1}pageSize${page.pageSize}上一页/a /c:if c:if test${page.pageNum page.totalPages} a href?pageNum${page.pageNum 1}pageSize${page.pageSize}下一页/a /c:if分页还有另一种常见用法是 PageHelper 插件只需在 MyBatis 配置里添加一个拦截器查询前调用PageHelper.startPage(pageNum, pageSize)后面的 SQL 自动追加 LIMIT。但用在课程设计里手写分页反而更能体现功底面试时问到你也能把 LIMIT 参数的计算逻辑说清楚。5. SSMJSP 项目避坑记五个高频问题从现象到解决以下都是实际调试中反复出现的问题每一条都值得背下来。写代码时多花十分钟检查这些点能省下后面一整天的排查时间。5.1 启动报错Failed to configure a DataSource现象Tomcat 启动时直接失败控制台提示Failed to configure a DataSource: url attribute is not specified。原因项目里引入了 spring-boot-starter-jdbc 依赖Spring Boot 自动配置机制尝试创建数据源但找不到数据库连接配置。SSM 项目里出现这个通常是因为 pom.xml 混入了 Spring Boot 相关依赖。解决检查依赖树去掉所有 spring-boot-starter 开头的依赖。SSM 项目的数据源是自己声明 Bean 的不需要 Spring Boot 自动配置。如果一定要保留某些工具类用exclusions排除掉 auto-configuration 相关依赖。5.2 请求报 404HTTP 状态 404 的前端控制器映射坑现象访问/member/list返回 404Tomcat 控制台无任何异常日志。原因SpringMVC 的url-pattern配置成了*.do或者/以外的路径。.do风格是 Struts 遗留习惯如果写成了url-pattern*.do/url-pattern请求路径不带.do后缀自然匹配不到。解决把 DispatcherServlet 的 url-pattern 改成/让它处理所有请求。改完后静态资源访问会失效同时配置mvc:default-servlet-handler/SpringMVC 会先把请求交给默认 Servlet 处理静态资源处理不了的才进 Controller。5.3 Invalid bound statement (not found)Mapper 接口和 XML 对不上现象浏览器访问任何调用到某个 Mapper 的接口都会报Invalid bound statement (not found)但接口类存在。原因MyBatis 扫描 mapper XML 时找不到对应的 XML 文件。常见原因有两个一是 mapper XML 里的 namespace 写错了接口全限定名二是 XML 没被编译到 classes 目录Maven 默认只把src/main/resources下的文件打包进 classpath如果你把 XML 放在src/main/java下面默认会被忽略。解决第一检查 XML 的 namespace 是否和接口全限定名完全一致第二把 XML 文件移到src/main/resources/mapper/目录下或者在 pom.xml 里配置 resources 包含**/*.xmlbuild resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build这个配置的意思是虽然 XML 和 .java 在同一个目录构建时也把 XML 文件复制到输出目录。5.4 插入中文变成问号或乱码现象页面上新增的会员姓名在数据库里变成???或者从数据库查出来显示乱码。原因乱码几乎都是字符集不统一链路上的某一环断了。链路是「JSP 页面编码 → Tomcat 请求解码 → MySQL 连接字符集 → MySQL 表字符集」。最常见的是 MySQL 连接 URL 没带字符集参数以及 Tomcat 的 GET 请求默认用 ISO-8859-1 解码。解决三步到位。数据库连接 URL 加上?useUnicodetruecharacterEncodingutf8表创建时指定DEFAULT CHARSETutf8mb4配置一个 CharacterEncodingFilter 拦截所有请求强制转 UTF-8这个方法最有效。也许你习惯在每个 JSP 里写pageEncodingUTF-8我一般建议做的是写完之后再抓一次包确认 Content-Type 里 charset 是 utf-8 还是 ISO-8859-1——这一步很重要它负责的是配置文件没有覆盖到的漏网之鱼。5.5 JSON 序列化问题日期变成时间戳或格式不对现象Controller 返回一个含Date字段对象前端拿到的是类似1612345678912的毫秒值。原因没有配置日期格式化Jackson 默认把java.util.Date序列化成时间戳。解决在日期字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8)并确保该字段有 getter。如果用的是 fastjson则用JSONField(format yyyy-MM-dd HH:mm:ss)。这里额外提醒一个隐藏问题timezone不配的话数据库的 datetime 值可能被当成 UTC 时间解析显示出来比当地时间慢 8 小时。这是“网络热词 jsp个人信息展示页面”背后经常被问到的一个细节——不仅是展示页面所有涉及时间字段的序列化都躲不开这个规则。6. 部署上线到服务器打包、验证方法、MySQL 参数调到能用本地跑通只是前半段真正落地到服务器有几个容易翻车的地方说一下打包部署和上线后的验证方法。先讲打包。直接用 Maven 的 package 命令。mvn clean package打完包后在 target 目录会生成gym-manager.war把 war 文件放进 Tomcat 的 webapps 目录启动 Tomcat 后它会自动解压部署。需要注意如果你的项目里用了 Lombok打出来的 war 包依赖里必须带上它否则线上环境报错java.lang.NoClassDefFoundError本地开发不报是因为 IDE 插件替你处理了。接着检查 MySQL 最大连接数默认 151 在并发稍稍上来时就是瓶颈。登录到 MySQL 执行SHOW VARIABLES LIKE max_connections; SET GLOBAL max_connections 500;这里注意SET GLOBAL只在当前实例生效重启后还是要改配置文件里的max_connections。数据库连接池 C3P0 的maxPoolSize不要超过 MySQL 的max_connections留出 10% 余量给其他客户端这个参数调大不会让系统变快反而可能让连接池抢占 MySQL 连接资源导致别人的查询排队。上线后的验证不要只打开页面看有没有 404。列一个检查清单按顺序过一遍检查项命令或操作预期结果应用状态curl -I http://ip:8080/gym-manager/返回 200数据库连接登录页面输入 admin 账号能进后台日志无 ERRORtail -f logs/catalina.out无红色异常静态资源加载浏览器 F12 看 Console无 404 资源时区正确新增一个会员录入时间与本地时间一致还有一个细节值得单独说JSP 文件修改后不需要重启 Tomcat但生产环境默认 JSP 是预编译的修改后需要重新编译。如果线上发现改了页面内容但没生效检查 Tomcat 的 conf/web.xml 里 servlet 的 development 参数是否为 true。开发阶段要保持 true部署上线可以设成 false 节省 CPU但意味着你得重新部署才能改前端页面。最后说一点个人习惯。这套系统我前前后后帮人改过不下五个版本每次验收时被追问最多的问题排在第一位的是“你的密码存的是明文吗”。哪怕只是一个课程设计哪怕系统只在学校内网演示我也建议至少用 BCrypt 存密码。第二多的追问是“你的 SQL 有防注入吗”。MyBatis 里#{}是预编译参数占位安全${}是字符串拼接会有注入风险。凡是排序字段、表名这类无法用占位符的地方用白名单校验后再拼接不要把请求参数直接塞进去。这两条做上答辩时就有底气了。希望你把这套结构吃透后不只是会复制粘贴而是能从数据库表设计一路讲到部署细节。如果文章里某段配置或某个参数和你本地环境不一致多看看控制台报错再对照检查配置类的问题最怕静下心来看日志。希望帮到你。本文还有配套的精品资源点击获取
返回列表