基于Web的毕业设计选题平台设计与实现:并发控制与权限安全实战

发布时间:2026/9/23 14:24:06

基于Web的毕业设计选题平台设计与实现:并发控制与权限安全实战 简介基于Web的毕业设计选题平台是一份面向高校师生及教务管理人员的完整项目源码旨在解决传统选题管理中效率低、反馈慢的问题。系统采用Java与Vue前后端分离架构涵盖课题发布、浏览、选择、审核及管理全流程并通过人工智能算法优化选题推荐提供更智能的个性化选题体验。资源压缩包共102个文件大小约2.73MB其中包含30个Java后台业务代码、12个Vue前端组件、12个JavaScript脚本、SQL数据库初始化脚本、XML配置及Maven工程文件等覆盖后端服务、前端交互与数据存储三层。前端采用响应式设计后端模块化分层清晰并配有项目配置文件与数据库脚本导入IDE即可运行调试便于二次开发。已有45人学习下载适合作为毕业设计选题平台开发参考或用于Spring Boot、Vue、MySQL等技术栈的实战学习。1. 基于Web的毕业设计选题平台设计与实现先想清楚它是预约系统不是信息发布系统到四月中旬毕业论文题目还在班群里传 Word 接龙学生挨个私聊老师问哪个还能选最后教务老师用 Excel 合并时发现两个学生选了同一个题。基于Web的毕业设计选题平台设计与实现就是专门终结这种混乱的教师在线发布课题学生在规定时间内选题退选管理员统一审核和统计。它本质上是一套带时间窗口、名额约束和状态流转的预约审批系统难点不在页面而在并发抢题、数据唯一性和权限控制。适合计算机专业学生拿来做毕业设计项目也适合想真正把选题流程线上化的教务和教师。下面按我实际做这类系统的顺序展开。2. 业务模型与技术选型五张核心表和三条权限线把需求钉死这类平台翻车通常不是代码写错而是业务模型没定死就动手。我接到类似需求会先画一张三角色流程图教师出题、学生选题、管理员管过程和结果。三条权限线对应三套页面谁能在哪个阶段做什么表结构就跟着长出来了。模型对了后端的接口和前端页面自然顺。2.1 先划分用例边界哪些功能要做哪些明确不做先列核心用例防止后期被评审老师或临近毕业的学生加需求加到失控。教师端四个用例申报课题并填写名称、简介、人数上限查看自己课题的被选情况审核或驳回学生申请关闭不再收人的题目。学生端三个按关键词或专业过滤已开放课题在平台设定的时间窗内提交选题和退选随时查看自己的审核状态。管理员端四个批量导入学生和教师账号审核教师课题是否开放配置选题各阶段起止时间导出最终选题名单。“明确不做”也是需求的一部分。我一般会在设计文档里写清楚不做站内聊天、不做论文查重、不做答辩分组提醒。原因很简单这些功能会让状态机复杂一个量级而且 Excel 也能勉强替代。把名额、状态、时间这三个点做扎实平台的业务价值已经完整。剩下的功能全都属于可以后补的扩展项。2.2 数据库设计五张表之间的关系和三个最关键索引表结构不需要多sys_user、topic、select_record、time_config 再加一张公告表足够了。前四张是核心公告只是顺手补的展示内容。用户表存三类账号课题表存教师出的题选课记录表存整个选题申请的流转时间配置表让开放窗口可配置不写死在代码里。CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL, password VARCHAR(128) NOT NULL COMMENT BCrypt 密文, role TINYINT NOT NULL COMMENT 1 学生 2 教师 3 管理员, real_name VARCHAR(32) NOT NULL, deleted TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE topic ( id BIGINT PRIMARY KEY AUTO_INCREMENT, teacher_id BIGINT NOT NULL COMMENT 关联 sys_user.id, title VARCHAR(200) NOT NULL, description TEXT, max_students INT NOT NULL DEFAULT 1, selected_count INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 0 COMMENT 0 待审 1 开放 2 关闭, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_teacher (teacher_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE select_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL, topic_id BIGINT NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0 待审核 1 已通过 2 已退选, is_current TINYINT GENERATED ALWAYS AS ( CASE WHEN status IN (0,1) THEN 1 ELSE NULL END ) STORED, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_student_current (student_id, is_current), KEY idx_topic (topic_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE time_config ( id TINYINT PRIMARY KEY, phase VARCHAR(32) NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, enabled TINYINT NOT NULL DEFAULT 1 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里最值得展开的是 select_record 里的生成列 is_current。很多参考代码直接对 (student_id, status) 建唯一索引那是错的同一个学生第一次退选后 status 变成 2第二次退选再插入 status2 会撞唯一键如果去掉唯一索引学生又能并发提交多条待审核记录。生成列把“当前有效”映射成 1把“已退选”映射成 NULLMySQL 唯一索引允许多个 NULL 重复于是数据库层面就保证了一个学生最多只能有一条待审核或已通过的记录。这条设计在论文里也值得单独讲。关于 selected_count 字段注意它表示“已占名额数”而不是“已通过数”。到底是提交申请就占名额还是教师审核通过才占名额这是第 3 章的核心分歧点。先记住表里已经有这个字段后面代码会用到。2.3 后端骨架Spring Boot 是主流FastAPI 和 SQLAlchemy 是另一条好路java web 方向做这种管理系统最稳的组合是 Spring Boot 3 加 MyBatis-Plus 加 MySQL 8。Spring Boot 的资料多答辩时被问到任何一层都能找到对应参考实现内嵌 Tomcat 也让部署简单一个 java -jar 就能起服务。搭建时只需要在 pom.xml 引入 spring-boot-starter-web、mybatis-plus-spring-boot3-starter 和 mysql-connector-j然后配置数据源第一次跑通不需要额外东西。application.yml里几个参数值得留意数据库连接串加上useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai否则中文乱码和时区错误会在后面轮番出现MyBatis-Plus 的 mapper-locations 指向classpath*:mapper/**/*.xml方便把复杂 SQL 写在 XML 里而不是拼在代码中。如果你的团队更熟 Pythonpython web 框架里的 FastAPI 加 SQLAlchemy 写这种接口也相当舒服尤其批量导入和统计出库时可以直接用 pandas。换框架不换业务模型后面讲的事务、唯一约束和并发控制思想在两条技术路线里完全通用。我个人偏好 Spring Boot 是因为毕业设计生态里遇到问题容易搜到现成答案新手跟起来压力小。2.4 前端不要过度设计Vue3 加 Element Plus 按角色拆三个入口前端用 web 前端开发最常见的 Vue3 加 Vite 加 Element Plus 组合就够了。页面按角色拆登录页、学生选题列表、我的选题、教师课题管理、管理员用户导入和统计页。不要一上来就设计复杂的动态菜单权限三个固定入口已经能覆盖流程。路由守卫只防“手滑”不防“故意”。一个简化的 beforeEach 是这样router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.meta.role to.meta.role ! store.role) { next(/401) } else { next() } })这段逻辑说明了两件事第一token 存在本地是多数毕设的常见做法简单直接第二路由守卫只控制前端能不能进某个页面拦截不了别人直接用 Fiddler 改请求或手工调用/api/admin/import。真正的角色判断必须在后端每个接口执行从认证上下文里拿角色而不是信任请求参数里带的 role 字段。这个原则后面第 4 章还会反复出现。后端的包结构我建议严格分层不要一个 Controller 写完所有逻辑。常见的做法是controller、service、mapper、entity、common五层common 里放 Result 包装类、BizException 和全局异常处理器。分层不是形式主义选题接口要在事务里同时操作 topic 和 select_record 两张表没有 service 层承接事务注解都不知道往哪里放。3. 把核心流程跑通选题、退选与并发名额扣减的代码实现第 2 章把表和骨架立住之后这一章解决的是平台最要紧的三件事学生怎么安全提交选题、退选时名额怎么释放、以及多人同时抢一个课题时凭什么不会超卖。这三个问题承载了平台 80% 的业务复杂性。3.1 选题接口五步校验和一条条件 UPDATE很多现成模板把选题接口写成先查询课题、再判断名额、再插入记录、最后 update selected_count。低并发下没有问题但到了开放第一天上午九点几百个学生同时点按钮两个线程会同时通过名额判断产生超卖。所以我的做法是把名额扣减放到一条 UPDATE 的 WHERE 条件里让数据库行锁保证原子性。Service public class SelectTopicServiceImpl implements SelectTopicService { Resource private TopicMapper topicMapper; Resource private SelectRecordMapper recordMapper; Override Transactional(rollbackFor Exception.class) public void selectTopic(Long studentId, Long topicId) { Long existId recordMapper.selectCurrentId(studentId); if (existId ! null) { throw new BizException(已有待审核或已通过的选题不能重复提交); } Topic topic topicMapper.selectById(topicId); if (topic null || topic.getStatus() ! 1) { throw new BizException(课题不存在或未开放); } if (topic.getSelectedCount() topic.getMaxStudents()) { throw new BizException(该课题名额已满); } int updated topicMapper.occupySeat(topicId); if (updated 0) { throw new BizException(手慢了名额刚被抢完); } SelectRecord record new SelectRecord(); record.setStudentId(studentId); record.setTopicId(topicId); record.setStatus(0); try { recordMapper.insert(record); } catch (DuplicateKeyException e) { throw new BizException(你已经提交过选题申请了); } } }对应的 mapper XML 里的 occupySeat 是这样update idoccupySeat UPDATE topic SET selected_count selected_count 1 WHERE id #{id} AND status 1 AND selected_count max_students /update逻辑说明这里把“查名额、占名额”合并成了一步。只要 UPDATE 影响行数为 0就说明课题不存在、已关闭或者名额已满不需要再去对比旧值。Transactional保证 selected_count 加 1 和插入 select_record 要么同时成功要么同时回滚不会出现计数变了但记录没插进去的脏状态。即使两个并发请求同时走到 insert生成列唯一索引 uk_student_current 也会拦住第二条抛出 DuplicateKeyException 后整个事务回滚count 加的 1 也随之回滚。参数说明topicId 来自前端请求studentId 必须从登录态解析绝不能由前端传过来。selectCurrentId的 SQL 就是查 status in (0,1) 的记录配合索引扫描非常快。这个方案用数据库的原子性代替了 Java 层的锁单机多线程和将来多实例部署都适用。3.2 教师审核与退选状态流转和名额扣减必须同事务选题申请提交后是待审核状态教师可以通过或驳回。审核接口有一个容易漏的点驳回时要释放名额通过时不当选名额。因为 3.1 里提交申请时就占了名额被驳回后名额不吐出来课题就会越满越假。Transactional(rollbackFor Exception.class) public void audit(Long teacherId, Long recordId, boolean pass) { SelectRecord record recordMapper.selectById(recordId); if (record null) { throw new BizException(选题记录不存在); } Topic topic topicMapper.selectById(record.getTopicId()); if (topic null || !topic.getTeacherId().equals(teacherId)) { throw new BizException(只能审核自己课题下的选题); } if (record.getStatus() ! 0) { throw new BizException(该记录已被处理过); } record.setStatus(pass ? 1 : 2); recordMapper.updateById(record); if (!pass) { topicMapper.releaseSeat(record.getTopicId()); } }这段代码里的一个关键设计是幂等。第一次审核后 status 变成 1 或 2第二次再进来会命中“该记录已被处理过”不会再重复释放名额。否则教师多点一次“通过”后台就把同一个名额释放两回课题人数就会对不上。退选接口是审核的对称操作。学生只能退自己的记录待审核和已通过状态下的退选都要释放名额因为两种状态当初都占过名额。这里要注意退选和驳回是两条代码路径但名额释放逻辑必须收敛到同一个topicMapper.releaseSeat(topicId)方法里避免只改了一处另一处漏掉。事务边界上改 record 状态和改 topic 计数必须在一个事务里否则中间进程崩溃就会出现“记录退选了人数没减”的翻车现场。3.3 两种抢题模式先到先得与志愿分配选一种做深如果学校规定“学生一次只能选一个课题先到先得”3.1 的方案就是完整的。但有些学校实行志愿制学生可以填三个志愿教师最终确定一个。这时候 select_record 的唯一索引就不能用了因为同一个人会有多条“待处理”的记录。常见做法是另建一张 topic_wish 表字段是 student_id、topic_id、priority允许同一学生插入多条记录等申报结束后由管理员触发一个分配任务先按学生志愿优先级排序再结合教师确认或成绩排名确定每人最终命中一个课题把结果写回 select_record。分配规则可以很简单也可以做成带权重的算法但这套逻辑会让项目复杂度至少翻一倍。我的实际建议是个人毕设做先到先得加教师审核就足够了肖愿制写进论文“优化方向”里讲比现在硬做两种模式稳妥得多。毕业设计评审判的是你能否把一条流程做完整、考虑清楚边界而不是功能塞得多满。3.4 用 Fiddler 看请求报文前后端联调别靠猜前后端联调时最常见的场景是前端说“我请求发了”后端说“我没收到”最后发现两边说的是两回事。用 Fiddler 抓包工具是最直接的排查方式。配置上先让 Fiddler 监听 8888 端口并开启 HTTPS 解密安装它生成的根证书浏览器代理指向 127.0.0.1:8888 后所有请求都会经过 Fiddler按 URL 关键字过滤就能看到选题接口的原始请求和响应。抓到请求后重点看三样东西请求方法是不是 POSTContent-Type 是不是 application/jsonAuthorization 头有没有带上 token。下面这张表是我在实际联调里最常见的几类问题抓包现象可能原因处理方式出现 OPTIONS 请求且返回 403后端 CORS 没放开预检配置 allowedOriginPatterns不要用*加 allowCredentials 的组合请求体是表单格式后端却按 JSON 解析Axios 没设 Content-Type在请求拦截器统一设置 JSON 头返回 200 但业务 code 是 500后端抛了业务异常去后端控制台看堆栈别盯着前端弹窗猜接口返回 401token 没带进 header检查请求拦截器是否从 localStorage 取 token这些判断依赖真实报文而不是代码 review 时的“我觉得应该没问题”。养成联调先抓包的习惯能省下大半天互相甩锅的时间。4. Web安全、并发与部署选题高峰期前的三道防线平台功能跑通只是开始真正决定上线当天会不会翻车的是安全边界、并发控制和部署方式。很多毕设能演示但扛不住真实场景问题都出在这一章。4.1 SQL注入与越权ORM不是免死金牌MyBatis 的#{}会走预编译但${}是字符串拼接。最典型的翻车点不在 where 条件而在排序字段。有的同学为了做前端排序把 orderBy 参数直接拼进 SQLString orderBy request.getParameter(orderBy); queryWrapper.last(ORDER BY orderBy);如果学生传title; DROP TABLE topic; --这条 SQL 的破坏力可想而知。正确做法是白名单校验ListString allowed Arrays.asList(created_at, selected_count, title); if (!allowed.contains(orderBy)) { throw new BizException(非法排序字段); } queryWrapper.last(ORDER BY orderBy);越权是另一个隐蔽问题。选题记录的 id 是自增整数攻击者遍历 recordId 就能查询别人的申请。后端每次都要用当前登录学生 id 作为查询条件而不是只凭 recordId 查出来再用SelectRecord record recordMapper.selectOne( new LambdaQueryWrapperSelectRecord() .eq(SelectRecord::getId, recordId) .eq(SelectRecord::getStudentId, currentStudentId) ); if (record null) { throw new BizException(记录不存在); }这里注意提示语不能区分“记录不存在”和“无权操作”否则等于告诉攻击者这个 id 是存在的且属于别人。统一返回“记录不存在”是安全上常见做法。4.2 并发抢题的硬约束Synchronized 管不住集群和应用重启有人会在选题方法上直接加 synchronized这在单机单进程时确实有效但有两个硬伤第一服务一旦多实例部署锁就管不到另一台机器第二锁在 JVM 进程内应用重启瞬间锁就消失正在并发处理的请求依然会冲进去。比 synchronized 更接近正确答案的是两种数据库方案。一种是SELECT ... FOR UPDATE悲观锁先锁住课题行再判断名额正确性没问题但要求锁和事务在同一连接里稍不注意就会出现锁不释放、死锁难排查。另一种是我在 3.1 用的方式用一条带条件的 UPDATE 原子占座影响行数为 0 就说明失败。两者相比后者锁的粒度更轻不需要额外事务工具代码也更好解释。唯一索引作为最后一道兜底防线不能省。并发再猛烈程序逻辑再严谨提交到数据库时唯一约束会再挡一次。把“数据库约束优先于代码逻辑”当成习惯很多脏数据问题根本不会出现。4.3 Nginx反向代理与静态资源缓存一台服务器扛住选题高峰选题开放第一天学生会集中刷新页面和提交申请。纯靠 Spring Boot 同时处理静态资源和动态接口Tomcat 线程会被占满。常见做法是用高性能 web 服务器 Nginx 做前置静态资源直接由 Nginx 返回动态接口反向代理到后端同时把打包后的前端文件缓存起来。server { listen 80; server_name topic.local; client_max_body_size 10m; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 5s; proxy_read_timeout 60s; } location /assets/ { alias /opt/topic-web/dist/assets/; expires 7d; add_header Cache-Control public, max-age604800; } location / { root /opt/topic-web/dist; index index.html; try_files $uri $uri/ /index.html; } }参数说明client_max_body_size 10m是为了避免管理员上传 Excel 时返回 413proxy_pass不带尾斜杠保留/api/前缀后端接口路径和本地开发保持一致/assets/目录里的文件是 Vite 打包产物文件名带 hash可以放心设 7 天缓存这是 Linux web 缓存最常见的配置方式try_files解决前端 history 路由刷新 404 的问题。一个特别容易翻车的细节如果后端又把 context-path 设成了/apiNginx 反代后再加一层前缀接口路径就变成/api/api/login。建议后端不设 context-path由 Nginx 统一处理/api前缀两边只保留一层排查起来会简单很多。4.4 登录态与 CORS从 JWT 到 HttpOnly Cookie 的选择登录态常见的做法是 JWT登录成功后后端返回 token前端存 localStorage每次请求在 Axios 拦截器里加 Authorization 头。它的好处是无状态、方便前后端分离缺点是对 XSS 敏感前端只要有一处 innerHTML 拼了用户输入token 就可能被偷走。所以 JWT 方案下要求前端不直接用 v-html 渲染教师填的课题描述所有富文本内容都要先转义。安全要求高一点就把会话改成 HttpOnly Cookie再配上 Secure 和 SameSiteLax。Cookie 由浏览器自动携带JavaScript 读不到XSS 的损失面会小很多。但跨域配置也要跟上后端 CORS 的 allowedOrigins 不能是*因为带 Cookie 的跨域请求不允许通配来源本地开发用http://localhost:5173前端地址前后端都统一好否则登录成功但后续接口不带 Cookie看起来就像“登录失效”。5. 避坑指南从开发到上线最容易踩的五个坑这一章写的是真实项目里反复出现的排错记录。每一条都按现象、原因、解决三部分写按顺序排查能省出大量时间。5.1 重复选题数据库唯一索引没建程序判断再细也会漏现象同一名学生出现在两个课题的通过名单里退选记录混乱最终名单导出后还要人工核对。原因代码里“先查再插”的逻辑在并发下并不可靠。两个请求同时查到“该学生没有选题记录”然后各自插入一条如果数据库层没有唯一约束兜底就都成功了。更隐蔽的来源是 Excel 导入旧数据时本身就有重复记录混在里面。解决select_record 表用 2.2 里的生成列唯一索引 uk_student_current导入前先按学号去重已经污染的库要先清理脏数据再建索引否则索引建不上或被现有数据挡住。5.2 退选名额不释放修 Bug 只改了一处逻辑现象学生退选成功自己也能重新选别的课题但原课题在教师端仍显示满员其他学生进不来。原因退选和驳回是两个独立入口当时只在其中一个分支调用了 releaseSeat。另一个分支改完状态就走名额没有减回去。解决把名额释放收敛到一个topicService.releaseSeat(topicId)方法所有把选题状态改成 2 的地方统一调用它退选接口做幂等第二次退选直接返回成功而不是报错避免前端重复点击造成连锁问题。5.3 选题时间窗口错乱服务器时区与前端时区打架现象管理员明明配置了 9 点开放学生 8 点 55 就能提交或者刚过截止时间 1 分钟接口把请求放过去了。原因服务器操作系统是 UTC 时区数据库连接串没指定 serverTimezoneSpring Boot 的 Jackson 又按默认时区序列化。前端用 JavaScript 的new Date()换算成北京时间和后端存进 MySQL 的 DATETIME 对不上。解决数据库连接串加serverTimezoneAsia/Shanghai实体时间字段用 LocalDateTime不用java.util.Date部署脚本里加-Duser.timezoneAsia/Shanghai所有窗口判断由后端以服务器时间为准执行前端只负责展示。5.4 Excel 批量导入题目乱码、重复、格式错一次性全来现象管理员上传模板后系统提示“第 3 行标题为空”但用记事本打开 Excel 文件明明有字同样的模板导入两次系统里出现两套一样的题目。原因旧的 CSV 文件是 GBK 编码后端按 UTF-8 读中文自然乱码模板表头顺序和解析代码不一致判重键设计得不对。解决用 EasyExcel 按实体类读取自己 split 字符串拼行很容易在引号、换行符上出问题模板固定列顺序判重用“教师工号加题目标题”的组合而不是只凭题目名校验失败不中断整批把出错行和原因收集起来返回让管理员下载而不是到第 10 行才报错让人无从查起。5.5 部署后刷新 404、接口 502路由和代理各背一口锅现象Nginx 下访问首页正常点浏览器刷新变成 404登录接口返回 502 Bad Gateway。原因前端 history 路由在 Nginx 里没有 fallback后端服务没起来或 Nginx 转发地址写错了端口后端 context-path 和 Nginx 前缀叠加重叠。解决先按顺序排查。ss -lntp看后端 8080 端口是否在监听curl http://127.0.0.1:8080/actuator/health看服务是否真正可用最后确认 Nginx 配置里try_files $uri $uri/ /index.html;写没写。502 的另一个常见来源是 Nginx 转发到http://127.0.0.1:8080/api而后端 context-path 又是/api两层拼成/api/api后端根本没有这个路由自然全部 502。6. 交付前的检查清单与两个值得做的扩展临近交付功能都跑通不代表能上线。我每次给这种系统做验收会先按一张固定清单过一遍再跑一个并发脚本最后才去写文档和演示准备。检查项通过标准数据库唯一约束同一学生并发提交两次选题第二次被拒绝且返回明确提示时间窗口未到开放时间、超过截止时间时服务端接口都拒绝提交名额上限最后一个名额被抢走后后续请求返回“名额已满”权限边界学生访问教师接口返回 403修改 recordId 访问他人记录返回“记录不存在”退选对称待审核和已通过两种状态退选后课题人数都减一并发脚本可以很简单不需要引入 JMeter。用一个 for 循环模拟 20 个并发请求抢同一个名额只有 1 的课题成功数必须等于 1for i in $(seq 1 20); do curl -s -X POST http://localhost:8080/api/student/select-topic \ -H Authorization: Bearer $TOKEN \ -H Content-Type: application/json \ -d {topicId: 1} \ -o /tmp/resp_$i.json done wait grep -h 成功 /tmp/resp_*.json | wc -l这段脚本里每个请求都带相同 token 的前提下成功数等于 1 才说明并发控制真正生效。如果结果大于 1优先查唯一索引建没建再查事务回滚有没有生效。扩展方向我做两个。第一是志愿分配算法在现有表结构上加一张 wish 表把先到先得改成志愿加教师确认核心是优先级排序和最终唯一性校验这个方向写进论文非常加分。第二是统计导出按专业、课题方向和导师维度导出 Excel甚至用 web 页面直接生成 PDF 选题确认单技术上不难但能补上管理侧最后一块拼图。我第一次做类似的选题系统部署当天就发现两个学生同时出现在同一个课题的通过名单里原因就是我当时没建唯一索引后续又从 Excel 导入造了一条脏数据。后来我养成了一个习惯凡是“一个用户同一时刻只能有一条 X”的规则先写数据库约束再写代码判断。顺序反了数据库迟早用脏数据教你做人。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/23 14:24:06

主成分分析法MATLAB实现与改进:从标准化到特征值分解

简介:这份MATLAB程序集以主成分分析法(PCA)及改进版为核心,专为数据降维、指标提取与综合评价场景设计,适合统计建模、数据挖掘和机器学习方向的开发者与科研人员使用。程序实现既包含数据标准化、协方差矩阵计算、特征…

2026/9/23 14:24:06

3个坑搞定信息安全运营最佳实践

3个坑搞定信息安全运营最佳实践 官方文档动辄几百页,翻到第三页就开始打瞌睡,根本抓不住重点。别急,搞懂 信息安全运营 的核心逻辑,你也能像老手一样避坑。今天不聊虚的,直接拆解三个让无数新手栽跟头的实操场景,帮你把 最佳实践 刻进肌肉记忆。…

2026/9/23 14:24:06

TASKalfa 4012i_3212i维修手册实战:错误码定位与拆装保养指南

简介:TASKalfa 4012i_3212i 维修手册是面向复印机维修人员与设备维护工程师的实用技术指南,围绕京瓷这两款黑白复合机的保养与检修场景,系统梳理安全警告、安装规范与使用注意事项,帮助读者在拆装、维护过程中保障客户、机器及自身…

2026/9/23 15:24:19

PCB软硬结合设计:层叠结构与材料协同降本增效

简介:本资源是一篇聚焦PCB软硬结合设计技术的深度技术文章,面向硬件工程师、PCB设计师及电子产品研发人员,重点解决移动设备小型化、低成本与高可靠性并存的设计难题。文章系统阐述了软硬结合板如何通过消除连接器与柔性电缆,降低…

2026/9/23 15:24:19

DNF生化模式帧数暴跌?3招优化代码让卡顿变丝滑

DNF生化模式帧数暴跌?3招优化代码让卡顿变丝滑 凌晨两点,盯着屏幕上的DNF生化模式,怪物刷得密密麻麻,角色刚扔出个技能,画面直接卡成PPT。想切后台看看任务列表,结果整个客户端无响应,鼠标转圈圈。这时候你打开任务管理器,CPU飙到95%…

2026/9/23 15:24:19

Python属性访问机制与高效调试实践

1. Python属性访问机制与调试痛点在Python开发中,属性访问是最基础也最频繁的操作之一。当我们需要调试一个复杂系统时,经常需要知道某个对象的属性在何时被访问、被谁访问以及访问的结果如何。传统做法是在代码中手动添加print语句,但这不仅…

2026/9/23 15:24:19

借呗怎么提升额度源码解析 3个坑让你少折腾

借呗怎么提升额度源码解析 3个坑让你少折腾 配置环境就卡半天,是不是觉得熟悉?明明照着文档敲代码,报错信息却像天书。别急,今天咱们不聊玄学,直接上 借呗怎么提升额度 背后的逻辑,用 源码解析…

2026/9/23 15:19:18

JavaCC+递归下降实现类C编译器:四层验证与栈可视化实战

简介:本资源是重庆理工大学编译原理课程设计的完整实现成果,面向计算机专业本科生及编译技术初学者,聚焦类C语言编译器的设计与开发实践。项目基于Java与JavaCC工具链构建,涵盖词法分析、语法分析(递归下降LL1验证&…

2026/9/23 12:07:00

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/23 12:06:55

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/23 0:01:54

3个实战技巧搞定形式英语:从看教程到跑通性能优化

3个实战技巧搞定形式英语:从看教程到跑通性能优化 看了一堆教程还是不会写项目?别慌,这种“眼高手低”的困境在开发者圈子里太常见了。很多人以为卡点在语法,其实真正拦路虎是缺乏将知识点串联成完整链路的能力。今天咱们不聊虚的,直接拿【形式英语】这…

2026/9/22 16:34:32

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/22 20:01:30

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/22 13:25:41

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码