简介本资源是一份面向计算机专业本科生及Java Web初学者的课程设计文档聚焦羽毛球运动场景下的Web应用开发实践解决体育爱好者信息获取、场地预约与社群互动的实际需求。文档完整呈现了基于JSPMySQLB/S架构的交流平台系统设计全过程涵盖开发背景、SSM框架选型依据、八大核心模块用户管理、新闻/场地/预约/评论/图片/留言/系统管理的功能分析与数据库设计并附有首页及站内新闻等关键页面的实现说明。资源为单个2.26MB的Word文档.docx内容结构清晰含摘要、目录、关键技术详解JSP、MySQL、MyEclipse、性能分析、需求建模、系统流程图及详细实现章节适合作为Java Web开发课程设计参考范本或毕业设计基础素材。目前已有245人学习下载。1. 为什么一个“羽毛球交流平台”要用 Java JSP MySQL 做——不是为了炫技而是让球友发帖不卡、约球不丢、积分不乱你见过凌晨两点还在刷新的羽毛球约球页面吗不是服务器崩了是用户刚点“我要打双打”系统卡在“提交中…”三秒后弹出“网络错误”结果隔壁群已经组好局、拍都拿上了。这不是玄学是典型 B/S 架构下 Web 层状态管理失当、数据库事务没兜住、前端异步没降级的真实翻车现场。这个标题里的“基于 Java 羽毛球交流平台系统设计与实现”本质是用成熟可控的技术栈解决一个高频、轻量、强交互、需长期运维的垂直社区场景用户注册/登录、场地发布/预约、约球组队、积分排行、图文动态、消息通知。它不追求高并发秒杀但要求数据一致性比如约球成功必须扣减可约名额、操作可追溯谁什么时候取消了局、界面响应快JSP 模板直出 HTML 避免首屏白屏。技术选型上Java 提供稳定生态和企业级工具链JSP 在当时是快速构建动态页面的务实选择比纯 Servlet 写 HTML 清晰比 Struts2 更轻MySQL 承担关系型数据核心用户、场地、订单、积分流水SSMSpring SpringMVC MyBatis则是当时最主流、文档最全、新人最容易上手的分层架构。它不是为写毕业论文而堆砌技术名词而是把“球友想发个带图的训练心得”“教练想批量导入学员名单”“管理员想查上周所有未履约约球记录”这些真实诉求一五一十落地成可运行、可维护、可排查的代码。如果你正要启动一个类似的小型社区项目或者正在啃一份用这套组合实现的毕设源码这篇笔记就是帮你绕过那些别人踩过的坑、直接跑通关键链路的实操地图。2. 从零搭起骨架用 SSM 搭建羽毛球平台基础工程结构与核心依赖一个能跑起来的羽毛球平台骨架必须稳。这里说的“骨架”不是指 Maven 的 pom.xml 里一堆坐标而是指 Spring 容器怎么加载、MyBatis 怎么连库、SpringMVC 怎么分发请求——这三者如何咬合决定了后续所有功能是否容易扩展、出错时日志是否清晰、改个字段要不要重写半套 DAO。我一般会先放弃“一步到位”的幻想用最简路径验证三层能否贯通写一个User实体类配一个UserMapper接口写一条SELECT * FROM user WHERE id ?再写一个UserController返回 JSON。能跑通这个最小闭环后面加场地、约球、积分就只是复制粘贴改字段。2.1 创建 Maven 工程并配置 SSM 核心依赖新建一个 Maven Web ProjectIDEA 或 Eclipse 均可pom.xml中必须明确指定 JDK 版本建议 1.8兼容性最好、Servlet API 版本3.1.0以及 SSM 三大件的版本。注意不要盲目追新。比如 Spring 5.3.x MyBatis 3.4.x SpringMVC 5.3.x 是当时最稳定的组合比 Spring Boot 2.0 早两年但对 Tomcat 8、JDK 1.8 支持极佳。以下是精简后的核心依赖块已去除非必要插件properties spring.version5.3.30/spring.version mybatis.version3.4.6/mybatis.version mysql.connector.version5.1.47/mysql.connector.version junit.version4.13.2/junit.version /properties dependencies !-- Spring Core -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency !-- Spring Web MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency !-- MyBatis-Spring 整合包关键没有它Spring 不认识 MyBatis-- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.connector.version}/version /dependency !-- JSP Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjsp-api/artifactId version2.2/version scopeprovided/scope /dependency !-- JSTLJSP 页面做循环、判断必需-- dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies提示scopeprovided/scope表示 Servlet 和 JSP API 由 Tomcat 提供打包时不要打进 WAR 包否则会冲突。mybatis-spring是桥接器版本必须严格匹配 MyBatis 和 Spring 版本1.3.2 对应 MyBatis 3.4.x 和 Spring 5.x这是血泪经验——配错版本会导致SqlSessionFactoryBean初始化失败报NoSuchMethodError日志里根本找不到UserMapper的影子。2.2 配置 Spring 容器applicationContext.xml 与数据库连接池Spring 容器是整个系统的“心脏”它负责创建、装配、管理所有 BeanService、DAO、DataSource。配置文件src/main/resources/applicationContext.xml是它的蓝图。这里最关键的不是写多少 Bean而是DataSource 的配置方式。别用BasicDataSourceDBCP它在高并发下容易连接泄漏也别手写DriverManagerDataSource每次请求都新建连接性能灾难。我坚持用Druid阿里开源它自带监控、SQL 防火墙、连接泄漏检测且配置项清晰。以下是生产环境可用的最小化 Druid 配置!-- 数据源配置 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property nameurl valuejdbc:mysql://localhost:3306/badminton?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value123456/ property namedriverClassName valuecom.mysql.jdbc.Driver/ !-- 连接池基础参数 -- property nameinitialSize value5/ property nameminIdle value5/ property namemaxActive value20/ !-- 连接有效性检测关键避免拿到失效连接-- property namevalidationQuery valueSELECT 1/ property nametestWhileIdle valuetrue/ property nametimeBetweenEvictionRunsMillis value60000/ !-- 防泄漏必须开否则约球高峰期连接数暴涨不回收-- property nameremoveAbandonedOnMaintenance valuetrue/ property nameremoveAbandonedOnBorrow valuetrue/ property nameremoveAbandonedTimeout value1800/ /bean !-- SqlSessionFactoryBeanMyBatis 的工厂把 DataSource 和 Mapper XML 关联起来 -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nameconfigLocation valueclasspath:mybatis-config.xml/ /bean !-- MapperScannerConfigurer自动扫描 Mapper 接口生成代理对象 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.badminton.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean参数说明initialSize和minIdle设为 5保证服务启动就有 5 个空闲连接待命maxActive20 是保守值足够支撑 500 日活的羽毛球平台validationQuery必须是SELECT 1不能写SELECT NOW()否则某些 MySQL 版本会因权限问题失败removeAbandonedOnBorrow开启后如果某次借连接超时removeAbandonedTimeout1800秒Druid 会强制回收并打印警告日志——这是排查“连接耗尽”的后悔药。2.3 配置 SpringMVCweb.xml 与 dispatcher-servlet.xmlSpringMVC 是“前台接待员”它决定哪个 URL 交给哪个 Controller 处理。配置分两层web.xml是 Servlet 容器Tomcat的入口dispatcher-servlet.xml是 SpringMVC 自己的子容器。顺序不能错先加载applicationContext.xml父容器含 Service、DAO再加载dispatcher-servlet.xml子容器含 Controller、ViewResolver。web.xml关键片段如下!-- 加载 Spring 父容器 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- SpringMVC 前端控制器 -- servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:dispatcher-servlet.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping !-- 解决 POST 请求中文乱码 -- 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 init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mappingdispatcher-servlet.xml则专注 MVC 本身!-- 启用注解驱动 -- mvc:annotation-driven/ !-- 静态资源放行CSS/JS/IMG -- mvc:default-servlet-handler/ !-- 视图解析器JSP 文件放在 /WEB-INF/jsp/ 下后缀为 .jsp -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean !-- 扫描 Controller -- context:component-scan base-packagecom.badminton.controller/注意mvc:default-servlet-handler/必须有否则/static/css/app.css这类静态资源会被 DispatcherServlet 拦截返回 404InternalResourceViewResolver的prefix和suffix决定了你的 JSP 页面物理路径——所有 Controller 返回user/list实际渲染的是/WEB-INF/jsp/user/list.jsp这是安全隔离防止用户直接访问 JSP 源码。3. 让数据活起来MySQL 数据库设计与 MyBatis 映射实战羽毛球平台的数据模型表面看是“用户、场地、约球”但深挖需求会发现一个用户可以是普通球友、场地管理员、系统超级管理员一场约球包含时间、地点、人数、费用、状态待确认/进行中/已完成/已取消积分不是简单数字而是按“发起约球10、履约20、被爽约-5”等规则动态计算。所以数据库设计不能只画 ER 图得想清楚哪些字段要索引、哪些关联要懒加载、哪些状态变更要触发日志。我习惯先建user表再建court场地最后用appointment约球表把它们串起来中间穿插score_log积分流水表记录每一次增减。3.1 核心数据表设计与建表 SQL含索引与注释以下 SQL 直接可执行MySQL 5.7每张表都标注了业务含义和关键索引原因。别跳过注释它们是未来查慢 SQL 的线索-- 用户表type 字段区分角色0-普通用户, 1-场地管理员, 2-超级管理员 CREATE TABLE user ( id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名登录用, password VARCHAR(100) NOT NULL COMMENT BCrypt 加密密码, real_name VARCHAR(30) DEFAULT NULL COMMENT 真实姓名, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像URL, score INT(11) DEFAULT 0 COMMENT 当前积分, type TINYINT(1) DEFAULT 0 COMMENT 用户类型0-普通,1-场地管理员,2-超级管理员, status TINYINT(1) DEFAULT 1 COMMENT 状态0-禁用,1-启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_username (username), -- 登录查询必走 KEY idx_phone (phone) -- 手机号找回密码必走 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户基本信息表; -- 场地表address 字段用 TEXT因为地址可能很长status 控制是否对外显示 CREATE TABLE court ( id BIGINT(20) NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 场地名称, address TEXT NOT NULL COMMENT 详细地址, owner_id BIGINT(20) NOT NULL COMMENT 所属管理员ID关联 user.id, price_per_hour DECIMAL(10,2) DEFAULT 0.00 COMMENT 每小时价格, capacity INT(11) DEFAULT 4 COMMENT 容纳人数默认双打, status TINYINT(1) DEFAULT 1 COMMENT 状态0-停用,1-启用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner_id (owner_id), -- 查某管理员所有场地 KEY idx_status (status) -- 列表页按状态筛选 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT羽毛球场地信息表; -- 约球表核心业务表status 是状态机起点 CREATE TABLE appointment ( id BIGINT(20) NOT NULL AUTO_INCREMENT, court_id BIGINT(20) NOT NULL COMMENT 场地ID, creator_id BIGINT(20) NOT NULL COMMENT 发起人ID, start_time DATETIME NOT NULL COMMENT 开始时间, end_time DATETIME NOT NULL COMMENT 结束时间, player_count INT(11) DEFAULT 2 COMMENT 预计人数, status TINYINT(1) DEFAULT 0 COMMENT 状态0-待确认,1-已确认,2-进行中,3-已完成,4-已取消,5-已爽约, fee DECIMAL(10,2) DEFAULT 0.00 COMMENT 费用, remark VARCHAR(255) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_court_id (court_id), -- 查某场地所有约球 KEY idx_creator_id (creator_id), -- 查某人发起的所有约球 KEY idx_start_time (start_time), -- 按时间范围查询如查今天所有约球 KEY idx_status (status) -- 状态筛选如查所有“待确认”约球 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT约球预约主表; -- 积分流水表每一次积分变动都留痕不可删改 CREATE TABLE score_log ( id BIGINT(20) NOT NULL AUTO_INCREMENT, user_id BIGINT(20) NOT NULL COMMENT 用户ID, change_amount INT(11) NOT NULL COMMENT 变动金额正为增加负为减少, reason VARCHAR(100) NOT NULL COMMENT 原因APPOINTMENT_CREATE, APPOINTMENT_FINISH, APPOINTMENT_CANCEL..., related_id BIGINT(20) DEFAULT NULL COMMENT 关联ID如 appointment.id, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), -- 查某人所有积分记录 KEY idx_related_id (related_id) -- 通过约球ID反查积分变动 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户积分变动流水表;为什么这样设计user.score字段是冗余的汇总值方便快速展示所有明细记在score_log保证可追溯appointment.status用数字而非字符串节省空间且排序快每个表都有create_time和update_time审计必备所有外键如court.owner_id没加FOREIGN KEY约束——不是偷懒而是避免线上ALTER TABLE锁表用应用层逻辑保证一致性更可控。3.2 MyBatis 映射XML 方式实现复杂查询与动态 SQLMyBatis 的强大在于 XML 中的if、foreach、choose。比如“管理员查看自己名下所有场地并支持按名称模糊搜索、按状态筛选”用纯注解写会非常臃肿XML 则清晰直观。CourtMapper.xml示例?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.badminton.mapper.CourtMapper !-- 根据管理员ID查询场地列表支持条件筛选 -- select idselectByOwnerId resultTypecom.badminton.entity.Court SELECT * FROM court WHERE owner_id #{ownerId} if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND status #{status} /if ORDER BY create_time DESC /select !-- 查询某场地未来7天内所有已被预约的时间段用于前端日历禁用 -- select idselectOccupiedSlots resultTypemap SELECT DATE(start_time) as date, HOUR(start_time) as hour_start, HOUR(end_time) as hour_end FROM appointment WHERE court_id #{courtId} AND status IN (1, 2, 3) -- 只查已确认、进行中、已完成的排除待确认和取消 AND start_time DATE(NOW()) AND start_time DATE_ADD(NOW(), INTERVAL 7 DAY) ORDER BY start_time /select /mapper对应的CourtMapper.java接口public interface CourtMapper { // 根据管理员ID查询场地列表 ListCourt selectByOwnerId(Param(ownerId) Long ownerId, Param(name) String name, Param(status) Integer status); // 查询某场地未来7天被占用的时间段返回Map列表key为date/hour_start/hour_end ListMapString, Object selectOccupiedSlots(Param(courtId) Long courtId); }参数说明Param注解是必须的否则 MyBatis 在多参数时无法绑定#{ownerId}selectOccupiedSlots返回ListMapString, Object是因为结果字段名是别名date,hour_start不适合映射到固定实体类用 Map 更灵活status IN (1,2,3)这里硬编码是合理的因为状态值在业务逻辑里是常量改数据库字段成本远高于改代码。3.3 MyBatisPlus 小技巧根据 Java 实体类生成建表 SQL非官方但极实用虽然项目用的是原生 MyBatis但开发阶段快速建库我常用 MyBatisPlus 的AutoGenerator逆向工程能力——不是集成进项目而是单独写个 Main 方法生成 SQL。步骤如下新建一个独立的 Java 类不在主项目里引入 MyBatisPlus 依赖仅编译期配置DataSourceConfig指向本地测试库调用generator.execute()它会扫描TableName注解的实体类输出标准建表语句。public class SqlGenerator { public static void main(String[] args) { // 1. 全局配置 GlobalConfig gc new GlobalConfig(); gc.setOutputDir(D:\\temp); // 输出路径 gc.setAuthor(dev); gc.setOpen(false); // 不打开输出目录 // 2. 数据源配置指向你本地 MySQL DataSourceConfig dsc new DataSourceConfig(); dsc.setUrl(jdbc:mysql://localhost:3306/test?useUnicodetruecharacterEncodingutf8); dsc.setDriverName(com.mysql.jdbc.Driver); dsc.setUsername(root); dsc.setPassword(123456); // 3. 策略配置只生成表结构不生成代码 StrategyConfig strategy new StrategyConfig(); strategy.setInclude(user, court, appointment, score_log); // 指定表名 strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰 strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 4. 执行生成 AutoGenerator mpg new AutoGenerator(); mpg.setGlobalConfig(gc); mpg.setDataSource(dsc); mpg.setStrategy(strategy); mpg.execute(); } }运行后D:\temp下会生成user.sql等文件内容是标准的CREATE TABLE ...语句可直接在 MySQL 客户端执行。这比手写 SQL 快 5 倍且零拼写错误。注意生成的 SQL 默认带ENGINEInnoDB和CHARSETutf8mb4符合我们前面的设计。4. 把功能串起来JSP 页面实现与关键交互逻辑含图片定位、视频播放JSP 是这个平台的“皮肤”它把后端数据变成用户看得见、点得着的界面。但 JSP 不是写 HTML而是用c:if、c:forEach、fmt:formatDate等标签处理动态数据用img和video标签展示媒体。难点不在语法而在如何让一张场地照片精准显示在页面指定位置、如何让一段教学视频在 JSP 里流畅播放、如何让用户上传的头像实时显示。这些看似前端的事其实和后端路径配置、文件存储策略强相关。4.1 JSP 页面基础结构与 JSTL 标签使用规范所有 JSP 页面必须以标准模板开头声明所需标签库并设置字符集。/WEB-INF/jsp/base.jsp是母版其他页面% include file/WEB-INF/jsp/base.jsp %即可复用头部、尾部% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8% % taglib prefixc urihttp://java.sun.com/jsp/jstl/core% % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt% % taglib prefixfn urihttp://java.sun.com/jsp/jstl/functions% !DOCTYPE html html head meta charsetUTF-8 title羽毛球交流平台/title link relstylesheet href${pageContext.request.contextPath}/static/css/bootstrap.min.css link relstylesheet href${pageContext.request.contextPath}/static/css/style.css /head body !-- 导航栏 -- nav classnavbar navbar-expand-lg navbar-light bg-light a classnavbar-brand href${pageContext.request.contextPath}/羽毛球平台/a div classcollapse navbar-collapse ul classnavbar-nav mr-auto li classnav-itema classnav-link href${pageContext.request.contextPath}/court/list场地列表/a/li li classnav-itema classnav-link href${pageContext.request.contextPath}/appointment/my我的约球/a/li /ul c:choose c:when test${sessionScope.user ! null} span classnavbar-text欢迎${sessionScope.user.realName}/span a classbtn btn-sm btn-outline-primary ml-2 href${pageContext.request.contextPath}/logout退出/a /c:when c:otherwise a classbtn btn-sm btn-outline-primary ml-2 href${pageContext.request.contextPath}/login登录/a /c:otherwise /c:choose /div /nav !-- 主体内容由子页面填充 -- div classcontainer mt-4 c:if test${not empty errorMsg} div classalert alert-danger${errorMsg}/div /c:if c:if test${not empty successMsg} div classalert alert-success${successMsg}/div /c:if关键点${pageContext.request.contextPath}是绝对路径前缀确保 CSS/JS/图片链接不因部署路径变化而 404c:choose做登录态判断比写 Java Scriptlet 更安全c:if用于显示提示消息避免空指针异常。4.2 JSP 图片定位用 CSS Grid 精确控制场地照片布局“JSP 图片如何对坐标定位”这个问题本质是前端布局问题但 JSP 里必须提供结构化的数据。比如场地详情页需要显示一张大图封面 四张小图环境细节。后端 Controller 把图片 URL 列表传给 JSPController public class CourtController { RequestMapping(/court/detail) public String detail(RequestParam Long id, Model model) { Court court courtService.findById(id); // 假设 court.getImages() 返回 ListString每个元素是图片相对路径如 /upload/court_1_1.jpg model.addAttribute(court, court); return court/detail; } }JSP 页面用 CSS Grid 定位代码简洁且响应式div classcourt-images !-- 封面大图占满整行 -- img src${pageContext.request.contextPath}${court.images[0]} alt封面 classcover-img !-- 四张小图2x2 网格 -- div classthumbnail-grid c:forEach items${court.images} varimg varStatusstatus c:if test${status.index 0 status.index 5} img src${pageContext.request.contextPath}${img} alt细节图 ${status.index} classthumb-img /c:if /c:forEach /div /div配套 CSS/static/css/style.css.court-images { margin: 20px 0; } .cover-img { width: 100%; height: 400px; object-fit: cover; /* 关键等比缩放填满容器 */ border-radius: 8px; } .thumbnail-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px; } .thumb-img { width: 100%; height: 150px; object-fit: cover; border-radius: 4px; cursor: pointer; }为什么用object-fit: cover它确保不同尺寸的图片比如 1920x1080 的封面 vs 800x600 的细节图都能完美填满容器不拉伸、不变形、不留白。这是比background-image更语义化、更利于 SEO 的方案。4.3 JSP 实现 MP4 视频播放嵌入 HTML5video标签“JSP 实现 mp4 视练播放”只需一行 HTML5 标签但路径和属性必须正确。假设视频存放在/WEB-INF/upload/video/注意/WEB-INF/下资源不能被直接 URL 访问所以视频必须放在webapp/upload/video/目录下!-- Controller 传入 videoPath /upload/video/coach_tutorial.mp4 -- video width800 height450 controls autoplay muted source src${pageContext.request.contextPath}${court.videoPath} typevideo/mp4 您的浏览器不支持视频播放。 /video关键属性说明controls显示播放控件autoplay自动播放但现代浏览器要求muted静音才允许source标签明确指定 MIME 类型避免某些服务器返回text/plain导致无法播放src使用${pageContext.request.contextPath}确保路径正确。切记MP4 文件必须是 H.264 编码 AAC 音频否则 iOS 设备可能无法播放。5. 避坑指南SSM JSP MySQL 组合下最常见的 5 个翻车现场与解决方案这套技术栈成熟但成熟不等于没坑。很多问题不是代码写错而是配置项理解偏差、版本冲突、或对框架生命周期无知导致的。下面这 5 条是我在线上环境、毕设答辩、外包交付中反复踩过、也帮别人救过火的真问题每一条都按“现象 → 原因 → 解决”给出可立即执行的动作。5.1 现象JSP 页面中文显示为乱码如“羽毛球”显示为“???”但数据库里是好的原因三个地方的字符集没对齐——MySQL 连接 URL 里没加characterEncodingutf8Tomcat 的server.xml里没配URIEncodingUTF-8JSP 页面没声明pageEncodingUTF-8。三者缺一不可。解决检查applicationContext.xml中dataSource.url确认包含?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai打开 Tomcatconf/server.xml找到Connector port8080...添加属性URIEncodingUTF-8所有 JSP 文件第一行必须是% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%。提示contentType和pageEncoding必须都是UTF-8且大小写敏感写成utf-8无效。5.2 现象MyBatis 查询返回null但日志显示 SQL 执行成功且用 Navicat 手动执行同一 SQL 能查到数据原因实体类POJO的字段名与数据库列名不一致且没配resultMap或开启mapUnderscoreToCamelCase。例如数据库列是user_name实体类字段是userNameMyBatis 默认不会自动转换。解决在mybatis-config.xml中开启全局驼峰转换configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings /configuration或者在SqlSessionFactoryBean配置中指定bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property nameconfigurationProperties props prop keymapUnderscoreToCamelCasetrue/prop /props /property /bean注意本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?