ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于Java+Spring Boot的社团活动网站源码设计与部署实战

2026/10/7 20:51:04 拓冰建站 浏览量
基于Java+Spring Boot的社团活动网站源码设计与部署实战 简介基于Java的社团活动网站设计与实现完整源码主要面向Java全栈开发者、高校毕业设计或课程设计人群用于解决高校/机构内社团信息发布、活动报名、成员管理等活动组织效率低下的问题采用前后端分离架构。压缩包共108个文件大小约902KB其中包含47个Java源文件处理后端业务与数据逻辑22个Vue组件负责前端页面搭建12个JavaScript脚本增强交互与动态渲染另有XML、CSS、SQL、JSON等文件覆盖配置、样式与数据层面。项目目录组织清晰文件命名规范可读性与可维护性较好目前已有262人学习浏览。开发者既能借此深入掌握Java后端开发细节也能学习Vue等前端框架的动态网站构建思路是一份适合作为社团管理平台改造或全栈技术进阶的实战参考资料。1. 基于 Java 的社团活动网站设计与实现源码别把它当成黑匣子基于 Java 的社团活动网站设计与实现源码听上去像是又一个毕业设计模板题但它真正解决的是“活动发布、在线报名、后台管理”这一条完整业务链。很少有一个 Java 项目能像它这样既能当课程设计交差又能被改造成二手交易、竞赛报名甚至会议室预约系统。这套源码的核心不是花哨页面而是几张表、几个接口和一组管理页面。我见过不少学生拿到源码跑不起来问题往往不在代码本身而是不知道 Web 容器怎么配、数据库初始化脚本放哪、JSP 和静态资源为什么 404。这篇笔记会把一套典型的 Java 社团活动网站从技术选型拆到部署排查讲清楚表怎么建、登录和报名怎么写、上线前怎么验证以及那些你迟早会踩的坑。适合刚学 Java Web 的初学者、准备二次开发的毕业生以及想快速搭活动管理后台的社团技术负责人。2. 社团活动网站的技术选型为什么 Spring Boot MyBatis 比 SSM 更省心如果你手里拿到了多个版本的“社团活动网站源码”会发现 Java 系的无外乎三种纯 JSPServlet、SSMSpringSpring MVCMyBatis、Spring BootMyBatis。对社团活动这种业务边界清晰、并发不高的系统我一般会直接选 Spring Boot MyBatis。不是因为 SSM 不能跑而是 SSM 的配置文件太多启动排错成本高Spring Boot 用自动配置和起步依赖把大部分样板代码收走了。这里的关键不是“哪个更高级”而是“哪个更快跑通出问题后更快定位”。我曾帮人调过一份 SSM 老源码一本 spring-mvc.xml 里把context:component-scan写错扫了两遍路径启动时不报错注入时全是 null这种黑匣子问题在 Spring Boot 里几乎不会出现。2.1 从 Servlet 到 Spring BootJava Web 框架的三代变化Java Web 的演进主线是从 JSPServlet 的脚本式开发到 Spring MVC 的注解驱动再到 Spring Boot 的自动装配。对社团网站这个场景JSPServlet 不是不能写而是代码量翻倍一个活动列表页要写一堆 out.println登录校验每个 Servlet 重复 copy。SSM 解决了分层问题但配置繁琐。Spring Boot 则把 Tomcat 内嵌进来启动一个 main 方法就能看到网站部署时又可以打成 war 包放到外部 Tomcat。这套组合对“拿源码自己改”的人来说是最友好的依赖由 Maven 统一管理配置集中在 application.yml出问题时错误信息也能直接指向 bean 初始化还是数据库连接。很多 Java 开发工程师面试题里会问“SSM 和 Spring Boot 的区别”核心答案就是自动配置与起步依赖。自动配置是 Spring Boot 根据 classpath 下的依赖推断出你要干什么加了 spring-boot-starter-web它就帮你配好 DispatcherServlet 和 Jackson加了 mybatis-spring-boot-starter它就帮你扫描 Mapper 接口。你只需要关心业务代码而不是那些 XML。社团网站源码如果给你的是 SSM 版本也不是不能改造成 Spring Boot但建议直接拿 Spring Boot 版做基础省下至少一个下午的配置时间。当然这不是说不用理解底层原理。恰恰相反正因为 Spring Boot 封装得太好很多人连 DispatcherServlet 都不认识。但作为落地项目我们追求的是先在本地跑起来再往深处看源码。所以选型结论是项目用 Spring Boot MyBatis Thymeleaf如果要服务端渲染接口层可以用 JSP 或模板引擎二选一。我见过一半以上的社团网站源码用的是 JSP所以后面部署部分我同时兼容外部 Tomcat 的 war 部署。2.2 可复现的项目结构一个 Spring Boot 项目如何组织拿到源码第一件事不是双击启动类而是看目录结构。一个标准的 Maven 工程必须能区分 controller、service、mapper、entity、config 五层。下面这个结构是社团网站最常见的组织方式src/main/java/com/example/club/ ├── controller/ # 接收 HTTP 请求返回 JSON 或页面 │ ├── ActivityController.java │ ├── UserController.java │ └── AdminController.java ├── service/ # 业务逻辑事务边界写在这里 │ ├── ActivityService.java │ └── UserService.java ├── mapper/ # MyBatis 的 Mapper 接口一个接口对应一个 XML │ ├── ActivityMapper.java │ └── UserMapper.java ├── entity/ # 数据库表对应的实体类 │ ├── Activity.java │ ├── User.java │ └── Club.java └── config/ # 拦截器、WebMvc 配置 └── LoginInterceptor.java src/main/resources/ ├── mapper/ # MyBatis 的 XML 文件和 mapper 接口同包路径 │ ├── ActivityMapper.xml │ └── UserMapper.xml └── application.yml # 数据源、端口、文件上传等配置 src/main/webapp/ # 如果使用 JSP 作为视图层 ├── static/ # CSS、JS、图片 └── WEB-INF/views/ # JSP 页面这个结构就是三层架构的一个具体实例。controller 层不做 SQL 拼接service 层是业务逻辑的边界mapper 层只管数据库读写。很多学生喜欢把业务逻辑写在 controller 里比如在 getActivityList 方法里直接调 mapper结果后面加一个“报名人数统计”功能就要把 controller 重写一遍。按这个结构mapper 的 SQL 写在 XML 里service 组合各种 mapper 方法controller 只做参数收集和结果封装。源码阅读建议也按这个顺序先 application.yml 看数据源和端口再 entity 看字段然后打开 Mapper XML 看 SQL最后看 service 和 controller。这样两小时就能把整个项目摸清。application.yml 里有几个必配项spring.datasource.url、driver-class-name、username、password以及 mybatis.mapper-locations。mapper-locations 写成 classpath:mapper/*.xml 是最常见配置如果你的 XML 没放在 resources/mapper 下启动后调用 Mapper 会报 Invalid bound statement这个问题在第 4 章会专门说。另一个值得提前留意的参数是 server.servlet.context-path如果配置了 /club那么所有接口访问路径都要加 /club 前缀部署到 Tomcat 后还要和 war 包名区分开容易混乱。2.3 数据模型设计活动、用户、社团、报名四张核心表社团活动网站的业务闭环是用户加入社团社团发布活动用户在活动页报名管理员导出名单。最少需要四张表club、user、activity、activity_signup。下面是一份可以直接执行的初始化 SQL字段命名用下划线Java 实体里对应驼峰MyBatis 开启 map-underscore-to-camel-case 后就能自动映射。-- 社团表 CREATE TABLE club ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password VARCHAR(128) NOT NULL, -- 建议存 BCrypt 哈希不要存明文 real_name VARCHAR(32), role TINYINT NOT NULL DEFAULT 1, -- 1 普通用户 2 管理员 club_id INT, FOREIGN KEY (club_id) REFERENCES club(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 活动表 CREATE TABLE activity ( id INT PRIMARY KEY AUTO_INCREMENT, club_id INT NOT NULL, title VARCHAR(128) NOT NULL, content TEXT, location VARCHAR(128), start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, max_people INT NOT NULL DEFAULT 100, remain_count INT NOT NULL DEFAULT 100, status TINYINT NOT NULL DEFAULT 1, -- 1 报名中 2 进行中 3 已结束 4 已取消 FOREIGN KEY (club_id) REFERENCES club(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 报名表 CREATE TABLE activity_signup ( id INT PRIMARY KEY AUTO_INCREMENT, activity_id INT NOT NULL, user_id INT NOT NULL, signup_time DATETIME DEFAULT CURRENT_TIMESTAMP, status TINYINT DEFAULT 1, -- 1 有效 2 取消 UNIQUE KEY uk_activity_user (activity_id, user_id), FOREIGN KEY (activity_id) REFERENCES activity(id), FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里的核心设计点是 activity 表加了 remain_count 字段默认等于 max_people每次报名成功就减一。这是后面解决并发超卖的基础比“先 select count(*) 再比较”要安全得多。activity_signup 表加了联合唯一索引 uk_activity_user从数据库层面保证同一个用户不能重复报名。这两处是很多零散源码容易忽略的逻辑上看着没问题并发一压或者手滑点了两次提交就翻车。数据模型还有个常见误区用户表直接存明文密码或者用 md5 不带盐。社团网站虽然不一定被攻击但既然要做完整源码就用 BCrypt。另外字段类型上尽量用 utf8mb4不要用 utf8因为活动内容里很容易出现 emoji 表情utf8 会报 1366 错误。如果你拿到的源码表里把报名用户存在 activity 的 signup_ids 字段里用逗号分隔建议尽早拆出单独的报名表否则后面统计人数、取消报名都写不出简洁的 SQL。3. 把登录、发布活动、在线报名写进源码Controller Service Mapper 三层实现3.1 登录接口与登录态拦截Session 超时怎么处理社团网站的前后端一般是同部署登录用 Session 比 Token 简单得多。登录成功后把用户对象放进 HttpSession后续接口通过拦截器判断 Session 里有没有 user没有就重定向到登录页。下面是一个登录接口的典型写法RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/login) public Result login(RequestBody LoginRequest req, HttpSession session) { User user userService.login(req.getUsername(), req.getPassword()); if (user null) { return Result.error(用户名或密码错误); } // 登录成功把用户ID、用户名、角色放进 Session // 注意不要存整个 password 字段后续展示时也建议用 VO 过滤 session.setAttribute(loginUser, user); return Result.success(user); } PostMapping(/logout) public Result logout(HttpSession session) { session.removeAttribute(loginUser); return Result.success(); } }这里的 userService.login 内部会先查用户再用 BCrypt 校验密码。注意代码里直接返回了 User 对象如果 User 类里有 password 字段Jackson 序列化时会把它带出去。常见做法是在 User 的 password 字段上加 JsonIgnore或者返回一个只带 id、username、role 的 LoginResult。Session 默认超时时间由 server.servlet.session.timeout 控制我一般给 30 分钟太短了用户填完报名表单发现被踢出去太长了后台管理有安全风险。光有登录接口还不够要拦截未登录的访问。实现一个 HandlerInterceptorpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(loginUser) null) { // 如果是 AJAX 请求返回 401 让前端跳转普通请求直接重定向登录页 if (request.getHeader(X-Requested-With) ! null) { response.setStatus(401); } else { response.sendRedirect(/login); } return false; } return true; } }然后注册进 WebMvc 配置Configuration public class WebConfig implements WebMvcConfigurer { Autowired private LoginInterceptor loginInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /api/user/login, /static/**, /activity/list); } }这个配置里最容易翻车的是路径匹配。addPathPatterns(/) 匹配所有路径excludePathPatterns 里的 /static/放行静态资源。很多源码忘了放行 /static结果登录页 CSS 全挂。另外如果项目是前后端分离前端请求都带 X-Requested-With: XMLHttpRequest 头那么 401 状态的处理要由前端统一拦截不要在拦截器里 sendRedirect否则 AJAX 会拿到一段 HTML。3.2 活动发布接口文件上传与数据入库的边界管理员发布活动通常不止文本字段还要传一张封面图。上传文件的关键是“存哪里”和“怎么访问”。不要把图片存数据库 BLOB而是存到本地磁盘目录数据库只存相对路径。PostMapping(/activity/publish) public Result publish(RequestParam(title) String title, RequestParam(content) String content, RequestParam(location) String location, RequestParam(startTime) String startTime, RequestParam(endTime) String endTime, RequestParam(maxPeople) Integer maxPeople, RequestParam(value cover, required false) MultipartFile cover, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); if (loginUser.getRole() ! 2) { return Result.error(无权限); } Activity activity new Activity(); activity.setClubId(loginUser.getClubId()); activity.setTitle(title); activity.setContent(content); activity.setLocation(location); activity.setStartTime(LocalDateTime.parse(startTime, DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); activity.setEndTime(LocalDateTime.parse(endTime, DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); activity.setMaxPeople(maxPeople); activity.setRemainCount(maxPeople); activity.setStatus(1); if (cover ! null !cover.isEmpty()) { activity.setCoverUrl(fileStorageService.save(cover)); } activityService.publish(activity); return Result.success(); }这段逻辑有几个参数细节startTime 和 endTime 从前端传来的是字符串用 DateTimeFormatter 解析成 LocalDateTime如果前端传的是时间戳数字就要用 Instant.ofEpochMilli 转换。maxPeople 同时赋给 max_people 和 remain_count初始名额就是最大名额。权限判断用的是 Session 里的 role这个字段在前端也可以展示但真正的校验必须放在后端否则有人直接 POST 接口就能发活动。文件存储服务的参考实现如下Service public class FileStorageService { Value(${file.upload-dir:./uploads}) private String uploadDir; public String save(MultipartFile file) { // 用 UUID 重命名避免中文文件名和路径问题 String original file.getOriginalFilename(); String ext original null ? : original.substring(original.lastIndexOf(.)); String fileName UUID.randomUUID() ext; File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir, fileName)); return /uploads/ fileName; } catch (IOException e) { throw new RuntimeException(文件保存失败, e); } } }如果使用 Spring Boot 内置 Tomcat 启动/uploads 并不是默认静态资源路径直接把 /uploads/xxx.jpg 写在 img 标签里会 404。需要在 WebConfig 加一行Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceLocations(file: uploadDir /); }参数说明uploadDir 默认是工程目录下的 ./uploads生产环境部署后我一般会改成 Linux 绝对路径比如 /data/club/uploads并且保证该目录有写权限。否则用 Tomcat 部署 war 包时uploads 目录会在临时解压目录里重启 Tomcat 文件就没了这是经典翻车点。3.3 在线报名接口与“名额超卖”的修正报名是社团网站要求最严格的接口因为它涉及数据一致性。最直白的写法是“查剩余名额大于 0 就插入”但并发下两个请求同时查出来都是 1就会都通过最终超卖。正确做法是把扣减名额做成一个带条件的原子更新再用受影响行数判断是否成功。Transactional public void signUp(Integer activityId, Integer userId) { Activity activity activityMapper.selectById(activityId); if (activity null || activity.getStatus() ! 1) { throw new BusinessException(活动不存在或不在报名期); } // 原子扣减只有当 remain_count 0 时才会更新成功 int rows activityMapper.reduceRemainCount(activityId); if (rows 0) { throw new BusinessException(名额已满); } try { signupMapper.insert(activityId, userId); } catch (DuplicateKeyException e) { // 联合唯一索引兜底防止重复报名时把名额也扣了 throw new BusinessException(你已经报名过该活动); } }对应 Mapper XMLupdate idreduceRemainCount UPDATE activity SET remain_count remain_count - 1 WHERE id #{activityId} AND remain_count 0 /update逻辑说明reduceRemainCount 的 update 语句利用 MySQL 行锁多个并发事务同时执行时只有第一个能拿到行锁并把 remain_count 改掉后续事务要么等锁要么条件不满足返回 0。Transactional 保证扣名额和插报名记录要么都成功要么都回滚。如果 insert 因为唯一索引冲突抛出 DuplicateKeyException事务回滚会把刚才扣掉的名额加回来所以不需要在 catch 里手动 update 回滚回滚会自动恢复。这里有个必要前提activity 表里得有 remain_count 字段。如果源码里没有可以执行 ALTER TABLE activity ADD COLUMN remain_count INT NOT NULL DEFAULT 0然后执行 UPDATE activity SET remain_count max_people 把存量数据补上。另外注意报名接口一定要校验活动状态 status否则已取消的活动还能报名。还有不要把状态判断放在前端管理员下架活动后老页面仍可能带着报名表单后端校验是最后防线。4. 社团活动网站源码里的常见问题与排查5 个高频翻车点下面这五个问题是我在帮人调试社团网站源码时遇到概率最高的。每一条都按“现象、原因、解决”来说你可以直接对照排查。4.1 数据库连接池连接耗尽网站突然卡死现象网站运行半天后所有涉及数据库的接口全部超时日志里刷 Connection is not available, request timed out after 30000ms。原因连接池最大连接数配得太小或者代码里有数据库连接泄漏。很多旧源码用 C3P0 或 DBCP配置里 maxPoolSize 只有 5稍微有几个慢查询就把连接占满。还有人喜欢在业务代码里手动写 JDBC 工具类开了 connection 不关时间一长连接池被借空。解决如果项目用的是 Spring Boot HikariCP确保配置为spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 5 connection-timeout: 30000如果源码是 SSM 里的 C3P0我建议直接换掉C3P0 在并发稍微上来后容易出现连接清理不及时。排查泄漏可以先看数据库 side 的 show processlist看有没有大量 Sleep 状态的连接。代码层面重点查所有自定义 JDBC 工具把 Connection、PreparedStatement、ResultSet 都用 try-with-resources 关闭。4.2 静态资源 404页面有内容但样式全乱现象启动后能访问登录页HTML 骨架正常但所有 CSS、JS、图片都 404控制台一片红色。原因这套组件里最常见的就是拦截器把 /static/** 也拦截了或者 Spring Boot 没找到静态资源目录。另一个可能项目是 JSP 方式打成 war 包部署到外部 Tomcat静态资源放在 src/main/webapp/static 下但 application.yml 里的 spring.mvc.static-path-pattern 被改成自定义值导致默认映射失效。解决先看控制台有没有“No mapping for GET /static/css/common.css”之类日志。然后在 WebConfig 里确认 excludePathPatterns 包含了所有静态路径。如果用了自定义静态路径要显式加资源映射Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/static/**) .addResourceLocations(classpath:/static/); } }注意 Tomcat 部署 war 时资源映射路径要确认 classpath 正确。另外如果 JSP 页面里引用的是相对路径 ../static/css还要注意页面请求 URL 的层级Chrome Network 里点一下请求路径就能判断是映射问题还是路径问题。4.3 MyBatis 报 Invalid bound statement接口刚调用就炸现象启动不报错一旦调用某个 Mapper 方法就抛 BindingException: Invalid bound statement (not found): com.example.club.mapper.ActivityMapper.selectById。原因Mapper 接口和 XML 没有绑定成功。常见情况有三种XML 文件的 namespace 不是接口全限定名XML 没有放在 mapper-locations 扫描路径下或者接口方法名在 XML 里的 id 不一致。有时还会遇到多模块项目里 XML 没被 Maven 打进 package。解决打开 Mapper 接口按住 Ctrl 点进去看 XML 是否存在。然后用文本编辑器核对第一行mapper namespacecom.example.club.mapper.ActivityMapper再检查 application.ymlmybatis: mapper-locations: classpath:mapper/*.xml这里 classpath:mapper/*.xml 是相对 resources 根目录的写法如果 XML 实际放在 src/main/java 下而不是 resources 下编译后不会出现在 classes 里必须把 XML 移到 resources/mapper。另外如果项目里用了 MyBatis-Plus还要注意 Mapper 接口上是否加了 Mapper 注解或者在启动类加了 MapperScan扫描路径写错也会报相同错误。4.4 中文乱码数据库、请求、响应三处不一致现象表单提交“迎新晚会”后库里存的是“”或者页面上展示出来一半乱码。原因三个环节至少有一个用了非 UTF-8。最常见的是 JDBC 连接 URL 没指定 characterEncoding其次是 MySQL 表字符集不是 utf8mb4然后是 JSP 页面编码或者 Tomcat 的 URI 编码不对。如果是 POST 请求Tomcat 8 以后默认 UTF-8但老项目里可能有 CharacterEncodingFilter 被重复配置覆盖。解决在 application.yml 里把连接 URL 写完整url: jdbc:mysql://127.0.0.1:3306/club_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiMySQL 8 还要注意 driver-class-name 要用 com.mysql.cj.jdbc.Driver。表字符集用 SHOW CREATE TABLE 检查需要改就执行 ALTER TABLExxxCONVERT TO CHARACTER SET utf8mb4。如果页面是 JSP检查 pageEncoding 是否 UTF-8如果部署在外部 Tomcat在 server.xml 的 Connector 里加上 URIEncodingUTF-8。排查时依次检查请求日志、数据库存储、响应头 Content-Type就能定位乱码发生在哪一段。4.5 报名名额超卖一个活动报了 120 人现象max_people 设 100最终报名记录 120 条甚至同一用户有多条报名记录。原因代码用了“先 select count 再判断”的逻辑在高并发下多个请求同时读到 99然后一起 insert。另一个原因是报名表没有联合唯一索引重复提交也不会报错。解决先给 activity_signup 表加唯一索引ALTER TABLE activity_signup ADD UNIQUE KEY uk_activity_user (activity_id, user_id);再把报名方法改成第 3 章里的原子扣减方式using update activity set remain_count remain_count - 1 where remain_count 0。这两个改动一起做基本能杜绝超卖。做完后模拟并发用 IDEA 的 HTTP 脚本或 JMeter 发 50 个并发请求观察最终报名记录数与 remain_count 之和是否等于 max_people。如果不一致几乎可以确定是代码里还有别的地方直接改了 remain_count。5. 把源码部署到服务器Maven 打包、Tomcat 上线和验证命令5.1 环境准备先统一 JDK、Maven、MySQL 的版本搭配源码能本地跑不代表能部署到服务器。最常见的问题是版本不一致导致的玄学错误。我一般用下面这套组合JDK 8 或 11Maven 3.6 以上MySQL 5.7 或 8.0Tomcat 9。如果你的源码是 Spring Boot 2.xJDK 8 足够如果是 Spring Boot 3.x那必须 JDK 17。看到这里你可能想骂“版本匹配怎么又是玄学”其实规则很简单看 pom.xml 里 spring-boot-starter-parent 的版本号2.x 用 JDK83.x 用 JDK17。不要盲目装最新版 JDK不然启动时直接报 UnsupportedClassVersionError。部署前先检查这三条命令的输出java -version mvn -version mysql --version我整理了一个版本要求表按这个配能省掉一半启动问题组件建议版本说明JDK1.8 或 11对应 Spring Boot 2.x3.x 用 17Maven3.63.8 对仓库镜像配置更严格MySQL5.7 / 8.08.0 驱动名变化见 5.2Tomcat9.0.x如果打 war 包部署才需要5.2 修改 application.yml 和初始化数据库拿到源码后先把数据源改成你本机的库名、账号和密码。不要用 root 空密码跑生产最少也要设一个专用账号。下面是 Spring Boot 2.x 常见的配置注意 MySQL 8 的驱动名和时区server: port: 8080 servlet: context-path: / session: timeout: 30m spring: datasource: url: jdbc:mysql://127.0.0.1:3306/club_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: club_admin password: 换成你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.club.entity逻辑说明context-path 会影响所有接口前缀如果留空就是根路径。driver-class-name 在 MySQL 5.7 用 com.mysql.jdbc.Driver8.0 用 com.mysql.cj.jdbc.Driver。用错会报 ClassNotFound。然后初始化数据库在 MySQL 里执行源码带的 .sql 文件mysql -uclub_admin -p club_db club.sql执行完用 SHOW tables; 确认四张核心表都存在。不要跳过这步很多用户直接改完配置就跑结果启动时 Hibernate 或 MyBatis 建表失败再回来查才发现少执行了初始化脚本。5.3 Maven 打包、war 部署和 curl 验证配置改好后开始打包。在项目根目录执行mvn clean package -DskipTests参数说明clean 清掉旧文件package 打包-DskipTests 跳过测试因为社团网站源码里的测试往往依赖本地数据库跑不过会打断打包。打包成功后 target/ 目录下会生成 war 或 jar。如果是 war把它复制到 Tomcat 的 webapps 目录cp target/club-website.war /opt/tomcat/webapps/ /opt/tomcat/bin/startup.shTomcat 启动时会自动解压 war。注意war 包的文件名就是上下文路径比如 club-website.war 对应的访问前缀是 http://ip:8080/club-website/。如果不想要前缀把 war 命名为 ROOT.war。这两者的区别会让前端 JS 请求接口路径差出 /club-website很多人部署完页面 404 就是这里没对应上。启动后不要急着点页面先用 curl 做接口冒烟测试# 1. 检查登录页是否返回 200 curl -I http://127.0.0.1:8080/club-website/login # 2. 登录并保存 Cookie curl -c cookies.txt -X POST http://127.0.0.1:8080/club-website/api/user/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} # 3. 携带 Cookie 发布活动 curl -b cookies.txt -X POST http://127.0.0.1:8080/club-website/activity/publish \ -d title迎新晚会content第一次迎新location礼堂startTime2025-09-01 19:00:00endTime2025-09-01 21:00:00maxPeople100这里用 -c 保存 Cookie-b 发送 Cookie。如果登录接口要求 JSON注意 -d 的 Content-Type如果要求 form-urlencoded不用加 -H 也行。curl 返回的 JSON 里如果包含 code200 且 data 里有 JSESSIONID说明登录通过。发布活动返回成功后再去数据库查一条记录确认写入字段都正确。这一步能挡住 80% 的接口路径问题。提示curl 的 -c 和 -b 只是最简单的 Cookie 处理方式如果换到 HTTPS 环境还要注意证书参数或者直接用浏览器开发者工具验证。如果你用的是 Spring Boot 内置 Tomcat 打成 jar部署命令更简单nohup java -jar club-website.jar --server.port8080 即可。但要注意 jar 包内静态资源和上传文件路径的处理方式不同第 3 章的 addResourceHandlers 配置要对应实际路径。6. 给社团活动网站加一层慢查询监控用 MySQL 慢日志和 Spring AOP 找到最耗时的接口网站上线之后最折磨人的不是功能报错而是“感觉变慢了”却不知道慢在哪。我习惯在上线第二周做一次性能体检第一件事就是开 MySQL 慢查询日志。登录服务器执行SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; SET GLOBAL slow_query_log_file /var/log/mysql/slow.log;这个方案不用重启数据库也不用改动源码跑一天后看 slow.log凡是超过 2 秒的 SQL 都会在里面。注意 SET GLOBAL 在 MySQL 重启后会失效生产环境记得把这三行写进 my.cnf 的 [mysqld] 配置段。慢日志只能告诉我们 SQL 有问题如果要定位到具体接口我还会在 Controller 层加一个 AOP 切面统计每个接口的耗时Aspect Component public class TimeLogAspect { Around(execution(* com.example.club.controller.*.*(..))) public Object logTime(ProceedingJoinPoint pjp) throws Throwable { long start System.currentTimeMillis(); Object result pjp.proceed(); long cost System.currentTimeMillis() - start; if (cost 500) { System.out.println(pjp.getSignature().toShortString() 耗时 cost ms); } return result; } }这个切面的关键是 Around 和 ProceedingJoinPoint。pjp.proceed() 放行原方法前后分别计时。超过 500ms 的打印到应用日志里。配合慢 SQL 日志就能把“活动列表页慢”和“一条 select * from activity where ... limit 10 执行了 3 秒”对应起来。我吃过一次亏某次帮人调的社团网站用户反馈活动列表越来越慢我一开始怀疑数据库问题结果慢日志里没有一条 SQL后来加了 AOP 切面才发现接口每天定时生成 Excel 统计报表一次性查出全表数据还做了 BigInteger 拼接。AOP 日志把这个 8 秒的耗时暴露出来后改成异步生成文件才解决。这个教训让我现在上线任何 Java Web 项目第一周就加上慢 SQL 和接口耗时监控不要等用户来截图才想起来。希望帮到你。本文还有配套的精品资源点击获取