
简介基于Spring Boot与Vue的校园社团管理系统是一套完整的高分毕业设计源码项目内含数据库脚本。系统采用前后端分离架构实现社团创建与注销、成员管理、活动发布与报名、审批流程、分类管理等典型业务适合计算机相关专业学生作为毕业设计或课程大作业的参考范本也可供初级开发人员学习主流框架整合与实战开发。压缩包内共223个文件其中包含55个Java源码文件、56个编译后的class文件、15个Vue组件、11个xml配置、7个js脚本、SQL数据库脚本以及yml、properties、jar依赖等目录结构清晰便于在IDE中导入运行并快速上手。资源包大小约27.49MB已有192人学习下载。源码均经过本地编译验证评审得分达95分以上系统逻辑完整、难度适中能帮助读者深入理解Spring Boot后端接口编写、Vue前端交互设计、数据库表设计及前后端联调思路同时节省自行搭建基础框架与准备数据的时间。1. 从选型到落地Springboot Vue 校园社团管理系统在毕设里到底省了多少事“校园社团管理系统”听起来是所有管理系统里最稳的选题可真正动手的人都知道增删改查只是表皮成员审批、活动报名、社团状态、角色权限才是真正让你熬夜的地方。Springboot 把接口层和服务层收拢成一条稳定链路Vue 负责把页面和路由拆成三个用户端数据库再兜住重复报名和外键关系——三者叠加恰好能把这类系统做成一棵“改一处不会崩一片”的树。这套方案尤其适合正在做毕设的学生不用从零设计权限模型也不用自己造轮子处理跨域把源码和数据库导入后按下面步骤能在一小时内跑起来再按模块去改。对想拿高分的人来说重点不是跑通而是清楚每个逻辑为什么这样写。2. 先定模块再写代码从业务拆解到 Springboot Vue 的数据表设计2.1 三端用户与功能边界管理员、社长、普通社员各管什么很多毕设一上来就写 user 表然后把所有字段塞进去。真正常见的做法是先按用户端把功能切开。管理员端负责全局配置社团审核、账号启停、公告发布、数据统计。社长端负责自己社团的日常运转更新社团介绍、审批新成员、创建活动、查看报名列表。普通用户端只关心三件事找社团、加入社团、报名活动。三端功能可以简单列成一张表用户端核心页面典型接口关键状态管理员社团审核、统计 Dashboard社团审核通过/驳回、禁用账号社团状态 1 正常 2 停用社长成员管理、活动管理创建活动、审批入社申请成员状态 0 待审 1 通过 2 拒绝普通用户社团广场、活动列表、我的报名申请入社、报名活动、取消报名报名状态 0 已报名 1 已取消为什么要按这三端而不是把页面混在一起因为 Springboot 设置角色依赖时只有把接口按角色分组才能用拦截器做“社长只能操作 club_id 等于自己社团”的校验。Vue 这边也能用路由元信息控制菜单显隐。这样后面无论是加功能还是答辩讲权限都不至于把代码改成一团。2.2 Springboot 后端分层与 Vue 前端目录Controller-Service-Mapper 怎么和组件对上选 Springboot 的理由是约定大于配置。一个接口写下来Controller 只做参数接收Service 写业务规则Mapper 管 SQL。Vue 组件则按视图和 API 分离views 里放页面api 目录放 axios 请求router 做路由。我一般会把后端的包结构组织成下面这样src/main/java/com/example/club/ ├── controller # 接口入口只接收参数和返回 Result ├── service # 业务规则例如报名时间判断、重复报名判断 ├── mapper # MyBatis 数据访问写 SQL 或注解 ├── entity # 数据库表对应的实体类 ├── interceptor # 登录/权限拦截器 └── common # Result、异常、工具类Controller 不直接操作 JdbcTemplateMapper 不写业务 if。为什么一定要这样分第一活动报名、社团审核这种多步骤操作如果揉在一个 Controller 里出错时定位太慢第二答辩时面试官常问“这个报名的并发你怎么处理”你能直接指出校验在 Service 层、兜底在数据库层这是加分点。前端目录我一般拆成src/ ├── api/ # 每个模块一个 js封装 axios 请求 ├── router/index.js # 路由和导航守卫 ├── views/ │ ├── admin/ # 管理端页面 │ ├── manager/ # 社长端页面 │ └── user/ # 普通用户页面 ├── components/ # 表单、弹窗、分页等复用组件 └── store/ # 保存登录用户信息、当前社团信息这样划分的理由是前后端通过接口契约对齐。后端 service 里有什么方法前端 api 目录基本上就有什么函数后端 Controller 的路径改变只需要改一个 api 文件不会牵扯到十几个页面组件。很多同学拿到源码后喜欢在组件里直接写 axios当时方便等后端调整路径就要全局搜索非常容易漏。2.3 核心数据表设计与 SQL 示例社团表、成员表、活动表、报名表怎么建数据库是整个系统的底盘。如果表结构错了后面所有统计和联调都会受影响。我现在拿起一份社团管理源码一定先找这几个表user、club、club_member、activity外加 activity_signup。它们的建表 SQL 大体是下面这个样子。CREATE DATABASE IF NOT EXISTS club_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE club_system; CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 登录密码BCrypt 加密, nickname varchar(50) DEFAULT NULL, role tinyint NOT NULL DEFAULT 3 COMMENT 1 管理员 2 社长 3 普通用户, club_id bigint DEFAULT NULL COMMENT 社长绑定的社团, create_time datetime DEFAULT CURRENT_TIMESTAMP, deleted tinyint NOT NULL DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB COMMENT用户表;user 表的核心点是 username 加唯一索引防止注册重名password 字段留 100 长度是给加密串留余量不要用 varchar(32)。CREATE TABLE club ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, intro text, owner_user_id bigint DEFAULT NULL, status tinyint NOT NULL DEFAULT 0 COMMENT 0 待审核 1 正常 2 停用, max_members int DEFAULT 50, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner_user_id (owner_user_id) ) ENGINEInnoDB COMMENT社团表;这里容易踩的坑是给社团名加唯一索引导致同一个学校不允许重名社团实际需求往往允许同名不同社团。所以一般只建普通索引。CREATE TABLE club_member ( id bigint NOT NULL AUTO_INCREMENT, club_id bigint NOT NULL, user_id bigint NOT NULL, status tinyint NOT NULL DEFAULT 0 COMMENT 0 待审核 1 已通过 2 已拒绝, apply_time datetime DEFAULT CURRENT_TIMESTAMP, pass_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_club_user (club_id, user_id), KEY idx_user_id (user_id) ) ENGINEInnoDB COMMENT社团成员表;联合唯一索引 uk_club_user 是防重复申请的关键。Service 层检查再多数据库这条索引也能兜底。CREATE TABLE activity ( id bigint NOT NULL AUTO_INCREMENT, club_id bigint NOT NULL, title varchar(100) NOT NULL, content text, max_people int DEFAULT 20, apply_start datetime NOT NULL, apply_end datetime NOT NULL, status tinyint DEFAULT 0 COMMENT 0 未开始 1 报名中 2 已结束, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_club_activity (club_id, status) ) ENGINEInnoDB COMMENT活动表; CREATE TABLE activity_signup ( id bigint NOT NULL AUTO_INCREMENT, activity_id bigint NOT NULL, user_id bigint NOT NULL, signup_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_activity_user (activity_id, user_id) ) ENGINEInnoDB COMMENT活动报名表;整套 SQL 的关键参数我解释一遍所有主键都坚持 bigint 自增避免在主键上做大量字符串比较逻辑删除字段保留数据方便统计人数时间字段统一用 datetime避免 timestamp 的时区和 2038 年问题。报名表同样加联合唯一索引保证一个用户对一次活动只有一条报名记录这是能不能拿高分的重要细节。3. 跑通前后台联调把源码数据库装到本地后的完整命令与配置3.1 环境版本选型JDK、Maven、Node、MySQL 用什么版本最稳拿到源码和数据库文件后不要急着双击启动。先看依赖再定环境。Springboot 毕设项目最常见的有两种组合Spring Boot 2.7.x JDK 8 MyBatis Vue2Spring Boot 3.x JDK 17 Vue3。这两个组合的包名有差异Spring Boot 3 里 javax 变成 jakarta直接决定你能不能编译通过。我一向建议新手先用下表这套版本组合组件版本建议原因JDK1.8 或 11兼容 Spring Boot 2.xMaven3.6.x 或 3.8.x对老依赖解析稳定MySQL5.7 或 8.05.7 还原度高8.0 更现代Node.js16 或 18Vue2 配 16Vue3 配 18npm随 Node避免 npm 8 以上触发 ERESOLVE注意springboot版本太高是最常见的失败来源。如果项目的 pom.xml 里写的是 3.xJDK 还是 8那编译阶段就会报 “package javax.servlet does not exist”。这时候不要硬改代码直接把 Spring Boot 版本降回 2.7.x或者换成 JDK 17。用命令行检查版本java -version mvn -version node -v npm -v mysql --version每装完一个就执行一下别等到启动报错再回头查。3.2 导入数据库mysql 命令执行 SQL 文件与关键参数拿到手的数据库文件通常是 club_system.sql。我一般先建库再导入避免文件里没有 CREATE DATABASE。mysql -uroot -p --default-character-setutf8mb4 \ -e CREATE DATABASE IF NOT EXISTS club_system CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci mysql -uroot -p --default-character-setutf8mb4 club_system club_system.sql这里有两个参数必须注意。--default-character-setutf8mb4 是让客户端按 utf8mb4 解释 SQL 文件否则中文注释和初始数据会成为乱码。另一个是 MySQL 8 默认认证插件 caching_sha2_password老项目用的驱动版本如果太老会报 Public Key Retrieval 错误解决方式是连接串加 allowPublicKeyRetrievaltrue。导入完成后验证一下SHOW TABLES; SELECT COUNT(*) FROM club;如果 club 表能查出来几条数据说明数据库文件没问题。如果导入时报 Unknown database说明手动建库失败回到建库命令检查权限。3.3 启动后端修改 application.yml 并用 Maven 打包运行数据库导好之后第一个要改的文件是后端的 application.yml。它的作用是把 Springboot 项目和数据库、MyBatis、端口连接起来。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/club_system?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true logging: level: com.example.club.mapper: debug重点参数是 url 里的 serverTimezoneAsia/Shanghai。如果漏掉很可能在启动时报时区无法识别或者时间字段差 8 小时。map-underscore-to-camel-case 让数据库的 create_time 自动映射到实体的 createTime省去一堆 ResultMap。配置好后执行cd backend mvn clean package -DskipTests java -jar target/club-system-0.0.1-SNAPSHOT.jar-DskipTests 跳过单元测试是为了不让测试用例拖慢启动。启动成功的标志是控制台出现 “Started ClubSystemApplication in x seconds” 或 “Tomcat started on port(s): 8080”。如果端口被占用用lsof -i:8080或 Windows 下netstat -ano | findstr 8080找到占用进程杀掉或改 server.port。改端口以后记得同步改前端代理。3.4 启动前端npm install 与 npm run serve配置代理解决跨域Vue 项目启动前先装依赖。建议把 node_modules 删掉重新装比保留旧依赖更干净。cd frontend npm install npm run serve如果 npm install 报 ERESOLVE 错误多半是因为 Node 版本和依赖库的 peerDependencies 冲突稳妥做法是npm install --legacy-peer-deps npm run serve启动后浏览器打开 http://localhost:8081。真正决定前端能不能调到后端接口的是 vue.config.js 里的代理。毕设系统我不建议在后端写一堆 CORS 配置直接让前端开发服务器代理转发更省事。// vue.config.js const { defineConfig } require(vue/cli-service) module.exports defineConfig({ devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })浏览器同源策略会拦截 8081 到 8080 的跨域请求而 devServer 的 proxy 把 /api 开头的请求转发到后端浏览器看到的还是同一个源。要注意的是前端 axios 的 baseURL 必须写成 /api后端 Controller 的 RequestMapping 也必须带 /api 前缀否则代理匹配不上。到这里前后端应该已经能连起来。翻车率最高的环节也集中在这个阶段下面单独用一整章写避坑。4. 核心业务实现细节从登录鉴权到活动报名的完整数据流4.1 登录鉴权为什么 Springboot 里用 JWT 而不是维护 Session在前后端分离的项目里Session 方式要求浏览器维护 Cookie后端集群还要配 Session 共享对毕设来说成本过高。JWT 是更常见的做法用户登录成功后后端签发一个带过期时间的 token前端每次请求放到 Authorization 头里后端用拦截器解析 token 拿到用户身份。一个最基础的 JWT 工具类长这样Component public class JwtUtil { Value(${jwt.secret}) private String secret; public String createToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Long getUserId(String token) { return Long.valueOf(Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getSubject()); } }.claim(role, role) 把角色塞进 token后端拦截器就能直接判断接口权限过期时间 24 小时对社团管理这种低频系统足够也可以按需改成 12 小时。secret 必须和项目 application.yml 里的 jwt.secret 一致否则每次重启后旧 token 都会失效。拦截器里再放一个 HandlerInterceptor从 request header 取出 token 并解析解析失败直接返回 401不进入 Controller。这样所有需要登录的接口只要配置拦截路径即可不用每个方法都重复写鉴权。4.2 创建活动接口Controller 接收参数 → Service 校验 → Mapper 写入活动创建是社长端最核心的操作。这里要展示的是分层怎么写才不会乱。Controller 层只做参数接收和返回PostMapping(/api/activity) public Result createActivity(RequestBody ActivityDTO dto, RequestAttribute(userId) Long userId) { Activity activity new Activity(); activity.setClubId(dto.getClubId()); activity.setTitle(dto.getTitle()); activity.setContent(dto.getContent()); activity.setMaxPeople(dto.getMaxPeople()); activity.setApplyStart(dto.getApplyStart()); activity.setApplyEnd(dto.getApplyEnd()); activityService.create(activity, userId); return Result.success(); }RequestAttribute(userId) 是拦截器解析 JWT 后塞进去的用户 id用来校验当前用户是不是该社团社长。Service 层做真正的业务判断public void create(Activity activity, Long userId) { ClubMember member clubMemberMapper.findByClubAndUser(activity.getClubId(), userId); if (member null || member.getStatus() ! 1) { throw new BusinessException(只有社团成员才能创建活动); } if (activity.getApplyEnd().isBefore(LocalDateTime.now())) { throw new BusinessException(报名截止时间必须晚于当前时间); } if (activity.getMaxPeople() null || activity.getMaxPeople() 0) { throw new BusinessException(活动人数必须大于 0); } activityMapper.insert(activity); }把校验放进 Service 而不是 Controller一方面是因为 Controller 不该出现业务 if else另一方面是复用以后如果加了活动导入功能同样要调 service.create规则不会被绕过去。max_people 的判断放在数据库写入前防止无意义的数据落进 activity 表。4.3 报名活动的并发与状态校验避免一人重复报名活动报名的完整逻辑包含三个判断活动存在、时间窗口、是否已满。但真正决定系统能不能扛住并发的是数据库的联合唯一索引。Transactional public void signUp(Long activityId, Long userId) { Activity activity activityMapper.selectById(activityId); if (activity null) { throw new BusinessException(活动不存在); } LocalDateTime now LocalDateTime.now(); if (now.isBefore(activity.getApplyStart()) || now.isAfter(activity.getApplyEnd())) { throw new BusinessException(当前不在报名时间内); } int already activitySignupMapper.countByActivityAndUser(activityId, userId); if (already 0) { throw new BusinessException(你已报名该活动请勿重复报名); } int signedCount activitySignupMapper.countByActivity(activityId); if (signedCount activity.getMaxPeople()) { throw new BusinessException(报名人数已满); } activitySignupMapper.insert(activityId, userId); }Transactional 保证 insert 和 count 之间发生异常时能够回滚。在并发场景下两个请求同时通过 count 判断都没满然后同时 insert数据库的 uk_activity_user 唯一索引会拒绝第二个从而保证不会产生重复记录。有人会问既然已经查了一次 count为什么不直接在 SQL 里写 UPDATE activity SET sign_count sign_count 1 WHERE sign_count max_people那其实是另一种方案适合活动表加了 sign_count 字段的项目。但毕设用表关联统计人数逻辑更直观答辩时也容易讲明白。4.4 Vue 前端路由与请求封装调用后端接口时页面怎么配合前端要做的事情是把页面状态和后端接口一一对齐。我习惯把请求拆到 src/api 目录并且用 axios 实例统一注入 token。// src/utils/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config }) export default requestbaseURL 写成 /api 而不是完整地址因为我们在 vue.config.js 里配置了代理代码在开发和打包后都能保持一致。超时时间 10 秒避免页面无限转圈。路由守卫控制页面权限// src/router/index.js router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next(/login) } else if (to.meta.role to.meta.role ! store.state.user.role) { next(/home) } else { next() } })vue-router 的路由配置里可以附加 requireAuth 和 role管理员页面、社长页面、普通用户页面各标记不同角色。前端拦截只是让页面进不去真正的数据安全还是依赖后端拦截器这一点在答辩时最好说清楚。到这一步登录、创建活动、报名、路由权限都已经串起来。下面是这套系统里最常见的坑。5. 常见问题避坑社团管理系统从联调到部署的 5 个高频翻车点5.1 前端页面能开但接口全是 404先排查代理而不是改后台代码现象npm run serve 后登录页能打开点击登录Chrome Network 面板里请求 /api/login 返回 404有的报 405。原因绝大多数情况是前端发到 8081 的请求没有转发到后端。要么 vue.config.js 的 proxy target 写成了 8080 但后端其实起了 8081要么 axios 的 baseURL 没走 /api。如果是 404也可能是后端 Controller 路径没有 /api 前缀代理把请求转过去但匹配不到映射。解决先用 curl 直接测后端确认后端接口是否正常curl http://localhost:8080/actuator/health curl http://localhost:8080/api/activity?page1如果后端能返回 JSON问题就在前端代理。回到 vue.config.js 核对 target 端口和路径前缀把前端请求路径与后端 RequestMapping 对齐。我会再开 Network 面板看 Request Headers确认请求是发到 8081 还是已经被代理转发到 8080。这个自查动作 30 秒内能定位不用翻代码。5.2 数据库中文乱码从建库到连接串四处检查现象社团名、活动标题在页面显示成问号或者导入 SQL 后表内容全是 ??。原因建库时字符集用的是默认 latin1或者后台连接串没有带 characterEncodingutf8mb4。还有一种情况是 SQL 文件本身是 UTF-8但 mysql 客户端用系统默认编码去解释造成导入时数据已经损坏。解决先统一建库语句再改连接串。如果表已经建好用下面的命令把库和表字符集改过来ALTER DATABASE club_system CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE club_member CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;ALTER TABLE CONVERT TO 会连字段一起转只改表默认字符集不会改已有字段。检查表结构时也不要只看表要连库名、表名、连接串三处一起看否则下一次 insert 还会出错。5.3 Spring Boot 版本太高JDK 8 编译直接失败现象mvn clean package 报错 package javax.servlet does not exist或者启动类中的 swagger、springfox 依赖无法识别。原因Spring Boot 3.x 把基础包名从 javax 改成了 jakarta依赖要求 JDK 17。如果拿着 Spring Boot 3 的源码却还在用 JDK 8编译必然失败不是代码缺了一个 import 那么简单。解决最省事的是把 pom.xml 里 spring-boot-starter-parent 的版本降到 2.7.xparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent同时把代码里所有import javax.*保持原样因为 Spring Boot 2.x 用的就是 javax。如果你坚持留在 3.x就装 JDK 17并把 IDE 的编译级别改成 17。毕设场景下我不建议硬留在 3.x团队里其他成员未必有 JDK 17。5.4 前端 history 路由刷新 404打包放进 Spring Boot 变成白屏现象开发环境里从首页点进社团详情正常F5 刷新就 404打包成静态文件放进 Spring Boot 的 resources/static 后只有首页能开其他路由全是白屏或 404。原因vue-router 默认用 history 模式路由是浏览器端模拟的没有真实文件服务器收到 /clubs/3 会去找静态文件找不到就返回 404。Spring Boot 自带的静态资源处理器不会自动把未知路径回退到 index.html。解决最快是换成 hash 模式const router new VueRouter({ mode: hash, routes })改成 hash 后 URL 会带 #/clubs/3后端永远只认 # 前的内容刷新就不会失效。如果想保留 history 模式需要在 Spring Boot 里加一个路径回退成本不低。对毕设演示来说hash 模式完全够用。5.5 删除社团外键约束报错先清子表还是改表结构现象管理员删除一个社团后端抛 SQLIntegrityConstraintViolationException异常信息是 Cannot delete or update a parent row。原因activity 表和 club_member 表都用了外键引用 club 表默认外键规则是 RESTRICT只要子表还有数据父表就不让删。这种设计是保护数据不是 Bug。解决删除社团前先删除关联数据。Transactional public void deleteClub(Long clubId) { clubMemberMapper.deleteByClubId(clubId); activityMapper.deleteByClubId(clubId); clubMapper.deleteById(clubId); }这里的顺序非常关键必须先删子表再删父表。如果只改外键为 ON DELETE CASCADE数据库层面会级联删除但答辩时很容易被追问级联删除的后果所以代码里显式控制更稳妥。另外如果希望保留历史数据逻辑删除更符合需求把 status 改成停用而不是物理删除。6. 把系统变成高分毕设答辩前必做的三件事与验证方法6.1 补数据可视化统计各社团人数答辩演示时一个静态表格远不如一张 ECharts 柱状图有冲击力。后端加一个聚合统计GetMapping(/api/admin/club/statistics) public Result statistics() { ListMapString, Object list clubMemberMapper.countByClub(); return Result.success(list); }Mapper 里写 GROUP BY 查询前端用 ECharts 渲染柱状图和饼图。这个功能不复杂但能体现你掌握了聚合查询和数据可视化能力。6.2 用 curl 把核心链路串成测试脚本我自己的习惯是准备一个 shell 脚本登录、创建活动、报名、审批全部跑一遍每次改动后先跑脚本再点页面。例如TOKEN$(curl -s -X POST http://localhost:8080/api/login \ -H Content-Type: application/json \ -d {username:admin,password:123456} | jq -r .data.token) curl -s http://localhost:8080/api/activity?page1 -H Authorization: $TOKEN脚本能跑通说明核心链路稳定答辩现场不会因为手误冷场。6.3 数据库连接信息从配置文件剥离把密码硬编码在 application.yml 里项目一旦传给别人就容易泄露。改成环境变量更专业spring: datasource: password: ${DB_PASSWORD}启动时用DB_PASSWORDxxx java -jar club.jar注入。看似多了一步但部署和答辩导师都会认可这种安全性。最后说一句我见过太多毕设项目死在最后一周才前后端联调数据库乱码、端口冲突、外键报错全堆在一起。按上面的顺序把环境、代理、权限、删除关系逐项验证项目稳稳能跑。希望帮到你。本文还有配套的精品资源点击获取