又到了一年一度的毕业设计季节,社区里问得最多的就是"管理系统类题目有没有现成的""社团管理系统该怎么做"。这个题目之所以长盛不衰,是因为业务边界足够清晰、功能规模适中,单人开发完全能在两三个月内拿下,而且演示效果直观,答辩时有东西可讲。这篇博文就以"高校学生社团管理系统"为例,从选题思路、技术选型、数据库设计、核心功能实现,到部署答辩的完整链路,把我实际开发中的经验和踩过的坑一次讲清楚。
如果你正打算做类似方向(社团管理、学生组织管理、兴趣小组管理),或者已经选了这个题但还没想清楚架构设计,这篇文章可以作为你的参考底稿。内容里不会贴完整工程,但会把每一步的设计思路、关键表结构、核心接口逻辑和容易翻车的细节都拆开讲,让你拿到之后能根据自己的需求调整,而不是照搬一份代码然后答辩时一问三不知。
1. 毕设选题:为什么"社团管理系统"依然值得做
每年都有大量学生认为管理系统类题目太"老套",想搞点"新东西"。但现实是,毕设的核心评价标准从来不是技术新鲜度,而是"逻辑完整、能跑能演示、论文有东西可写"这三点。社团管理系统恰恰在这三点上都有天然优势。
1.1 这个题目的业务边界非常清晰
社团管理的核心业务无非就是几个闭环:社团的创建与注销、成员的申请与审核、活动的发布与报名、活动签到与总结、通知公告的推送、数据统计展示。每一条业务线都是学生在校园生活中真实接触过的场景,不需要额外调研行业需求,也不存在需求理解偏差的问题。
这意味着你在论文里描述需求分析时,可以写得非常扎实。比如"社团创建"这个功能,调研时就能明确三种参与者:发起人(学生)、审核人(团委或社联指导老师)、系统管理员。三者的流程和权限各不相同,只要把这种细节写进需求分析,论文的"可行性分析"和"功能需求"部分就有了非常充分的内容。
1.2 功能规模适中,单人可控
相比电商系统(订单、库存、支付、物流)、在线教育系统(课程、视频、直播、考试)这种动辄十几个模块的项目,社团管理系统的核心实体只有用户、社团、活动、公告这四个。每个实体的状态流转都有限,不会出现复杂的并发扣库存或支付对账问题。
我自己的开发节奏是做参考:数据库设计和接口定义用了一周,核心功能编码用了三周,剩下两周处理细节、美化页面、写论文和准备答辩。这个节奏对大多数在校生来说是可接受的。如果选一个过大的题目,前期设计阶段就会消耗大量精力,后期编码必然压缩,最后容易出现"功能全部是半成品"的状态。
1.3 三种典型开发者如何参考这个项目
| 读者类型 | 参考重点 | 不建议 |
|---|---|---|
| 后端基础较弱 | 重点关注数据库设计和接口逻辑,把完整工程跑通后再改功能 | 一上来就改架构、重新设计表 |
| 有Java Web基础但没接触过框架 | 重点看Spring Boot的约定优于配置思路、MyBatis的Mapper写法 | 纠结于"为什么不用SSH"这类历史问题 |
| 前端有一定经验 | 可以重点关注前后端分离的请求链路和权限拦截 | 把时间花在过度美化页面上 |
这里多说一句:毕设的系统管理类项目,技术上最忌讳的就是"堆砌"。把Spring Boot、Spring Security、MyBatis-Plus、Vue、Redis、RabbitMQ全塞进去,表面上显得很厉害,但答辩时任何一个环节被追问都可能翻车。我见过不少同学在项目里加了大量消息队列和分布式锁,最后被问了几个细节就答不上来。社团管理系统用常规技术栈完全够用,真正体现工作量的是你对自己设计逻辑的清晰度。
2. 技术选型的真实考量:别被"最新框架"绑架
技术选型这部分,我见过太多人走入两个极端:一是直接抄某培训机构的老项目,用了已经很多年没更新的框架组合;二是追求"简历好看",选了社区里评价很高的新框架,但自己根本不熟悉,出了问题没人问。这里分享我当时选型的思路,供你参考。
2.1 后端使用Spring Boot的核心理由
社团管理系统这种典型的CRUD加权限控制的业务,Spring Boot基本上是目前最合适的选择,没有之一。原因不是因为它新,而是因为它"约定优于配置"的特性,能让一个之前没做过完整项目的人把精力聚焦在业务逻辑上,而不是花时间配置各种XML和监听器。
我用的版本是Spring Boot 2.7.x,配合MyBatis-Plus做数据访问。选择MyBatis-Plus而不是原生MyBatis,单纯是为了减少重复的CRUD代码。用户表的增删改查、分页查询这些基础操作,MyBatis-Plus直接提供现成方法,能省下差不多三分之一的数据层代码量。
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency>提示:Spring Boot版本不要选太新的3.x,尤其在毕业设计阶段。3.x需要JDK 17,部分老师和实验室环境还在使用JDK 8,版本不兼容问题处理起来非常麻烦。2.7.x加上JDK 8是兼容性最好、资料最多的组合。
2.2 前端用服务端渲染还是前后端分离
这个决定要基于你个人的前端能力来做。我把两者进行了实际对比:
如果选服务端渲染(比如Spring Boot直接配Thymeleaf),优点是不用处理跨域问题,部署时一个jar包搞定,所有页面逻辑都在后端。缺点是页面交互能力有限,动态刷新体验一般。
如果选前后端分离(比如Vue + Element UI),优点是页面美观、交互流畅,答辩时视觉效果明显更好;缺点是开发量增加,需要处理跨域配置、接口联调、前端打包部署,而且对前端基础有一定要求。
我当时选的是Vue 2 + Element UI做后端管理界面,配合一个面向学生的移动端适配页面。这个组合最大的好处是Element UI的表格、表单、弹窗组件非常成熟,不需要自己写复杂的CSS交互,很快就能搭出看起来比较专业的后台界面。如果前端基础较弱,可以用Vue静态页面先实现界面,再联调接口,不要在一开始就去折腾Vue CLI、路由守卫这些进阶特性。
2.3 为什么我没有用Redis和权限框架
很多参考项目喜欢引入Redis做缓存,用Spring Security做权限管理。这两个技术本身非常好,但我斟酌之后都没有在这个项目中引入。
先说Redis。社团管理系统的数据量级,一个学校撑死几千个用户、上百个社团,数据库完全扛得住,引入Redis徒增复杂度。很多同学引入了Redis,代码里却没有几处实际用到缓存的地方,答辩时被问"为什么用Redis""缓存了什么数据""如何保证一致性",回答不上来反而减分。如果你的项目里确实有一些高频读写的场景(比如活动报名的热门列表),再考虑加Redis不迟。
再说权限框架。Spring Security的功能很强大,但配置复杂,学习曲线陡峭。对于这个项目,用一个自定义的拦截器加角色枚举,完全能实现所有权限要求。自定义方案的额外好处是,答辩时你可以非常清楚地把每个权限点讲明白,因为代码是你一行一行写的,而不是一个过滤器配置的。
3. 数据库设计:先想清楚"谁在用系统"再动手
我做这个项目最大的体会是:数据库设计决定了项目开发后半程的顺畅程度。如果你一上来就建表,建到一半发现某个业务逻辑绕不回去了,再回头改表结构就是噩梦。所以建议你先画一个角色流程图,想清楚每种角色能做什么操作,然后再设计表。
3.1 四种角色的权限边界
这个系统里一共有四种角色,分别是系统管理员、校团委或社联指导老师、社团管理员、普通学生会员。这四者的关系可以理解为:系统管理员管全局;指导老师负责审批社团创建和重要的活动报备;社团管理员负责自己社团的成员管理和活动发布;普通学生则是最基本的用户,可以浏览社团、报名活动。
权限设计上,我采用的是"URL级别拦截"加"数据级别归属校验"双重控制。URL级别拦截解决的是"这个角色能不能访问这个接口"的问题;数据级别校验解决的是"这条数据是不是属于你这个社团"的问题,避免社团A的管理员去修改社团B的信息。
3.2 核心表的拆分与字段设计
我设计的核心表一共有九张,把完整表结构贴在这里,可以直接参考。所有表的主键统一用自增id,创建时间和更新时间用统一的字段名,方便MyBatis-Plus自动填充。
第一张是用户表。除了基本的账号、密码、姓名、学号字段外,我加了role字段(区分角色)和avatar字段(用户头像)。密码存储直接使用的MD5加盐,虽然是基础做法,但毕设环境下足够说明你有安全意识。需要注意学号要加唯一索引,避免重复注册。
CREATE TABLE `sys_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录用户名', `password` varchar(100) NOT NULL COMMENT '密码(加密后)', `real_name` varchar(50) DEFAULT NULL COMMENT '姓名', `student_no` varchar(20) DEFAULT NULL COMMENT '学号', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `role` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1学生 2社团管理员 3指导老师 4系统管理员', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';第二张是社团表。字段包括社团名称、简介、logo、成立时间、所属类别、状态(正常、解散、待审核)。这里有一个容易忽略的点:社团表里不要直接存"团长ID"作为外键,因为社长是会换届的。更好的做法是单独建一张成员表,在成员表里通过role字段标记谁是社长。这样换届时只需要修改成员表的数据,社团表不用动。
第三张是社团成员表。字段包括用户id、社团id、角色(社长、副社长、干事、普通成员)、状态(待审核、通过、拒绝、退出)、入社时间。这张表是整个系统关联最密集的位置,查询"某个用户加入了哪些社团""某个社团有哪些成员"都需要通过它。记住在用户id和社团id上建联合唯一索引,防止一个人被同一个社团的申请记录重复创建。
活动相关需要拆成三张表:社团活动表、活动报名表、活动签到表。活动表记录活动标题、描述、开始时间、结束时间、地点、最大人数、状态。活动报名表记录哪个用户报名了哪个活动,状态是报名成功还是取消;签到表则在活动举办时记录实际到场人员。
公告表相对简单,字段是标题、正文、发布人id、发布社团id(可空,空表示校级公告)、置顶标记、发布时间。资源表可以记录一些社团的日常资源管理,但如果你觉得业务够讲,也可以不做。
3.3 表关系里最容易疏忽的状态流转
表结构设计完之后,一定要把每个实体的"状态流转图"写出来,这个是你答辩时的加分项。比如活动状态,我是这样设计的:草稿 → 待审核 → 已发布 → 报名中 → 已结束 → 已归档。在活动表的status字段里用数字枚举表示,代码里写一个常量类统一管理。
这里有个实际开发中很常见的问题:很多同学只在代码里用int类型表示状态,但没有注释枚举含义。过了几周自己再看代码,已经分不清2是代表"审核通过"还是"活动已结束"。所以强烈建议做一个常量类或者枚举类,把所有的状态码集中管理,并且配上注释。这不仅方便自己开发,论文里写数据字典时也可以直接复用。
社团状态相对简单:待审核、正常、已解散。但要注意一个业务逻辑:社团解散前,必须判断该社团下是否有正在进行中的活动,如果有,不允许解散。这个判断逻辑放后端接口里,前端按钮只做提示。类似的业务规则在答辩时都是很好的问答素材。
4. 功能模块拆解:每个核心模块我怎么落地
数据库模型定下来之后,剩下的工作主要是把各个业务闭环串起来。下面按模块把核心实现逻辑和关键代码思路展开讲。
4.1 登录认证与权限拦截的自研逻辑
登录接口其实很简单,根据用户名查出用户,比对密码加密结果,成功后把用户信息放到Session中。我这里没有用JWT,因为项目是单体应用,Session方式简单可靠。如果你用的是前后端分离,但又不想用复杂的Spring Security,用JWT加自定义拦截器也是可以的,逻辑本质上是一样的。
权限拦截是基于Spring MVC的拦截器实现的。我定义了一个AdminInterceptor,在preHandle方法里判断当前请求路径的URL前缀。比如请求路径以/admin/开头,则说明需要系统管理员或指导老师权限;以/club/开头,则必须登录;以/student/开头则普通学生登录即可。
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); SysUser user = (SysUser) session.getAttribute("currentUser"); String uri = request.getRequestURI(); if (uri.startsWith("/login") || uri.startsWith("/register")) { return true; } if (user == null) { // AJAX请求返回JSON提示,页面跳转会丢失请求目标 if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}"); } else { response.sendRedirect("/login"); } return false; } // 更细粒度的角色判断可以在这里补充 if (uri.startsWith("/admin/") && user.getRole() != SysUser.ROLE_ADMIN && user.getRole() != SysUser.ROLE_TEACHER) { return false; } return true; } }这里有一个细节容易出错:如果前端用Ajax发送请求,后端直接response.sendRedirect是无效的,因为浏览器拿到重定向响应后不会跳转页面。所以要对Ajax请求单独返回JSON状态码,前端判断code为401时再跳转登录页。这个错误当年我调试了很久,第一次做前后端分离的同学大概率都会在这里卡一下。
4.2 社团创建、加入、成员管理的完整闭环
学生创建社团的流程是这样的:学生填写社团名称、类别、简介,提交后生成一条"待审核"状态的社团记录,同时在社团成员表里插入一条"社长"身份的成员记录,状态为"通过"。
这里需要事务处理。创建社团和添加社长成员这两个操作必须放在同一个事务里,否则如果社团创建成功但社长成员插入失败,就出现一个没有管理员的"死社团"。使用@Transactional注解是最简单的方案,这个注解的失效场景是答辩时的高频问题,可以提前准备一下。
学生加入社团的流程是:申请提交后生成一条成员状态为"待审核"的记录。社团管理员在后台看到申请列表,点击通过或拒绝。通过时把该成员状态改为"通过",拒绝时改为"拒绝"。
这里有一个业务规则要考虑清楚:如果用户在同一个社团已经有一条"待审核"的记录,就不能再重复申请。这个规则在接口里通过联合唯一索引加业务判断双重保证。数据库唯一索引保证了数据层面不会出现两条相同记录,代码逻辑则保证用户能收到"你已经申请过该社团"的友好提示。两者缺一不可。
4.3 活动发布、报名、签到、归档的状态机
活动发布功能中,社团管理员可以创建活动草稿,编辑完成后提交审核。指导老师有权限看到所有待审核活动,通过后活动状态变为"报名中"。
活动报名的核心逻辑是校验三个条件:活动必须处于"报名中"状态;当前报名的用户不能已经报名;当前报名人数不能超过活动最大人数限制。这三个条件里,人数限制在多线程并发下会有超卖问题,但毕设阶段用MySQL的乐观锁思路处理即可,不需要引入复杂中间件。
我当时的方案很简单:在活动表中增加一个already_count字段,每次报名前先查询当前值,判断小于max_count后,在update语句里加上WHERE already_count < max_count的判断,更新时让already_count加一。这样即使极端并发下,数据库层面的原子更新也能挡住超额数据。这个方案可以在论文中写成一个完整的"并发报名问题解决"小节。
签到功能实现方式本质上是修改报名记录的状态字段。活动开始时,社团管理员扫一眼到场人员名单,手动勾选已签到人员,或者点击"全部签到"后单独取消未到场的人。要到微信扫码签到这种程度对于毕设来说属于过度设计,除非你有特殊需要,否则不建议。
活动结束后,管理员填写活动总结并归档,此时活动状态变为"已归档",所有报名记录和签到数据进入统计分析的范围。
4.4 数据统计:让图表说话,展示实现亮点
管理系统里如果只有表格,答辩时视觉体验非常平淡。我在这个项目里做的是社团数量统计、活动参与人数排行、各类型社团占比、月度活动数量趋势这四张图。数据来源都是现成的库表,用SQL查询之后聚合到前端,再用ECharts渲染。
比如活动参与人数排行,SQL就是一个简单的GROUP BY加SUM:
SELECT c.club_name, COUNT(a.id) AS join_count FROM club_activity a LEFT JOIN club c ON a.club_id = c.id WHERE a.status = 3 GROUP BY a.club_id ORDER BY join_count DESC LIMIT 10这类图表维度多、SQL简单,论文里也可以贴出实现思路。答辩时演示数据好看,老师对"系统是否完整"的印象分会高不少。但要注意:图表的数据不能是写死的静态数据,一定要是从数据库动态查出来的,否则一眼就看穿,反而得不偿失。
5. 最容易翻车的几个细节:实测踩坑记录
功能开发完成之后进入联调和部署阶段,这个阶段的坑往往比编码阶段更多。我把实际踩过的几个问题整理成一个排查表,方便你遇到同样问题时直接定位。
5.1 跨域、Session与拦截器顺序的三角关系
如果你用了前后端分离,Session方案天然会面对跨域问题。浏览器跨域请求默认不带Cookie,导致Session无法维持登录状态。当时我用一个很直接的方案:后端配置CORS允许携带凭证,前端Axios设置withCredentials: true,同时后端把Access-Control-Allow-Origin设置为具体域名,不能使用*。
这里有个比较容易忽略的点:如果你同时配置了CORS的WebMvcConfigurer和自定义Interceptor,需要理解拦截器先于CORS处理器执行。也就是说,如果某个请求被拦截器拦截后,你在拦截器里写CORS响应头是无效的,浏览器依然会因为缺少Access-Control-Allow-Origin而报跨域错误。我当时花了不少时间排查为什么拦截器返回的401提示前端接收不到。正确做法有几种:要么把CORS配置放在最前面的Filter中,由Filter先处理OPTIONS请求,要么在拦截器中对Origin单独设置允许的响应头。建议使用Spring的自带CORS配置配合OncePerRequestFilter处理,或者直接用JWT方案,把token放在Authorization头里,彻底绕开Cookie跨域问题。
5.2 文件上传的路径与访问映射
社团logo和活动图片的上传功能,如果没处理好,会出现"图片上传成功但访问404"的情况。这通常是因为你把文件存在了工程目录下的临时位置,项目重启后文件丢失,或者页面访问的URL和后端映射路径不一致。
我用的方案是:文件上传保存到服务器的自定义目录(比如项目根目录外的/files/upload/),然后通过一个资源配置类把/files/**映射到该目录。这样即使项目重新打包,上传的图片还在,不会消失。
@Configuration public class WebResourceConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String path = System.getProperty("user.dir") + "/files/"; registry.addResourceHandler("/files/**") .addResourceLocations("file:" + path); } }需要注意文件类型校验。只通过前端限制扩展名是不安全的,后端必须对上传文件做校验,最简单的做法是检查扩展名是否符合白名单,并限制文件大小(比如图片不超过5MB)。Spring Boot中通过spring.servlet.multipart.max-file-size设置大小上限,类型校验用FilenameUtils.getExtension判断即可。
5.3 分页查询的排序稳定性与总数统计
MyBatis-Plus的分页插件非常好用,但有一个细节:如果不指定排序字段,MySQL返回的结果顺序是不确定的。比如活动列表第一页和第二页之间可能出现重复数据。解决方法是任何一个分页查询都要明确ORDER BY字段,最稳妥的排序方式是ORDER BY id DESC或ORDER BY create_time DESC, id DESC。如果是按创建时间排序,建议加id作为次排序条件,因为时间戳如果精确到秒,多条记录可能相同。
还有一点是分页时查询总数的性能问题。数据量小的时候无所谓,但如果活动报名表数据量增长,每次翻页都统计总数会稍慢。这个系统里不用过度优化,只要在写SQL时确保where条件的字段有索引即可。MyBatis-Plus的分页插件默认会生成count查询,如果发现慢,可以在mapper的select结果上单独优化。
5.4 时间处理:数据库时区与北京时间
这个问题几乎每个项目都会遇到。部署的服务器如果设置的时区不是北京时间,插入create_time后页面显示的时间会和实际差八小时。解决方式是在数据库连接URL中明确指定serverTimezone:
jdbc:mysql://localhost:3306/club_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai同时Java侧的日期类型建议统一使用LocalDateTime,配合Jackson的序列化配置,在前端拿到的时间字符串格式固定,避免出现Timestamp和Date混用的解析问题。建议在系统启动时打印当前时间,确认服务器和数据库的时区一致,这个小动作可以省去后面的大量时间。
6. 从"能跑"到"能演示":准备数据和答辩的实用建议
系统完全能够运行时,不代表答辩就能顺利通过。我见过太多同学程序完美运行,但演示时数据太少,页面显得很空,老师扫了一眼就没得可问,最后项目分平平。合理的做法是提前准备一套完整的演示数据和一份演示脚本,把每一个功能节点都提前走一遍。
6.1 演示数据怎么造
演示数据不能随便插入几条。要让答辩现场看起来"像个真实在用的系统",需要按真实业务逻辑制造数据。比如创建五个不同类型的社团(文化类、体育类、学术类、艺术类、公益类),每个社团里加十到二十个成员,其中两三个有明确的干部身份;发布十个左右的活动,其中一部分已经报名多人,一部分活动处于报名中,另一部分已归档,这样每张页面都有数据可看。
造数据的时候建议用SQL脚本直接插入,保证数据可控、清理方便。如果手动在页面上点击生成,一方面耗时,另一方面数据质量不可控,容易出现错别字或时间点不对。可以直接用下面这种格式的脚本批量插入用户和成员关系,然后通过页面补充活动信息。
INSERT INTO sys_user (username, password, real_name, student_no, role) VALUES ('zhangsan', '加密后的密码', '张三', '2023001', 1), ('lisi', '加密后的密码', '李四', '2023002', 1);注意:插入用户表时密码不能是明文,要先通过项目里注册接口生成加密后的值再插入,否则直接插入明文密码会导致登录时无法匹配。
6.2 演示脚本设计的三个原则
设计演示脚本时,核心原则是用"业务故事"来串联功能,而不是机械地一个一个点菜单。我的演示顺序是这样的:先用学生账号登录,演示浏览社团、查看活动、申请加入某个社团;切换成社团管理员账号,演示审核新成员、发布新活动;再切换成指导老师账号,演示审批社团和活动;最后回到系统管理员账号,演示数据统计和用户管理。
这个演示路径本质上是"申请人→管理员→老师→超管"的一整条业务链条,"讲故事"的感觉会让老师更容易理解系统逻辑。这里要特别提醒:演示用的账号密码要提前写在纸上,演示前确认所有环境都已启动,尽量不要在现场登录时手滑输错三次账号导致锁定,这种小事故虽然不致命,但会打乱你的演示节奏。
6.3 答辩高频问题清单与回答准备
根据我自己的答辩经历,老师最常问的问题集中在以下几个方向:权限是怎么控制的、为什么选这个持久层框架、数据库为什么这样设计(第二、三范式角度)、某个状态是怎么流转的、如果有大量用户并发报名你怎么处理。这些问题在前面章节已经全部涉及,只要你在开发时真正理解了每一步,按逻辑答出来即可。
有一个容易被问住的问题是:"你这个系统相比线下管理,优势到底在哪里?"这个问题一定要提前准备答案。我当时回答的切入点是"数据流动性":线下管理时社团成员名单是一张纸质表,活动报名需要人到现场填表,数据是割裂的;系统把用户、社团、活动三个维度的数据串联起来,可以实时统计、留痕、追溯。这个回答既回答了问题,又展示了你的全局观。
6.4 论文与代码的对应关系怎么处理
很多同学的论文和代码是分开写的,最后写完才发现对应不上,只能反过来改代码改得焦头烂额。建议论文目录确定之后,每一章的标题都与系统功能一一对应。需求分析章节中列出的每一个功能需求,在代码里都要有对应的页面和接口;数据库设计章节的每一个字段,在代码的实体类里都要有对应的属性。主线对应上之后,后续写详细设计、测试章节都会顺利很多。
论文中的测试部分,不要只是截图证明功能正常,建议至少包含一条完整的测试用例描述:前置条件、操作步骤、预期结果、实际结果。比如"用户重复申请同一社团"这条用例,前置条件是用户未加入该社团且存在待审核申请记录,预期结果是接口返回提示"已申请,请勿重复提交",实际结果与预期一致。这类用例老师看了会认为你测试意识到位。
从选题到上线,高校学生社团管理系统这个项目带给我最大的收获,其实不是具体技术,而是"把一件事从头到尾做完整"的能力。你会发现,网上那些源码资源很多,但真正把数据库设计、权限控制、业务闭环串起来想明白,必须自己动手。如果你决定做这个项目,希望你参考我的思路,自己写出属于自己的那份代码。祝答辩顺利。