我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

校园活动发布系统设计:防超卖、状态机与RBAC权限实战

校园活动发布系统设计:防超卖、状态机与RBAC权限实战 校园活动发布系统这个题目每年毕业季都能在选题表里刷到好几遍。我第一次帮人看这类项目是几年前一个学弟把源码和开题报告一起发过来我打开数据库一看活动表八个字段报名表三个字段报名接口先 insert 再 count 一下连个唯一索引都没加。我问他同一个账号连点两下报名按钮会出几条记录他愣了几秒没答上来。这个场景基本就是这类项目的分水岭——能跑起来和能扛住问中间差的是对并发控制、状态流转、权限边界的真实理解。这篇就把一套校园活动发布系统从需求边界、表结构、防超卖、审核流一直说到开题报告怎么写、答辩怎么不被问倒全部摊开讲。后端我按 Spring Boot Vue 这套最常见的组合给示例换成 Django 或者小程序云开发骨架是一回事。适合正在做毕设的同学也适合刚接手校园信息化小项目的开发者拿去做参照。1. 项目整体定位与设计思路拆解1.1 需求边界先划清楚不做什么比做什么更重要校园活动发布系统听着简单实际拆开会发现它是个“多方参与 有审核流 有名额竞争”的小型业务系统。角色至少三类活动组织者社团负责人、院系学生工作干事、普通学生、审核方团委或学生工作部门。三类人看到的东西、能点的按钮、能改的字段完全不一样这是系统复杂度的第一层来源。核心功能我一般归纳成七块活动创建与草稿保存、活动审核与驳回重提、活动列表与详情展示、在线报名与取消、名额控制与候补、现场签到核销、数据统计与导出。这七块是必做的缺一块答辩时都容易被追问。“不做什么”同样要写进开题报告这是体现你思考深度的地方。我的建议是明确排除这几项在线支付校园活动绝大多数免费或线下缴费接支付会引入一堆资质和资金问题、站内即时聊天用通知和评论区足够做 IM 是给自己挖坑、第三方账号体系对接除非学校有统一认证否则自己做账号密码最稳、复杂的推荐算法数据量根本撑不起来硬写反而是减分项。把这些排除理由写清楚评审老师会觉得你是有判断力的而不是什么火堆什么。还有一个容易被忽略的边界活动名额的候补机制到底做不做。做能加分能讲出排队算法的故事不做也没问题但要在文档里说明“当前版本采用满员即止策略候补队列作为后续迭代方向”。最怕的是文档里写了候补、代码里没有答辩时被翻出来就很被动。1.2 技术选型的账要算清楚别为了炫技我见过太多项目栽在选型上——不是选错了框架是选了讲不明白的框架。答辩老师问“你为什么用这个”你答“网上教程多”这关就过不去。下面这张对比表是我自己带人做项目时常用的决策依据直接抄作业就行技术路线上手成本适合场景答辩友好度主要风险Spring Boot MyBatis-Plus Vue中功能完整、需要讲分层架构高教材和资料最多环境配置容易出问题Django 模板引擎低逻辑简单、想快速出效果中容易被问“是不是套模板”前端观感偏弱小程序 云开发低移动端体验好、演示有亮点中高扫码演示很加分后端逻辑讲不深Node.js Express React中高想体现前后端分离中老师可能不熟生态细节容易被追问我的实际建议是如果你的核心目标是稳过选 Spring Boot Vue。原因很实际这套组合的分层结构天然对应答辩要求的“三层架构”Controller 收参数、Service 写业务、Mapper 访问数据库你讲起来顺畅老师听起来熟悉。而且这套组合的坑都是被踩烂的坑网上能搜到答案不会在深夜两点卡在一个没人遇到过的问题上。选 MyBatis-Plus 而不是原生 MyBatis理由是单表 CRUD 的模板代码能省掉一大半。活动列表、报名记录这些全是最典型的单表分页查询用 MyBatis-Plus 的Page对象三行代码搞定省下来的时间拿去做防超卖和权限拦截性价比高得多。至于 Vue 用 2 还是 3看你手里的组件库——Element UI 配套 Vue2Element Plus 配套 Vue3别混用混用是新手最容易踩的头号大坑。提示选型方案一旦写进开题报告中途别改。改技术栈意味着推倒重来进度表上会立刻穿帮。1.3 权限模型别在 Controller 里写一堆 if新手最容易写的权限判断是这样的在接口开头写if (user.getRole().equals(admin))然后在另一个接口再抄一遍。业务一复杂这类判断会散落在几十个地方改一处漏三处。正确的做法是引入简化的 RBAC 模型四张表搞定用户表、角色表、用户角色关联表、权限点表也可以简化成角色枚举。这套模型的价值在于权限判断收敛到一个拦截器里业务代码只关心“这件事该不该做”不关心“你是谁”。具体到本系统我一般定义四个角色编码STUDENT学生可浏览、报名、取消、评价、ORGANIZER组织者可创建活动、查看自己活动的报名名单、发起签到、AUDITOR审核员可审核、驳回、强制下架、ADMIN管理员全部权限 用户管理。实现上用 Spring 的拦截器配合自定义注解比如在方法上打RequireRole(ORGANIZER)拦截器解析注解后比对当前登录用户的角色集合。这么做的第二个好处是前端菜单渲染可以直接复用一个权限接口后端返回当前用户的可访问菜单树前端动态生成路由不用把菜单写死在代码里。有个细节容易被漏掉组织者能不能给别人的活动报名能但要单独判断因为他同时具备学生身份。这就是角色不是互斥的、一个人可能同时是 ORGANIZER 和 STUDENT 带来的麻烦。我的处理方式是角色表允许一个用户挂多个角色判断权限时取并集而不是取第一个匹配。这一点如果能在开题报告的技术路线里提一句是很实在的加分项。2. 数据库设计整套系统的地基2.1 核心表清单与关键字段说明表结构设计是这个项目里最能体现功底的部分。我把常用的九张表列出来字段只列关键项通用字段创建时间、更新时间、逻辑删除标记就不重复写了表名作用关键字段sys_user用户主表id、账号、密码哈希、昵称、头像、学院、学号、状态sys_role角色表id、角色编码、角色名称sys_user_role用户角色关联用户id、角色id联合唯一activity活动主表id、标题、封面、分类id、主办方id、开始/结束时间、报名截止时间、名额、已报名数、状态、审核意见activity_signup报名记录id、活动id、用户id、报名状态、报名时间、取消时间、签到状态activity_category活动分类id、名称、排序、图标checkin_record签到记录id、活动id、用户id、核销时间、核销人id、核销方式notification站内通知id、接收人id、标题、内容、已读标记、关联活动idaudit_log审核流水id、活动id、审核人id、审核动作、审核意见、时间字段设计上有几个点必须说清楚因为它们直接对应答辩问题。第一activity表里为什么要存已报名数这个冗余字段 按范式来说这个数应该从报名表 count 出来不该存。但实际场景里活动列表页一次要查二十条活动每条都去 count 一次报名表就是二十次聚合查询页面直接卡住。存冗余字段是典型的用空间换时间代价是必须保证这个数和真实报名记录一致——这就引出下一节的并发问题。开题报告里把“反范式设计及其一致性保障”写一段是很能体现水平的内容。第二activity_signup表为什么必须有联合唯一索引 建一个(activity_id, user_id)的唯一索引这是防重复报名的最后一道防线也是最便宜的一道。应用层做判断会被并发穿透数据库唯一索引不会。我曾经见过一个项目应用层判断写得漂漂亮亮就是没加索引压测时同一个用户出现了六条报名记录答辩演示当场翻车。第三时间字段用什么类型 用datetime不要用字符串也不要用时间戳。用字符串会导致所有时间比较全靠代码数据库层面没法做索引范围的筛选。报名截止、活动开始、活动结束这三个时间的大小关系应该在建表时就写进业务约束文档里报名截止 ≤ 活动开始 活动结束。2.2 状态机让所有流转都合法活动状态是整个系统里最乱的地方。如果不把状态定义清楚最后会出现“已取消的活动还能报名”“已结束的活动被驳回”这种荒唐逻辑。我一般定六个状态状态码状态名可执行操作可流转到0草稿编辑、删除、提交审核11待审核撤回0、2、32已发布查看报名、签到、取消活动4、53已驳回修改后重提、删除14已结束查看统计、导出无5已取消查看原因无这张表的价值在于任何状态变更都必须在代码里显式判断来源状态是否合法。比如“提交审核”这个接口第一行就要判断当前状态是不是 0 或者 3不是就直接返回错误。我习惯把这种判断抽成一个枚举类把合法的流转关系写成静态 Map判断时查表这样新增状态时只需要改一处。报名记录也需要状态机但更简单已报名1、已取消2、已签到3。取消报名不是删除记录而是改状态因为你需要保留“谁报过名又取消了”的完整痕迹运营统计的时候要看这个数据。这个设计选择一定要在文档里写明白很多人图省事直接 delete被问到“用户取消报名后还能不能再报”就答不上来——用状态机的话答案是“把状态改回 1但受名额和截止时间限制”逻辑自洽。注意状态流转的接口一定要做幂等。用户连点两次“提交审核”第一次成功把状态从 0 改成 1第二次进来发现当前状态是 1直接返回“请勿重复提交”。没有这个判断审核队列里会出现两条一模一样的待审任务。2.3 索引与查询优化先想清楚页面要查什么索引不是拍脑袋加的是从页面查询倒推的。我把主要页面的查询条件列一遍学生端活动列表按分类筛选、按状态已发布筛选、按时间排序、关键词模糊搜索、分页我的报名按用户id查、按报名状态筛选、按活动开始时间排序组织者后台按主办方id查、按活动状态筛选审核工作台按状态1查、按提交时间排序签到页按活动id查、按学号/姓名模糊搜索对应的索引方案-- 活动列表主查询 ALTER TABLE activity ADD INDEX idx_status_start (status, start_time); ALTER TABLE activity ADD INDEX idx_category_status (category_id, status); ALTER TABLE activity ADD INDEX idx_organizer (organizer_id, status); -- 报名表唯一索引是基础再加两个查询索引 ALTER TABLE activity_signup ADD UNIQUE KEY uk_activity_user (activity_id, user_id); ALTER TABLE activity_signup ADD INDEX idx_user_status (user_id, signup_status); -- 审核工作台 ALTER TABLE activity ADD INDEX idx_status_submit (status, submit_time);模糊搜索这一块我要多说一句。很多人直接在标题字段上加普通索引然后LIKE %关键词%索引完全用不上。数据量小的时候没感觉数据量一上来就露馅。校园系统的数据量通常撑死几千条活动用不上索引也不会慢到哪去所以这里不要过度设计老老实实LIKE就行但要在文档里说明“当前数据规模下采用模糊匹配后续可引入全文索引”。这种“知道问题在哪但判断现阶段不需要解决”的表述比硬上一堆看不懂的优化方案更能体现判断力。3. 后端核心接口实现与并发控制3.1 活动发布接口校验链路要一层不漏发布接口是脏数据的主要来源校验必须做全。我把校验按照“参数格式 → 业务规则 → 权限 → 数据一致性”四层来组织每一层失败返回的错误码都不一样方便前端定位。PostMapping(/activity/publish) RequireRole(ORGANIZER) public Result publish(RequestBody Valid ActivityDTO dto) { // 第一层Bean Validation 注解已完成非空、长度、格式校验 // 第二层业务时间规则 LocalDateTime now LocalDateTime.now(); if (dto.getSignupDeadline().isAfter(dto.getStartTime())) { return Result.fail(报名截止时间不能晚于活动开始时间); } if (!dto.getStartTime().isBefore(dto.getEndTime())) { return Result.fail(活动结束时间必须晚于开始时间); } if (dto.getSignupDeadline().isBefore(now)) { return Result.fail(报名截止时间不能早于当前时间); } // 第三层名额合理性 if (dto.getQuota() null || dto.getQuota() 1 || dto.getQuota() 2000) { return Result.fail(活动名额需在 1 到 2000 之间); } // 第四层同一组织者短时间内不允许重复发布同名活动 long dup activityMapper.countRecentSameTitle( currentUserId(), dto.getTitle(), now.minusMinutes(10)); if (dup 0) { return Result.fail(请勿重复提交相同活动); } Activity entity ActivityConverter.toEntity(dto, currentUserId()); entity.setStatus(ActivityStatus.DRAFT.getCode()); activityMapper.insert(entity); return Result.ok(entity.getId()); }这里有个细节值得讲为什么要有第四层的重复提交判断。用户网络卡顿的时候会连点两下发布按钮第一次请求成功第二次也进来了结果数据库里出现两条一模一样的活动。前端加防抖能挡掉大部分但挡不住网络层重发。后端这道判断是兜底成本极低收益很高。另外说个实操心得内容字段的长度限制一定要在数据库和代码里保持一致。我在一个项目里遇到过前端限制 2000 字后端 DTO 没加Size数据库字段是varchar(2000)用户粘贴了一段 2500 字的文案直接抛出数据库截断异常前端显示的是一个看不懂的 500 错误。加一个Size(max 2000)就解决了但用户看到的是“活动描述不能超过 2000 字”这种能理解的提示。这种事不大但特别影响体验。3.2 报名名额扣减这是整个项目唯一的真并发点我敢说校园活动发布系统答辩时被问到的技术问题有七成集中在这里。场景很具体活动名额剩最后一个两个学生同时点报名怎么保证不超卖先说错误的写法因为大部分人第一反应就是这个// 错误示范先查后改中间有窗口期 Activity act activityMapper.selectById(id); if (act.getSignedCount() act.getQuota()) { return Result.fail(名额已满); } activityMapper.increaseSignedCount(id); // 两个线程都执行了这行 signupMapper.insert(record);问题在于“查”和“改”之间隔着几毫秒两个线程都查到signedCount quota - 1都判断可以报名然后都执行了加一最终signedCount比quota多了 1超卖了。我推荐三种方案按项目实际情况选方案一数据库乐观锁推荐最省事给activity表加一个version字段。更新时带上版本号做条件UPDATE activity SET signed_count signed_count 1, version version 1 WHERE id #{id} AND version #{version} AND signed_count quota;代码里判断影响行数是 1 就成功是 0 就说明要么名额满了要么被别人抢先改了版本让用户重试。这种方案的优点是不需要额外组件一个字段一行 SQL 搞定答辩讲起来也清楚。缺点是高并发下重试率会上升但校园场景一天几百人报名完全够用。其实这里可以直接把version省掉只用signed_count quota做条件因为这条 UPDATE 在数据库层面是原子操作行锁会串行化这些更新。加了 version 是为了在业务上区分“名额满”和“更新冲突”两种失败原因报错更精准。方案二Redis 预扣减数据量大的时候讲这个加分活动发布时把名额同步到 Redis报名时用 Lua 脚本做“判断 扣减”的原子操作扣减成功再异步落库。这个方案性能好但引入了一致性问题——Redis 扣了数据库写失败了怎么办需要补偿机制。我的建议是如果你没把握讲清楚补偿逻辑别用这个方案答辩老师一句“Redis 扣了但数据库没写进去怎么办”就能问穿。方案三报名表唯一索引兜底不管用哪种方案唯一索引都必须加。它是最后一道防线即使前面的逻辑全错了数据库也不会让同一个用户在一个活动里有两条有效记录。这个点一定要在答辩时主动说它体现的是“我知道应用层不可靠所以我在数据层做了兜底”的防御性思维。还有个真实场景要说取消报名后名额怎么处理。取消报名是把signup_status改成 2同时把signed_count减一。这里有个坑如果用户 A 报名、取消、再报名循环十次signed_count会不会算错会如果减一操作没有幂等保护。我的处理是减一的时候也带上条件判断确保只对“当前状态为已报名”的记录做减一UPDATE activity_signup SET signup_status 2 WHERE id #{id} AND signup_status 1; -- 上面的语句影响行数为 1 才执行下面这句 UPDATE activity SET signed_count signed_count - 1 WHERE id #{activityId} AND signed_count 0;两条语句放在同一个事务里要么都成要么都不成。3.3 签到核销二维码怎么做才安全又简单签到这块很多人做复杂了。我的思路是组织者打开签到页页面展示一个二维码学生扫码后跳转到核销页输入学号后四位确认完成签到。核销逻辑要幂等重复扫码只能成功一次。二维码内容不要直接放活动 id 明文否则别人随便改个数字就能签到别的活动。我的做法是放一个带签名的令牌// 生成活动id 时间戳 签名 String raw activityId : expireAt; String sign DigestUtils.md5DigestAsHex( (raw SECRET_SALT).getBytes(StandardCharsets.UTF_8)); String token Base64.getUrlEncoder().encodeToString((raw : sign).getBytes());核销接口收到 token 后先验签再判断是否过期然后查这个用户名下有没有该活动的有效报名记录有就把签到记录写进去写之前先查checkin_record表里有没有同样的(activity_id, user_id)记录有就直接返回“已签到”。用 MD5 做签名在校园项目里足够了它的作用是防篡改不是防破解。开题报告里可以说明“采用哈希签名保证令牌不可伪造密钥存放于服务端配置”这个表述既准确又不会引来深挖。提示二维码有效期别设太长按活动时长加两小时足够。设置成永久有效的令牌活动结束后还在外面流传是安全隐患。3.4 定时任务与通知让系统自己动起来三个定时任务基本是标配活动自动结束每小时扫一遍把结束时间 当前时间且状态为 2 的活动改成 4。报名截止提醒每天扫一遍把 24 小时内截止报名的活动推送给还没报名的学生用户量大的话要分批别一次全推。活动开始前提醒给已报名的用户推送“活动两小时后开始”。Scheduled(cron 0 0 * * * ?) public void autoCloseActivity() { ListActivity list activityMapper.selectList( new LambdaQueryWrapperActivity() .eq(Activity::getStatus, 2) .lt(Activity::getEndTime, LocalDateTime.now())); for (Activity act : list) { act.setStatus(4); activityMapper.updateById(act); } log.info(自动结束活动 {} 条, list.size()); }这里有个坑我必须提定时任务和用户操作之间会打架。用户正在报名一个活动定时任务刚好把活动状态改成已结束报名的判断逻辑如果先查状态后面又有几毫秒延迟就可能出现“报名成功但活动已结束”的脏数据。解决办法是报名接口里在事务内重新查一次活动状态用SELECT ... FOR UPDATE锁住那一行。这个细节说出来答辩老师一般会点头。通知功能我建议就用站内信别接短信。原因很实际短信要钱、要审核、要配模板而且校园项目的通知量完全不需要实时触达。站内信一张表搞定前端顶栏加个红点够用了。4. 前端页面与后台管理的关键实现4.1 学生端列表页的体验决定第一印象学生端只有三个页面是真正重要的活动列表、活动详情、我的报名。列表页的体验好坏直接决定演示时的观感。列表页我做这几件事卡片式布局展示封面、标题、时间、地点、名额进度条顶部固定分类标签横向滚动支持按时间/热度排序下拉到底自动加载下一页。名额进度条这个细节很值得做用不同颜色区分“充足 / 紧张 / 已满”用户一眼就知道要不要赶紧报。详情页的关键是按钮状态要准。同一个报名按钮根据不同情况要显示不同文案和状态场景按钮文案是否可点未登录登录后报名可点跳登录可报名立即报名可点名额已满名额已满不可点已过截止时间报名已截止不可点已报名取消报名可点活动已结束活动已结束不可点自己创建的活动管理活动可点跳后台这张表建议直接抄进代码用一个计算属性根据后端返回的字段统一算出来。最忌讳的是在模板里写一堆v-if嵌套改一处忘一处最后出现“已报名的活动还显示立即报名”这种低级问题。我的报名页要做的事更简单分“待参加 / 已结束 / 已取消”三个 tab每张卡片下面放“取消报名”和“查看详情”两个按钮取消要二次确认。二次确认这一步别省误触取消再想报名可能名额就没了用户体验会很差。4.2 管理端审核工作台是演示的重头戏后台管理页面的数量比学生端多但绝大多数是标准 CRUD用组件库的表格 弹窗表单就能拼出来。真正需要设计的是审核工作台。审核工作台的核心诉求是“快”。一个审核员一天可能要处理几十条申请所以要做到列表直接展示关键信息标题、主办方、时间、名额、封面缩略图点一下展开详情抽屉抽屉里直接有“通过 / 驳回”两个按钮驳回必须填意见。整个流程不跳页处理完自动定位到下一条。数据看板也是演示时的加分项。我一般放四个卡片加两个图活动总数、报名总人次、本月新增活动、待审核数量再加一个近七天报名趋势折线图和一个分类占比饼图。数据用后端一个统计接口一次返回别搞四个接口让前端并发请求简单点。导出功能用 EasyExcel 之类的库实现导出报名名单的时候注意一件事手机号、学号这类字段要做脱敏。导出成 Excel 直接给组织者里面全是完整手机号这在数据安全上是有问题的。我在项目里的做法是默认导出脱敏版本需要完整信息时由管理员单独申请。这个设计点在开题报告的“系统安全设计”一节里写上是很实用的内容。5. 开题报告怎么写才不被卡5.1 必备结构与本系统的内容映射开题报告被退回八成不是技术不行是结构不完整或者内容太空。标准的开题报告至少包含这几块我按本系统给出对应内容章节要写什么本系统怎么写选题背景与意义为什么做这个校园活动信息分散在群聊和海报里报名靠接龙统计靠手工效率低且易出错国内外研究现状别人做到什么程度综述现有活动管理类系统的功能特点指出通用系统对校园场景适配不足研究内容你要做什么列出七大功能模块每个模块两三句话说明技术路线用什么做、怎么做后端分层架构、前端组件化、数据库设计、关键算法说明重点与难点你解决了什么难题名额并发控制、审核状态机、权限模型进度安排时间怎么分配按周排见下表参考文献支撑材料不少于 10 篇含期刊、书籍、技术文档进度安排这张表我建议排得细一点显得你真有计划阶段时间占比主要工作交付物需求分析15%调研、功能梳理、用例图需求规格说明系统设计20%架构设计、数据库设计、接口设计设计文档、ER 图编码实现40%分模块开发、联调可运行源码测试优化15%功能测试、并发测试、缺陷修复测试用例与报告文档撰写10%论文撰写、修改、答辩准备论文终稿、演示材料“重点与难点”这一节是最容易写出差异化的地方。别写“界面美观”“功能完整”这种谁都能写的话就写具体的高并发下的名额一致性保障、多角色权限的细粒度控制、活动状态流转的合法性约束。每个难点下面写清楚问题是什么、你用了什么方案、为什么选这个方案三段话评审看了就知道你真做过。5.2 容易被问倒的几个点提前准备好答案我把带过的项目里被问到的频率最高的问题列出来每个都给一个能站住的答法。问你这个系统的创新点在哪这题千万别硬编创新。校园活动管理系统是个成熟品类硬说自己首创反而露怯。我的答法是从“场景适配”切入通用活动平台面向社会人群缺少校园特有的审核流和院系维度本系统把审核流程和院系归属做成了内建能力同时针对校园活动的免费属性去掉了支付模块整体更轻。这个答法诚实、有逻辑比编一个“基于大数据智能推荐”要强得多。问如果同时有一万人报名你的系统撑得住吗先承认边界再讲方案。答法当前部署环境下没有做过万人级压测但针对名额竞争这个核心场景做了乐观锁和唯一索引双重保障理论上不会超卖如果真要支撑这个量级需要引入缓存和消息队列做削峰这属于后续优化方向。承认边界不丢人硬吹才丢人。问你的数据库设计为什么这样分表能不能合并这题考的是你的设计理由。答用户、活动、报名三者是一对多和多对多关系合并会导致大量冗余和更新异常报名表独立出来还承担了保留历史记录的作用取消报名用状态标记而不是删除便于统计。讲清楚“为什么这样”比“这样是什么”重要得多。问代码是你自己写的吗被问到这句说明前面某个环节露了怯。应对方式是当场打开 IDE让老师随便点一个模块你讲这段代码的思路。这个准备一定要提前做挑三个模块把里面的每一行都能讲清楚最好是防超卖、权限拦截、状态机这三块因为它们最有内容。6. 常见问题排查与实操避坑实录6.1 开发期高频问题速查表下面这张表是我这些年攒下来的基本覆盖了这个项目 80% 的报错场景现象大概率原因处理方式前端请求全部 404代理配置的 target 少了端口或后端 context-path 不一致打开浏览器网络面板看真实请求地址与后端启动日志里的端口逐字比对跨域报错 CORS后端没配全局跨域或配了但拦截器在跨域之前执行用全局配置类统一处理别在单个 Controller 上打注解中文乱码数据库连接串没加字符集或建表时用了 latin1连接串加useUnicodetruecharacterEncodingutf8表统一 utf8mb4时间差 8 小时数据库时区、JVM 时区、序列化格式三者不一致连接串指定serverTimezoneAsia/Shanghai实体类统一用 LocalDateTime分页总数不对联合查询时 count 语句没去重检查 count 是否用了distinct或改用两次查询上传图片后访问 404静态资源映射路径和保存路径不一致映射配置里把本地目录映射到访问前缀路径用绝对路径定时任务不执行启动类没加EnableScheduling加注解同时确认 cron 表达式是六位还是七位报名数比实际多缺唯一索引或减一逻辑没做幂等补索引减一语句加状态条件6.2 部署环节的坑比写代码还磨人本地跑通不等于能部署这是我踩过最多次的地方。第一个坑是数据库初始化脚本不完整。本地开发时表是手动建的导出脚本时漏了索引和外键换台机器一跑就报错。解决办法是从一开始就用脚本管理表结构每次改表都同步更新脚本并且在干净的环境里完整跑一遍验证。我习惯在脚本开头加DROP TABLE IF EXISTS保证可以重复执行。第二个坑是配置文件里的密码和密钥进了代码仓库。数据库密码、签名盐值这些绝对不能写死在代码里提交上去。正确做法是放配置文件并且把配置文件加进忽略清单提供一个application-example.yml作为模板让别人填自己的。这个习惯从学生项目就该养成。第三个坑是前端打包后的路由模式。开发时用 hash 模式一切正常打包部署到服务器后如果用了 history 模式又没配服务端重定向刷新页面直接 404。要么老实用 hash 模式要么在 nginx 配置里加try_files $uri $uri/ /index.html。第四个坑是上传目录权限。部署到服务器后图片上传失败查半天发现是运行用户对目标目录没有写权限。这类问题排查思路很固定看日志报什么错、看目录权限、看磁盘空间三分钟能定位。6.3 答辩演示的保命技巧演示环节出问题比代码有 bug 更致命因为只有一次机会。提前录屏是底线。把完整流程走一遍录下来万一现场网络或环境出问题直接放录屏边放边讲。这不是作弊是专业。演示数据要提前铺。空数据库里点来点去什么都看不出来。提前准备二十条不同状态的活动、三条待审核的申请、十条报名记录、一个已经结束可以导出名单的活动。演示时从“审核一条申请”开始到“学生报名”“组织者签到”“导出名单”结束形成一个完整闭环五分钟讲完干净利落。别现场改代码。有人喜欢现场演示改一个 bug 展示能力风险极高。把自己的代码讲明白就够了。准备一个能跑的降级方案。如果服务器连不上本地有备份环境能起或者直接切录屏。三套方案备着心里就不慌。我在几个项目里反复验证过一件事这类系统的分数差异从来不在功能多少而在你对“为什么这么做”的表述是否清晰。同样是防超卖有人只会说“我加了锁”有人能说清楚“应用层判断会被并发穿透所以我在数据库层用条件更新兜底再加唯一索引做最后防线”——这两种回答在评审耳朵里完全是两个层次。代码写完只是开始把每个设计决策背后的判断讲出来才是这套东西真正的价值所在。
返回列表