毕业设计避坑指南:用SpringBoot和Vue做活动管理系统,我踩过的这些坑你别再踩了
毕业设计避坑指南:SpringBoot+Vue校园活动管理系统实战复盘
去年这个时候,我和屏幕前的你一样,正在为毕业设计焦头烂额。选择了校园活动管理系统这个题目,本以为是个"标准答案",没想到从技术选型到部署上线,踩的坑一个比一个深。今天就把这些血泪教训整理成这份避坑指南,希望能帮你少走弯路。
1. 技术栈选择与项目初始化
很多同学拿到题目第一反应就是找现成源码,但我的建议是:先花两天时间做好技术验证。当初我直接clone了一个GitHub项目就开始改,结果发现SpringBoot 2.3.x与Vue 2.6的兼容性问题,浪费了一周时间。
1.1 版本匹配的黄金组合
经过多次测试,推荐以下稳定组合:
-
后端 :
- SpringBoot 2.7.18(LTS版本)
- JDK 17(注意学校机房可能只装JDK8)
- MyBatis-Plus 3.5.3(比JPA更易上手)
-
前端 :
- Vue 2.7(兼容2.x生态)
- Element UI 2.15.12(管理后台首选)
- Axios 1.3.5(注意拦截器配置)
提示:用
vue create初始化项目时,务必勾选Router和Vuex,后期再加会非常麻烦。
1.2 数据库设计的三个致命错误
我的初版数据库设计被导师打了回来,主要问题集中在:
- 社团活动表 没有记录修改历史
- 报名表 缺少乐观锁字段
- 所有表都用varchar(255)糊弄
改进后的核心表结构:
| 表名 | 关键字段 | 设计要点 |
|---|---|---|
| activity | id, title, start_time, end_time, status(1-未开始 2-进行中 3-已结束) | 用datetime存储时间 |
| activity_apply | user_id, activity_id, apply_time, status | 添加version字段 |
| operation_log | operator_id, operation_type, before_state, after_state | JSON格式存储变更 |
-- 建表示例
CREATE TABLE `activity` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL COMMENT '活动名称',
`max_participants` int DEFAULT '100',
`start_time` datetime NOT NULL,
`status` tinyint DEFAULT '1' COMMENT '1-未开始 2-进行中 3-已结束',
`version` int DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2. 前后端联调的血泪史
跨域问题耗掉了我72小时,最后发现是Cookie惹的祸。分享几个必坑姿势:
2.1 跨域配置的终极方案
不要直接用 @CrossOrigin 注解!完整的解决方案应该是:
后端配置 :
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowCredentials(true)
.allowedMethods("*")
.maxAge(3600);
}
}
前端axios配置 :
// 在main.js中
axios.defaults.withCredentials = true
axios.interceptors.request.use(config => {
config.headers['Content-Type'] = 'application/json'
return config
})
2.2 接口规范的五个原则
- 永远返回统一响应体:
public class Result<T> {
private Integer code;
private String msg;
private T data;
// 省略getter/setter
}
-
状态码不要用200/500这种,定义业务码:
- 1000~1999 用户相关
- 2000~2999 活动相关
- 3000~3999 系统异常
-
分页查询统一参数:
- pageNum
- pageSize
- orderBy
-
日期字段统一传时间戳
-
删除用逻辑删除(is_deleted字段)
3. 权限控制的坑点指南
RBAC模型听起来简单,但实际开发时我遇到了:
3.1 权限表设计陷阱
我的第一版设计:
(此处原为mermaid图,按规范已删除)
问题出在角色-权限是多对多关系,但我在代码里用List存储,导致N+1查询。改进方案:
- 使用MyBatis-Plus的@TableField(exist = false)
- 缓存权限树
- 前端路由动态生成
3.2 Spring Security的正确打开方式
不要直接抄网上的配置!特别要注意:
- 密码加密必须用BCrypt
- 拦截器要放行Swagger路径
- 记住我功能要设置token有效期
核心配置示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/swagger**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.successHandler(new MyAuthSuccessHandler())
.and()
.rememberMe()
.tokenValiditySeconds(86400)
.key("myRememberKey");
}
}
4. 部署上线的那些坑
答辩前一天系统崩了,因为服务器内存溢出。分享几个救命经验:
4.1 生产环境必备配置
- Nginx配置 :
server {
listen 80;
server_name yourdomain.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
}
}
- SpringBoot启动参数 :
nohup java -jar \
-Dserver.port=8080 \
-Dspring.profiles.active=prod \
-Xms512m -Xmx1024m \
-XX:+HeapDumpOnOutOfMemoryError \
your-app.jar > log.out 2>&1 &
4.2 监控与日志
-
必备监控项:
- 接口响应时间(>500ms要报警)
- JVM内存使用率
- 数据库连接数
-
日志收集方案:
- 用Logback替代Log4j
- 按天归档日志
- 敏感信息脱敏
<!-- logback-spring.xml示例 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
5. 答辩加分项实操
最后分享几个让答辩老师眼前一亮的技巧:
-
压力测试报告 :
- 用JMeter模拟100并发
- 重点测试报名接口
- 准备SQL优化方案
-
系统亮点设计 :
- 活动冲突检测(时间+地点)
- 报名人数动态限制
- 二维码签到功能
-
代码质量保障 :
- SonarQube扫描报告
- 单元测试覆盖率(至少60%)
- API文档(Swagger+Markdown双版本)
// 活动冲突检测示例
public void checkActivityConflict(Activity newActivity) {
List<Activity> conflicts = activityMapper.selectList(new LambdaQueryWrapper<Activity>()
.eq(Activity::getLocation, newActivity.getLocation())
.lt(Activity::getEndTime, newActivity.getStartTime())
.gt(Activity::getStartTime, newActivity.getEndTime()));
if (!conflicts.isEmpty()) {
throw new BusinessException("该场地在指定时间段已被占用");
}
}
记得在演示时故意触发几个异常处理流程,这比顺利运行更能体现你的系统健壮性。我的答辩现场就因为展示了完整的报错处理流程,最终拿到了优秀。
更多推荐

所有评论(0)