【项目】【在线判题系统】c端:竞赛报名
目录一、功能分析二、controller三、service四、mapper五、缓存更新方法六、获取当前登陆用户6.1 引入依赖6.2 工具类6.3 修改已有代码6.3.1 TokenService6.3.2 拦截器TokenInterceptor6.3.3 mybatis自动更新MyMetaObjectHandler一、功能分析比赛报名的用户需要记录竞赛排名比赛结束统计已报名该竞赛的用户的分数我的竞赛当前用户已经报名的竞赛竞赛列表需要记录报名的用户先登录登录成功之后在竞赛列表中找到要报名的竞赛点击报名参赛按钮前端携带着竞赛id和token向后端发请求。后端接收到请求之后先判断是否符合报名条件。如果不满足直接返回用户报名失败报名失败原因。如果满足:将用户的报名信息存储到数据库和redis。前端接收到后端的响应之后根据返回的结果 提示用户报名成功或者报名失败。如果成功竟赛列表中该竟赛展示已报名标签并且在我的竞赛列表中要能找到报名的个竞赛。报名条件:1.用户必须处于登录状态、2、不能报名不存在的比赛3、不能重复报名、4、已经开赛的竞赛不能再进行我的竞赛列表缓存key: u:e:l:userIduserexamlist:userId,valuelist(存储examId)建表语句-- 竞赛报名用户-竞赛关联表createtabletb_user_exam(user_exam_idbigintunsignedNOTNULLCOMMENT用户竞赛关系id主键,user_idbigintunsignedNOTNULLCOMMENT用户id,exam_idbigintunsignedNOTNULLCOMMENT竞赛id,scoreintunsignedCOMMENT得分,exam_rankintunsignedCOMMENT排名,create_bybigintunsignednotnullcomment创建人,create_timedatetimenotnullcomment创建时间,update_bybigintunsignedcomment更新人,update_timedatetimecomment更新时间,primarykey(user_exam_id),-- 联合唯一索引防止同一用户重复报名同一竞赛uniquekeyuk_user_exam(user_id,exam_id),-- 普通索引提升查询效率keyidx_exam_id(exam_id))ENGINEInnoDBDEFAULTCHARSETutf8mb4COMMENT用户竞赛报名表;二、controlleroj-modules/oj-friend/src/main/java/com/yj/friend/controller/user/UserExamController.javapackagecom.yj.friend.controller.user;importcom.yj.common.core.constants.HttpConstants;importcom.yj.common.core.controller.BaseController;importcom.yj.common.core.pojo.R;importcom.yj.friend.domain.user.dto.ExamDTO;importcom.yj.friend.service.user.IUserExamService;importjakarta.annotation.Resource;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;RestControllerRequestMapping(/user/exam)publicclassUserExamControllerextendsBaseController{ResourceprivateIUserExamServiceuserExamService;PostMapping(/enter)publicRVoidenter(RequestBodyValidatedExamDTOexamDTO,RequestHeader(HttpConstants.AUTHENTICATION)Stringtoken){userExamService.enter(examDTO,token);returnR.success();}}接收参数oj-modules/oj-friend/src/main/java/com/yj/friend/domain/user/dto/ExamDTO.javapackagecom.yj.friend.domain.user.dto;importjakarta.validation.constraints.NotBlank;importlombok.Getter;importlombok.Setter;GetterSetterpublicclassExamDTO{NotBlank(message竞赛ID不能为空)privateLongexamId;}三、serviceoj-modules/oj-friend/src/main/java/com/yj/friend/service/user/IUserExamService.java接口类packagecom.yj.friend.service.user;importcom.yj.friend.domain.user.dto.ExamDTO;publicinterfaceIUserExamService{voidenter(ExamDTOexamDTO,Stringtoken);}oj-modules/oj-friend/src/main/java/com/yj/friend/service/user/impl/UserExamServiceImpl.java对报名的条件进行一一筛选然后存放进redis和mysqlpackagecom.yj.friend.service.user.impl;importcn.hutool.core.collection.CollectionUtil;importcom.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;importcom.yj.common.core.constants.Constants;importcom.yj.common.core.enums.ResultCodeEnum;importcom.yj.common.core.utils.ThreadLocalUtil;importcom.yj.common.security.exception.ServiceException;importcom.yj.common.security.service.TokenService;importcom.yj.friend.domain.exam.ExamDO;importcom.yj.friend.domain.user.UserExamDO;importcom.yj.friend.domain.user.dto.ExamDTO;importcom.yj.friend.manage.ExamCacheManager;importcom.yj.friend.mapper.exam.ExamMapper;importcom.yj.friend.mapper.user.UserExamMapper;importcom.yj.friend.service.user.IUserExamService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Service;importjava.time.LocalDateTime;ServicepublicclassUserExamServiceImplimplementsIUserExamService{AutowiredprivateUserExamMapperuserExamMapper;AutowiredprivateExamMapperexamMapper;AutowiredprivateExamCacheManagerexamCacheManager;Overridepublicvoidenter(ExamDTOexamDTO,Stringtoken){//竞赛是否存在LongexamIdexamDTO.getExamId();ExamDOexamDOexamMapper.selectById(examId);if(nullexamDO){thrownewServiceException(ResultCodeEnum.EXAM_NOT_EXISTS);}//竞赛是否已经开赛if(examDO.getStartTime().isBefore(LocalDateTime.now())){thrownewServiceException(ResultCodeEnum.EXAM_STARTED);}//重复报名LonguserIdThreadLocalUtil.get(Constants.USER_ID,Long.class);UserExamDOuserExamDOuserExamMapper.selectOne(newLambdaQueryWrapperUserExamDO().eq(UserExamDO::getExamId,examId).eq(UserExamDO::getUserId,userId));if(null!userExamDO){thrownewServiceException(ResultCodeEnum.USER_EXAM_HAS_ENTER);}//存储redis mysql 数据examCacheManager.addUserExamCache(userId,examId);userExamDOnewUserExamDO();userExamDO.setUserId(userId);userExamDO.setExamId(examId);userExamMapper.insert(userExamDO);}}四、mapperoj-modules/oj-friend/src/main/java/com/yj/friend/mapper/user/UserExamMapper.javapackagecom.yj.friend.mapper.user;importcom.baomidou.mybatisplus.core.mapper.BaseMapper;importcom.yj.friend.domain.user.UserExamDO;importorg.apache.ibatis.annotations.Mapper;MapperpublicinterfaceUserExamMapperextendsBaseMapperUserExamDO{}实体类packagecom.yj.friend.domain.user;importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.annotation.TableId;importcom.baomidou.mybatisplus.annotation.TableName;importcom.fasterxml.jackson.databind.annotation.JsonSerialize;importcom.fasterxml.jackson.databind.ser.std.ToStringSerializer;importcom.yj.common.core.domain.BaseDO;importlombok.Getter;importlombok.Setter;importjava.time.LocalDateTime;/** * 用户竞赛报名表 实体类 * 对应数据库表tb_user_exam */GetterSetterTableName(tb_user_exam)publicclassUserExamDOextendsBaseDO{/** * 用户竞赛关系id主键 */JsonSerialize(usingToStringSerializer.class)TableId(valueuser_exam_id,typeIdType.ASSIGN_ID)privateLonguserExamId;/** * 用户id */JsonSerialize(usingToStringSerializer.class)privateLonguserId;/** * 竞赛id */JsonSerialize(usingToStringSerializer.class)privateLongexamId;/** * 得分 */privateIntegerscore;/** * 排名 */privateIntegerexamRank;}五、缓存更新方法oj-modules/oj-friend/src/main/java/com/yj/friend/manage/ExamCacheManager.java添加方法//添加用户报名竞赛缓存publicvoidaddUserExamCache(LonguserId,LongexamId){StringuserExamListKeygetUserExamListKey(userId);redisService.leftPushForList(userExamListKey,examId);}privateStringgetUserExamListKey(LonguserId){returnCacheConstants.USER_EXAM_LISTuserId;}oj-common/oj-common-core/src/main/java/com/yj/common/core/constants/CacheConstants.java缓存头publicfinalstaticStringUSER_EXAM_LISTu:e:l:;//用户竞赛列表六、获取当前登陆用户使用ThreadLocal当前线程变量存储用户userId即可存储在拦截器中每一个模块都需要进行。6.1 引入依赖oj-common/oj-common-core/pom.xmldependencygroupIdcom.alibaba/groupIdartifactIdtransmittable-thread-local/artifactIdversion2.14.4/version/dependency6.2 工具类oj-common/oj-common-core/src/main/java/com/yj/common/core/utils/ThreadLocalUtil.java提供设置、获取、删除方法。packagecom.yj.common.core.utils;importcn.hutool.core.util.StrUtil;importcom.alibaba.ttl.TransmittableThreadLocal;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;publicclassThreadLocalUtil{privatestaticfinalTransmittableThreadLocalMapString,ObjectTHREAD_LOCALnewTransmittableThreadLocal();publicstaticvoidset(Stringkey,Objectvalue){MapString,ObjectmapgetLocalMap();map.put(key,valuenull?StrUtil.EMPTY:value);}publicstaticTTget(Stringkey,ClassTclazz){MapString,ObjectmapgetLocalMap();return(T)map.getOrDefault(key,null);}publicstaticMapString,ObjectgetLocalMap(){MapString,ObjectmapTHREAD_LOCAL.get();if(mapnull){mapnewConcurrentHashMapString,Object();THREAD_LOCAL.set(map);}returnmap;}publicstaticvoidremove(){THREAD_LOCAL.remove();}}6.3 修改已有代码6.3.1 TokenServiceoj-common/oj-common-security/src/main/java/com/yj/common/security/service/TokenService.java提供根据token获取userIdpublicClaimsgetClaims(Stringtoken,Stringsecret){Claimsclaims;try{claimsJwtUtils.parseToken(token,secret);//获取令牌中信息 解析payload中信息if(claimsnull){log.error(解析Token: {}出现异常,token);returnnull;}}catch(Exceptione){log.error(解析Token: {}出现异常,token,e);returnnull;}returnclaims;}publicLonggetUserId(Claimsclaims){if(claimsnull)returnnull;returnLong.valueOf(JwtUtils.getUserId(claims));//获取jwt中的userId}6.3.2 拦截器TokenInterceptoroj-common/oj-common-security/src/main/java/com/yj/common/security/interceptor/TokenInterceptor.java将userId存入当前线程完成后删除。OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{StringtokengetToken(request);ClaimsclaimstokenService.getClaims(token,jwtSecret);LonguserIdtokenService.getUserId(claims);ThreadLocalUtil.set(Constants.USER_ID,userId);tokenService.extendToken(token,jwtSecret);returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{ThreadLocalUtil.remove();}oj-common/oj-common-core/src/main/java/com/yj/common/core/constants/Constants.javapublicstaticfinalStringUSER_IDuser_id;6.3.3 mybatis自动更新MyMetaObjectHandleroj-common/oj-common-mybatis/src/main/java/com/yj/common/mybatis/MyMetaObjectHandler.javapackagecom.yj.common.mybatis;importcom.baomidou.mybatisplus.core.handlers.MetaObjectHandler;importcom.yj.common.core.utils.ThreadLocalUtil;importorg.apache.ibatis.reflection.MetaObject;importorg.springframework.stereotype.Component;importjava.time.LocalDateTime;importcom.yj.common.core.constants.Constants;ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,createTime,LocalDateTime.class,LocalDateTime.now());//创建人 获取当前用户用户idthis.strictInsertFill(metaObject,createBy,Long.class,ThreadLocalUtil.get(Constants.USER_ID,Long.class));this.setFieldValByName(updateTime,LocalDateTime.now(),metaObject);this.setFieldValByName(updateBy,ThreadLocalUtil.get(Constants.USER_ID,Long.class),metaObject);}OverridepublicvoidupdateFill(MetaObjectmetaObject){this.setFieldValByName(updateTime,LocalDateTime.now(),metaObject);this.setFieldValByName(updateBy,ThreadLocalUtil.get(Constants.USER_ID,Long.class),metaObject);}}

相关新闻

RSS订阅源整理指南:官方RSS与RSSHub路由配置实战

RSS订阅源整理指南:官方RSS与RSSHub路由配置实战

自己动手整理了一份 RSS 订阅源清单,包含各家的官方 RSS 和大量 RSSHub 路由。这篇博文把整理的过程、筛选思路、部署配置、坑点全部写清楚,希望能帮你少走弯路。内容比较长,但每一步都是实际跑过的,可以直接照着抄。

2026/10/20 3:04:40 阅读更多 →
科研绘图工作流:从图表设计到投稿图片的自查清单

科研绘图工作流:从图表设计到投稿图片的自查清单

一篇论文的审稿意见里,最容易被忽略却最能影响第一印象的,往往是插图。我见过太多这样的场景:数据扎实、逻辑严密,结果图却配色刺眼、字体发虚、坐标轴刻度乱糟糟。审稿人还没读完正文,心里已经给这篇稿子打了折扣。反…

2026/10/20 3:03:41 阅读更多 →
C# OpenCVSharp圆形检测实战:霍夫圆算法、边缘检测与亚像素精修

C# OpenCVSharp圆形检测实战:霍夫圆算法、边缘检测与亚像素精修

有段时间我的工作台上摆着一批金属垫片,项目要求很简单:在来料图像里快速找到每个垫片的圆心,把圆心坐标和半径发给后续机械臂。听着不难,真正开始做才发现,圆这种东西在照片里非常容易变形——光照一变,边…

2026/10/20 3:04:40 阅读更多 →

最新新闻

信息缺失下的开源项目写作:两种正确路径

信息缺失下的开源项目写作:两种正确路径

我无法基于现有信息生成这篇博文。原因很简单:我缺乏对“nanoMuse”这个具体项目的可靠认知。该标题指向一个具体的开源项目,但我没有它的官方文档、代码仓库、功能特性等第一手资料。如果我强行生成一篇详细的技术剖析、实操步骤或原理讲解,…

2026/10/20 5:59:22 阅读更多 →
机器学习驱动裂缝扩展预测:从数据特征到物理约束的完整落地指南

机器学习驱动裂缝扩展预测:从数据特征到物理约束的完整落地指南

1. 项目思路拆解:为什么用机器学习啃“裂缝扩展”这块硬骨头裂缝扩展预测这个方向,搁在十年前基本是断裂力学和有限元仿真二分天下的局面。做结构健康监测的人都知道,工程构件在反复载荷下萌生裂纹、慢慢扩展到临界尺寸、最终断裂&#xff0c…

2026/10/20 5:59:22 阅读更多 →
PEMFC建模实战:从极化曲线到可运行的一维模型

PEMFC建模实战:从极化曲线到可运行的一维模型

1. 从“一张极化曲线”说起:我为什么盯上了PEMFC建模做了几年燃料电池相关的电化学测试,说实话,我最大的感触是:实验台上测出来的极化曲线从来不是“测”出来的,而是“凑”出来的。你调了增湿温度、改了背压、换了膜电…

2026/10/20 5:59:22 阅读更多 →
Kubernetes Dashboard从部署到安全加固的完整落地指南

Kubernetes Dashboard从部署到安全加固的完整落地指南

把Kubernetes集群用熟之后,我最大的一个感受是:命令行确实高效,但它不适合“看”。集群里跑了几十个工作负载、上百个Pod的时候,用kubectl一条一条地查状态、翻日志、看事件,效率低得令人抓狂。Kubernetes Dashboard就…

2026/10/20 5:59:22 阅读更多 →
46MB网页端抠图工具,免显卡免Python一键抠任意主体

46MB网页端抠图工具,免显卡免Python一键抠任意主体

做内容这些年,谁没为抠图头疼过?尤其是随手要发个封面、做张白底图、给产品换个背景的时候,开专业软件太重,在线工具又动不动有水印。最近我盯上这个网页端抠图工具的新版本,模型体积还是 46MB,但功能描述很…

2026/10/20 5:59:22 阅读更多 →
图表设计方法论:从需求到成图的完整链路与实操技巧

图表设计方法论:从需求到成图的完整链路与实操技巧

1. 从"diagram-design"这个标题里能读出什么第一次看到"diagram-design"这个标题,我脑子里蹦出来的第一个判断是:这大概率不是一个单纯的"画图工具推荐",而是一套关于图表设计方法论的沉淀。为什么这么说&…

2026/10/20 5:58:21 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →