目录一、功能分析二、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);}}