|
文章目录1.数据库表设计1.practice_set套卷2.practice_set_detail套卷细节3.practice_info练习信息4.practice_detail练习详情5.E-R图2.架构设计(三层架构)3.练题微服务架构搭建1.创建一个练题微服务模块1.创建一个maven项目2.把src删除,只留pom.xml2.微服务父模块的pom.xml配置1.配置packaging为pom,指定编译版本,并统一指定SpringBoot版本,统一配置阿里云仓库,使子模块继承2.type和scope解释1.type2.scope3.创建一个练题微服务的api模块1.创建一个maven项目,删除resource目录和test目录2.创建一个common包存放Result和Page相关的1.目录结构2.PageInfo.java3.PageResult.java4.Result.java5.ResultCodeEnum.java6.引入lombok的依赖3.创建一个通用的枚举包1.结构2.IsDeleteFlagEnum.java4.创建一个req和vo分别存放入参和出参实体结构4.创建一个server子模块1.创建一个maven项目2.创建一个config包,暂时先存放mybatis的东西1.结构2.SqlStatementInterceptor.javasql状态拦截器3.MybatisPlusAllSqlLog.javasql转换器4.MybatisConfiguration.java注册两个拦截器3.引入基本依赖4.config下创建一个redis包,存放redis配置和工具类1.结构2.RedisConfig.java3.RedisUtil.java5.config下创建登录拦截器和上下文将从Header中获取logId放到ThreadLocal中1.结构2.GlobalConfig.javamvc的全局处理,空值不返回,存放自定义拦截器3.LoginContextHolder.javaThreadLocal工具类4.LoginInterceptor.java登录拦截器,从Header中获取logId放到ThreadLocal6.创建controller包1.结构2.DemoController.java测试7.创建其余的包1.结构2.DruidEncryptUtil.java用于对yaml中的东西加解密8.创建启动类1.PracticeApplication.java注意写MapperScan和ComponentScan,还有启动类注解9.创建配置文件1.resource创建一个mapper文件夹2.application.yml3.bootstrap.yml4.log4j2-spring.xml10.启动测试,一次成功!1.数据库表设计1.practice_set套卷createtablepractice_set(idbigintauto_incrementcomment'主键'primarykey,set_namevarchar(255)nullcomment'套题名称',set_typeintnullcomment'套题类型1实时生成2预设套题',set_heatintnullcomment'热度',set_descvarchar(255)nullcomment'套题描述',primary_category_idbigintnullcomment'大类id',created_byvarchar(32)charsetutf8nullcomment'创建人',created_timedatetimenullcomment'创建时间',update_byvarchar(32)charsetutf8nullcomment'更新人',update_timedatetimenullcomment'更新时间',is_deletedintdefault0nullcomment'是否被删除0为删除1已删除')comment'套题信息表'collate=utf8mb4_bin;12345678910111213141516172.practice_set_detail套卷细节createtablepractice_set_detail(idbigintauto_incrementcomment'主键'primarykey,set_idbigintnotnullcomment'套题id',subject_idbigintnullcomment'题目id',subject_typeintnullcomment'题目类型',created_byvarchar(32)charsetutf8nullcomment'创建人',created_timedatetimenullcomment'创建时间',update_byvarchar(32)charsetutf8nullcomment'更新人',update_timedatetimenullcomment'更新时间',is_deletedintdefault0nullcomment'是否被删除0为删除1已删除')comment'套题内容表'collate=utf8mb4_bin;12345678910111213143.practice_info练习信息createtablepractice_info(idbigintauto_incrementcomment'主键'primarykey,set_idbigintnullcomment'套题id',complete_statusintnullcomment'是否完成1完成0未完成',time_usevarchar(32)nullcomment'用时',submit_timedatetimenullcomment'交卷时间',correct_ratedecimal(10,2)nullcomment'正确率',created_byvarchar(32)charsetutf8nullcomment'创建人',created_timedatetimenullcomment'创建时间',update_byvarchar(32)charsetutf8nullcomment'更新人',update_timedatetimenullcomment'更新时间',is_deletedintdefault0nullcomment'是否被删除0为删除1已删除')comment'练习表'collate=utf8mb4_bin;123456789101112131415164.practice_detail练习详情createtablepractice_detail(idbigintauto_incrementcomment'主键'primarykey,practice_idbigintnullcomment'练题id',subject_idbigintnullcomment'题目id',subject_typeintnullcomment'题目类型',answer_statusintnullcomment'回答状态',answer_contentvarchar(64)nullcomment'回答内容',created_byvarchar(32)charsetutf8nullcomment'创建人',created_timedatetimenullcomment'创建时间',update_byvarchar(32)charsetutf8nullcomment'更新人',update_timedatetimenullcomment'更新时间',is_deletedintdefault0nullcomment'是否被删除0为删除1已删除')comment'练习详情表'collate=utf8mb4_bin;123456789101112131415165.E-R图2.架构设计(三层架构)3.练题微服务架构搭建1.创建一个练题微服务模块1.创建一个maven项目2.把src删除,只留pom.xml2.微服务父模块的pom.xml配置1.配置packaging为pom,指定编译版本,并统一指定SpringBoot版本,统一配置阿里云仓库,使子模块继承
4.0.0com.sun.club1.0-SNAPSHOT
pom
88
UTF-8org.springframework.boot2.4.2pomimportcentralaliyunmavenhttp://maven.aliyun.com/nexus/content/groups/public/defaulttruetrue1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950512.type和scope解释1.type:在Maven中,type元素指定了依赖项的包装类型。默认情况下,如果不指定type,Maven会假定它是一个jar文件。在你提供的示例中,type被设置为pom。这意味着被引入的依赖是一个POM类型的项目,通常用于依赖管理而非包含实际的代码库。这种类型的依赖通常用于声明一组库的版本管理,而不是作为代码库直接参与构建。2.scope:scope元素定义了依赖的使用范围。不同的scope值决定了依赖在项目的不同构建阶段以及不同模块间的可见性。常见的scope包括:compile:默认值,表示依赖在编译阶段和运行阶段都是必需的,且会被传递到依赖的项目。runtime:表示依赖不需要在编译阶段,但在运行时需要。provided:表示依赖在编译和测试时需要,但在运行时不需要,因为运行环境已提供该依赖。test:表示依赖仅在测试阶段需要,用于编译和运行测试代码。import(正如你的例子中所用):这是一个特殊的scope,用于只在中有效。它表示当前POM是从其他POM中导入依赖管理信息,通常用于继承和共享一组依赖定义。通过import,可以将其他项目的依赖版本管理集成到自己的项目中,从而保持依赖版本的一致性和可管理性。3.创建一个练题微服务的api模块1.创建一个maven项目,删除resource目录和test目录2.创建一个common包存放Result和Page相关的1.目录结构2.PageInfo.javapackagecom.sunxiansheng.practice.api.common;importjava.util.Objects;/***Description:分页请求的入参*@Authorsun*@Create2024/5/2816:25*@Version1.1*/publicclassPageInfo{privateIntegerpageNo=1;privateIntegerpageSize=20;publicIntegergetPageNo(){return(pageNo==null||pageNo{//当前页码,默认为1privateIntegerpageNo=1;//每页显示的记录数,默认为20privateIntegerpageSize=20;//总记录条数privateIntegertotal=0;//总页数privateIntegertotalPages=0;//当前页的记录列表privateListresult=Collections.emptyList();//表示当前页是从分页查询结果的第几条记录开始,下标从1开始privateIntegerstart=1;//表示当前页是从分页查询结果的第几条记录结束,下标从1开始privateIntegerend=0;//====================分页查询只需要设置这几个值即可====================//设置当前页码,并重新计算起始和结束位置publicPageResultsetPageNo(IntegerpageNo){this.pageNo=Objects.requireNonNull(pageNo,"Pagenumbercannotbenull");calculateStartAndEnd();returnthis;}//设置每页记录数,并重新计算起始和结束位置publicPageResultsetPageSize(IntegerpageSize){this.pageSize=Objects.requireNonNull(pageSize,"Pagesizecannotbenull");calculateStartAndEnd();returnthis;}//设置当前页的记录列表publicPageResultsetRecords(Listresult){this.result=Objects.requireNonNull(result,"Resultlistcannotbenull");returnthis;}//设置总记录条数,并重新计算总页数和起始结束位置publicPageResultsetTotal(Integertotal){this.total=Objects.requireNonNull(total,"Totalcountcannotbenull");calculateTotalPages();calculateStartAndEnd();returnthis;}//====================分页查询只需要设置这几个值即可====================//计算总页数privatevoidcalculateTotalPages(){if(this.pageSize>0){this.totalPages=(this.total/this.pageSize)+(this.total%this.pageSize==0?0:1);}else{this.totalPages=0;}}//计算起始和结束位置privatevoidcalculateStartAndEnd(){if(this.pageSize>0){this.start=(this.pageNo-1)*this.pageSize+1;this.end=Math.min(this.pageNo*this.pageSize,this.total);}else{this.start=1;this.end=this.total;}}publicIntegergetStart(){returnstart;}//获取每页记录数publicIntegergetPageSize(){returnpageSize;}publicIntegergetPageNo(){returnpageNo;}publicIntegergetTotal(){returntotal;}publicIntegergetTotalPages(){returntotalPages;}publicListgetResult(){returnresult;}publicIntegergetEnd(){returnend;}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalseageResultthat=(PageResult)o;returnObjects.equals(pageNo,that.pageNo)&Objects.equals(pageSize,that.pageSize)&Objects.equals(total,that.total)&Objects.equals(totalPages,that.totalPages)&Objects.equals(result,that.result)&Objects.equals(start,that.start)&Objects.equals(end,that.end);}@OverridepublicinthashCode(){returnObjects.hash(pageNo,pageSize,total,totalPages,result,start,end);}@OverridepublicStringtoString(){return"PageResult{"+"pageNo="+pageNo+",pageSize="+pageSize+",total="+total+",totalPages="+totalPages+",result="+result+",start="+start+",end="+end+'}';}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471484.Result.javapackagecom.sunxiansheng.practice.api.common;importlombok.Data;/***Description:*@Authorsun*@Create2024/5/249:48*@Version1.0*/@DatapublicclassResult{privateBooleansuccess;privateIntegercode;privateStringmessage;privateTdata;/***成功返回结果*@return*/publicstaticResultok(){Resultresult=newResult();result.setSuccess(true);result.setCode(ResultCodeEnum.SUCCESS.getCode());result.setMessage(ResultCodeEnum.SUCCESS.getDesc());returnresult;}/***成功返回结果,携带数据*@paramdata*@return*@param*/publicstaticResultok(Tdata){Resultresult=newResult();result.setSuccess(true);result.setCode(ResultCodeEnum.SUCCESS.getCode());result.setMessage(ResultCodeEnum.SUCCESS.getDesc());result.setData(data);returnresult;}/***失败返回结果*@return*/publicstaticResultfail(){Resultresult=newResult();result.setSuccess(false);result.setCode(ResultCodeEnum.FAIL.getCode());result.setMessage(ResultCodeEnum.FAIL.getDesc());returnresult;}/***失败,携带数据*@paramdata*@return*@param*/publicstaticResultfail(Tdata){Resultresult=newResult();result.setSuccess(false);result.setCode(ResultCodeEnum.FAIL.getCode());result.setMessage(ResultCodeEnum.FAIL.getDesc());result.setData(data);returnresult;}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475765.ResultCodeEnum.javapackagecom.sunxiansheng.practice.api.common;importlombok.Getter;/***Description:返回结果枚举*@Authorsun*@Create2024/5/249:53*@Version1.0*/@GetterpublicenumResultCodeEnum{SUCCESS(200,"成功"),FAIL(500,"失败");publicintcode;publicStringdesc;ResultCodeEnum(intcode,Stringdesc){this.code=code;this.desc=desc;}/***根据code获取枚举*@paramcode*@return*/publicstaticResultCodeEnumgetByCode(intcode){for(ResultCodeEnumvalue:values()){if(value.code==code){returnvalue;}}returnnull;}}123456789101112131415161718192021222324252627282930313233343536376.引入lombok的依赖org.projectlombok1.18.1612345673.创建一个通用的枚举包1.结构2.IsDeleteFlagEnum.javapackagecom.sunxiansheng.practice.api.enums;importlombok.Getter;/***Description:删除标识枚举*@Authorsun*@Create2024/5/249:53*@Version1.0*/@GetterpublicenumIsDeleteFlagEnum{DELETED(1,"已删除"),UN_DELETED(0,"未删除");publicintcode;publicStringdesc;IsDeleteFlagEnum(intcode,Stringdesc){this.code=code;this.desc=desc;}/***根据code获取枚举*@paramcode*@return*/publicstaticIsDeleteFlagEnumgetByCode(intcode){for(IsDeleteFlagEnumvalue:values()){if(value.code==code){returnvalue;}}returnnull;}}123456789101112131415161718192021222324252627282930313233343536374.创建一个req和vo分别存放入参和出参实体结构4.创建一个server子模块1.创建一个maven项目2.创建一个config包,暂时先存放mybatis的东西1.结构2.SqlStatementInterceptor.javasql状态拦截器packagecom.sunxiansheng.practice.server.config.mybatis;importorg.apache.ibatis.cache.CacheKey;importorg.apache.ibatis.executor.Executor;importorg.apache.ibatis.mapping.BoundSql;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.plugin.*;importorg.apache.ibatis.session.ResultHandler;importorg.apache.ibatis.session.RowBounds;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.Properties;@Intercepts({@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class}),@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class,CacheKey.class,BoundSql.class})})publicclassSqlStatementInterceptorimplementsInterceptor{publicstaticfinalLoggerlog=LoggerFactory.getLogger("sys-sql");@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{longstartTime=System.currentTimeMillis();try{returninvocation.proceed();}finally{longtimeConsuming=System.currentTimeMillis()-startTime;log.info("执行SQL:{}ms",timeConsuming);if(timeConsuming>999&timeConsuming=5000&timeConsuming=10000){log.info("执行SQL大于10s:{}ms",timeConsuming);}}}@OverridepublicObjectplugin(Objecttarget){returnPlugin.wrap(target,this);}@OverridepublicvoidsetProperties(Propertiesproperties){}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051523.MybatisPlusAllSqlLog.javasql转换器packagecom.sunxiansheng.practice.server.config.mybatis;importcom.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;importorg.apache.ibatis.executor.Executor;importorg.apache.ibatis.mapping.BoundSql;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.mapping.ParameterMapping;importorg.apache.ibatis.reflection.MetaObject;importorg.apache.ibatis.session.Configuration;importorg.apache.ibatis.session.ResultHandler;importorg.apache.ibatis.session.RowBounds;importorg.apache.ibatis.type.TypeHandlerRegistry;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.util.CollectionUtils;importjava.sql.SQLException;importjava.text.DateFormat;importjava.util.Date;importjava.util.List;importjava.util.Locale;importjava.util.regex.Matcher;publicclassMybatisPlusAllSqlLogimplementsInnerInterceptor{publicstaticfinalLoggerlog=LoggerFactory.getLogger("sys-sql");@OverridepublicvoidbeforeQuery(Executorexecutor,MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{logInfo(boundSql,ms,parameter);}@OverridepublicvoidbeforeUpdate(Executorexecutor,MappedStatementms,Objectparameter)throwsSQLException{BoundSqlboundSql=ms.getBoundSql(parameter);logInfo(boundSql,ms,parameter);}privatestaticvoidlogInfo(BoundSqlboundSql,MappedStatementms,Objectparameter){try{log.info("parameter="+parameter);//获取到节点的id,即sql语句的idStringsqlId=ms.getId();log.info("sqlId="+sqlId);//获取节点的配置Configurationconfiguration=ms.getConfiguration();//获取到最终的sql语句Stringsql=getSql(configuration,boundSql,sqlId);log.info("完整的sql:{}",sql);}catch(Exceptione){log.error("异常:{}",e.getLocalizedMessage(),e);}}//封装了一下sql语句,使得结果返回完整xml路径下的sql语句节点id+sql语句publicstaticStringgetSql(Configurationconfiguration,BoundSqlboundSql,StringsqlId){returnsqlId+":"+showSql(configuration,boundSql);}//进行?的替换publicstaticStringshowSql(Configurationconfiguration,BoundSqlboundSql){//获取参数ObjectparameterObject=boundSql.getParameterObject();List
parameterMappings=boundSql.getParameterMappings();//sql语句中多个空格都用一个空格代替Stringsql=boundSql.getSql().replaceAll("[\\s]+","");if(!CollectionUtils.isEmpty(parameterMappings)¶meterObject!=null){//获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换TypeHandlerRegistrytypeHandlerRegistry=configuration.getTypeHandlerRegistry();//如果根据parameterObject.getClass()可以找到对应的类型,则替换if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){sql=sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(parameterObject)));}else{//MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作MetaObjectmetaObject=configuration.newMetaObject(parameterObject);for(ParameterMappingparameterMapping:parameterMappings){StringpropertyName=parameterMapping.getProperty();if(metaObject.hasGetter(propertyName)){Objectobj=metaObject.getValue(propertyName);sql=sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));}elseif(boundSql.hasAdditionalParameter(propertyName)){//该分支是动态sqlObjectobj=boundSql.getAdditionalParameter(propertyName);sql=sql.replaceFirst("\\?",Matcher.quoteReplacement(getParameterValue(obj)));}else{//打印出缺失,提醒该参数缺失并防止错位sql=sql.replaceFirst("\\?","缺失");}}}}returnsql;}//如果参数是String,则添加单引号,如果是日期,则转换为时间格式器并加单引号;对参数是null和不是null的情况作了处理privatestaticStringgetParameterValue(Objectobj){Stringvalue;if(objinstanceofString){value="'"+obj.toString()+"'";}elseif(objinstanceofDate){DateFormatformatter=DateFormat.getDateTimeInstance(DateFormat.DEFAULT,DateFormat.DEFAULT,Locale.CHINA);value="'"+formatter.format(newDate())+"'";}else{if(obj!=null){value=obj.toString();}else{value="";}}returnvalue;}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151164.MybatisConfiguration.java注册两个拦截器packagecom.sunxiansheng.practice.server.config.mybatis;importcom.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassMybatisConfiguration{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptormybatisPlusInterceptor=newMybatisPlusInterceptor();mybatisPlusInterceptor.addInnerInterceptor(newMybatisPlusAllSqlLog());returnmybatisPlusInterceptor;}}1234567891011121314151617183.引入基本依赖
4.0.0
com.sun.club1.0-SNAPSHOT
1.81.81.8
UTF-8
UTF-82.4.22021.12020.0.6org.springframework.boot2.4.2org.springframework.bootorg.projectlombok1.18.16org.mapstruct1.4.2.Finalorg.mapstruct1.4.2.Finalorg.springframework.boot2.4.2com.alibaba1.2.24com.google.guava19.0org.apache.commons3.11com.google.code.gson2.8.6org.springframework.boot2.4.2org.apache.commons2.9.0org.springframework.boot2.4.2com.alibaba1.1.22mysql8.0.22com.baomidou3.4.0com.alibaba.cloudorg.springframework.cloudcom.alibaba.cloudorg.springframework.cloud${spring-cloud.version}pomimportorg.springframework.boot${spring-boot.version}pomimportcom.alibaba.cloud${spring-cloud-alibaba.version}pomimport${project.artifactId}
org.springframework.boot2.3.0.RELEASErepackage1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931944.config下创建一个redis包,存放redis配置和工具类1.结构2.RedisConfig.javapackagecom.sunxiansheng.practice.server.config.redis;importcom.fasterxml.jackson.annotation.JsonAutoDetect;importcom.fasterxml.jackson.annotation.JsonTypeInfo;importcom.fasterxml.jackson.annotation.PropertyAccessor;importcom.fasterxml.jackson.databind.DeserializationFeature;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.RedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;/***Description:原生redis的template的序列化器会产生乱码问题,重写改为jackson*@Authorsun*@Create2024/6/514:16*@Version1.0*/@ConfigurationpublicclassRedisConfig{@BeanpublicRedisTemplateredisTemplate(RedisConnectionFactoryredisConnectionFactory){RedisTemplateredisTemplate=newRedisTemplate();RedisSerializerredisSerializer=newStringRedisSerializer();redisTemplate.setConnectionFactory(redisConnectionFactory);redisTemplate.setKeySerializer(redisSerializer);redisTemplate.setHashKeySerializer(redisSerializer);redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());returnredisTemplate;}privateJackson2JsonRedisSerializerjackson2JsonRedisSerializer(){Jackson2JsonRedisSerializerjsonRedisSerializer=newJackson2JsonRedisSerializer(Object.class);ObjectMapperobjectMapper=newObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);jsonRedisSerializer.setObjectMapper(objectMapper);returnjsonRedisSerializer;}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546473.RedisUtil.javapackagecom.sunxiansheng.practice.server.config.redis;importlombok.extern.slf4j.Slf4j;importorg.springframework.data.redis.core.Cursor;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.core.ScanOptions;importorg.springframework.data.redis.core.ZSetOperations;importorg.springframework.stereotype.Component;importjavax.annotation.Resource;importjava.util.HashMap;importjava.util.Map;importjava.util.Set;importjava.util.concurrent.TimeUnit;importjava.util.stream.Collectors;importjava.util.stream.Stream;/***Description:RedisUtil工具类*@Authorsun*@Create2024/6/514:17*@Version1.0*/@Component@Slf4jpublicclassRedisUtil{@ResourceprivateRedisTemplateredisTemplate;privatestaticfinalStringCACHE_KEY_SEPARATOR=".";/***构建缓存key*@paramstrObjs*@return*/publicStringbuildKey(String...strObjs){returnStream.of(strObjs).collect(Collectors.joining(CACHE_KEY_SEPARATOR));}/***是否存在key*@paramkey*@return*/publicbooleanexist(Stringkey){returnredisTemplate.hasKey(key);}/***删除key*@paramkey*@return*/publicbooleandel(Stringkey){returnredisTemplate.delete(key);}publicvoidset(Stringkey,Stringvalue){redisTemplate.opsForValue().set(key,value);}publicbooleansetNx(Stringkey,Stringvalue,Longtime,TimeUnittimeUnit){returnredisTemplate.opsForValue().setIfAbsent(key,value,time,timeUnit);}publicStringget(Stringkey){return(String)redisTemplate.opsForValue().get(key);}publicBooleanzAdd(Stringkey,Stringvalue,Longscore){returnredisTemplate.opsForZSet().add(key,value,Double.valueOf(String.valueOf(score)));}publicLongcountZset(Stringkey){returnredisTemplate.opsForZSet().size(key);}publicSetrangeZset(Stringkey,longstart,longend){returnredisTemplate.opsForZSet().range(key,start,end);}publicLongremoveZset(Stringkey,Objectvalue){returnredisTemplate.opsForZSet().remove(key,value);}publicvoidremoveZsetList(Stringkey,Setvalue){value.stream().forEach((val)->redisTemplate.opsForZSet().remove(key,val));}publicDoublescore(Stringkey,Objectvalue){returnredisTemplate.opsForZSet().score(key,value);}publicSetrangeByScore(Stringkey,longstart,longend){returnredisTemplate.opsForZSet().rangeByScore(key,Double.valueOf(String.valueOf(start)),Double.valueOf(String.valueOf(end)));}/***可以使用这个来实现排行榜,指定键就相当于指定了一个排行榜,再指定成员和分数,则会给排行榜中的这个成员加分数*@paramkeyzset的键*@paramobj成员,一般为用户的唯一标识*@paramscore分数*@return*/publicObjectaddScore(Stringkey,Objectobj,doublescore){returnredisTemplate.opsForZSet().incrementScore(key,obj,score);}publicObjectrank(Stringkey,Objectobj){returnredisTemplate.opsForZSet().rank(key,obj);}/***从Redis有序集合(SortedSet)中按分数范围获取成员及其分数*@paramkey排行榜的key*@paramstart起始位置(包含)*@paramend结束位置(包含)*@returnSet>:每个TypedTuple对象包含以下内容:value:集合中的成员,score:成员的分数。*/publicSet>rankWithScore(Stringkey,longstart,longend){Set>set=redisTemplate.opsForZSet().reverseRangeWithScores(key,start,end);returnset;}/***向Redis中的hash结构存储数据*@paramkey一个hash结构的key*@paramhashKeyhash中的小key*@paramhashValhash中的小value*/publicvoidputHash(Stringkey,StringhashKey,ObjecthashVal){redisTemplate.opsForHash().put(key,hashKey,hashVal);}/***Redis中的String类型,获取value时将其转换为int类型*@paramkey*@return*/publicIntegergetInt(Stringkey){return(Integer)redisTemplate.opsForValue().get(key);}/***Redis中的String类型,将value增加一*@paramkey*@paramcount*@return*/publicvoidincrement(Stringkey,Integercount){redisTemplate.opsForValue().increment(key,count);}/***Redis中的hash类型,根据key来将每一个hashKey和hashValue转换为Map类型*@paramkey*@return*/publicMapgetHashAndDelete(Stringkey){Mapmap=newHashMap();//扫描hash,指定每一个Entry的类型,这里返回的就是Map的游标,可以进行遍历Cursor>cursor=redisTemplate.opsForHash().scan(key,ScanOptions.NONE);//遍历每一条数据,放到map中while(cursor.hasNext()){Map.Entrynext=cursor.next();ObjecthashKey=next.getKey();ObjecthashValue=next.getValue();map.put(hashKey,hashValue);//每遍历一条就删除redisTemplate.opsForHash().delete(key,hashKey);}returnmap;}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751765.config下创建登录拦截器和上下文将从Header中获取logId放到ThreadLocal中1.结构2.GlobalConfig.javamvc的全局处理,空值不返回,存放自定义拦截器packagecom.sunxiansheng.practice.server.config;importcom.fasterxml.jackson.annotation.JsonInclude;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.SerializationFeature;importcom.sunxiansheng.practice.server.config.interceptor.LoginInterceptor;importorg.springframework.context.annotation.Configuration;importorg.springframework.http.converter.HttpMessageConverter;importorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;importjava.util.List;/***mvc的全局处理*/@ConfigurationpublicclassGlobalConfigextendsWebMvcConfigurationSupport{@OverrideprotectedvoidconfigureMessageConverters(List>converters){super.configureMessageConverters(converters);converters.add(mappingJackson2HttpMessageConverter());}/***自定义mappingJackson2HttpMessageConverter*目前实现:空值忽略,空字段可返回*/privateMappingJackson2HttpMessageConvertermappingJackson2HttpMessageConverter(){ObjectMapperobjectMapper=newObjectMapper();objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false);objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);returnnewMappingJackson2HttpMessageConverter(objectMapper);}/***将自定义拦截器放进去*@paramregistry*/@OverrideprotectedvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(newLoginInterceptor());}}1234567891011121314151617181920212223242526272829303132333435363738394041424344453.LoginContextHolder.javaThreadLocal工具类packagecom.sunxiansheng.practice.server.config.context;importjava.util.Map;importjava.util.Objects;importjava.util.concurrent.ConcurrentHashMap;/***Description:上下文对象(ThreadLocal)*@Authorsun*@Create2024/6/1516:27*@Version1.0*/publicclassLoginContextHolder{//这个ThreadLocal持有一个MapprivatestaticfinalInheritableThreadLocal>THREAD_LOCAL=newInheritableThreadLocal();/***为ThreadLocal持有的Map设值*@paramkey*@paramval*/publicstaticvoidset(Stringkey,Objectval){Mapmap=getThreadLocalMap();map.put(key,val);}/***从ThreadLocal持有的Map取值*@paramkey*@return*/publicstaticObjectget(Stringkey){Mapmap=THREAD_LOCAL.get();returnmap.get(key);}/***清除ThreadLocal*/publicstaticvoidremove(){THREAD_LOCAL.remove();}/***初始化一个ThreadLocal持有的Map,要保证这个Map是单例的*@return*/publicstaticMapgetThreadLocalMap(){//获取到ThreadLocal的MapMapmap=THREAD_LOCAL.get();//如果是空的再创建一个Map,然后放进去if(Objects.isNull(map)){map=newConcurrentHashMap();THREAD_LOCAL.set(map);}//放到ThreadLocal中returnmap;}//以下为获取用户信息的方法publicstaticStringgetLoginId(){return(String)getThreadLocalMap().get("loginId");}}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667684.LoginInterceptor.java登录拦截器,从Header中获取logId放到ThreadLocalpackagecom.sunxiansheng.practice.server.config.interceptor;importcom.sunxiansheng.practice.server.config.context.LoginContextHolder;importorg.apache.commons.lang3.StringUtils;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;/***Description:处理用户上下文的拦截器*@Authorsun*@Create2024/6/1516:20*@Version1.0*/publicclassLoginInterceptorimplementsHandlerInterceptor{/***当请求到这里了,就说明,网关已经将用户的loginId放到了Header里了*@paramrequest*@paramresponse*@paramhandler*@return*@throwsException*/@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{StringloginId=request.getHeader("loginId");if(StringUtils.isNotBlank(loginId)){//将loginId放到ThreadLocal里面LoginContextHolder.set("loginId",loginId);}returntrue;}/***在操作结束后清除ThreadLocal,因为如果线程复用并且没清除,这个ThreadLocal还会存在,造成数据污染*@paramrequest*@paramresponse*@paramhandler*@paramex*@throwsException*/@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex)throwsException{LoginContextHolder.remove();}}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748496.创建controller包1.结构2.DemoController.java测试packagecom.sunxiansheng.practice.server.controller;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;/***Description:测试controller*@Authorsun*@Create2024/6/2515:38*@Version1.0*/@RestController@RequestMapping("/practice/")@Slf4jpublicclassDemoController{@RequestMapping("test")publicStringisLogin(){return"test";}}12345678910111213141516171819202122237.创建其余的包1.结构2.DruidEncryptUtil.java用于对yaml中的东西加解密packagecom.sunxiansheng.practice.server.util;importcom.alibaba.druid.filter.config.ConfigTools;importjava.security.NoSuchAlgorithmException;importjava.security.NoSuchProviderException;/***数据库加密util*/publicclassDruidEncryptUtil{privatestaticStringpublicKey;privatestaticStringprivateKey;static{try{String[]keyPair=ConfigTools.genKeyPair(512);privateKey=keyPair[0];System.out.println("privateKey:"+privateKey);publicKey=keyPair[1];System.out.println("publicKey:"+publicKey);}catch(NoSuchAlgorithmExceptione){e.printStackTrace();}catch(NoSuchProviderExceptione){e.printStackTrace();}}publicstaticStringencrypt(StringplainText)throwsException{Stringencrypt=ConfigTools.encrypt(privateKey,plainText);System.out.println("encrypt:"+encrypt);returnencrypt;}publicstaticStringdecrypt(StringencryptText)throwsException{Stringdecrypt=ConfigTools.decrypt(publicKey,encryptText);System.out.println("decrypt:"+decrypt);returndecrypt;}publicstaticvoidmain(String[]args)throwsException{Stringencrypt=encrypt("123456");System.out.println("encrypt:"+encrypt);}}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647488.创建启动类1.PracticeApplication.java注意写MapperScan和ComponentScan,还有启动类注解packagecom.sunxiansheng.practice.server;importorg.mybatis.spring.annotation.MapperScan;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.ComponentScan;/***Description:练题模块*@Authorsun*@Create2024/6/2515:48*@Version1.0*/@SpringBootApplication//扫描mapper接口@MapperScan("com.sunxiansheng.**.dao")//扫描service和controller注解@ComponentScan("com.sunxiansheng")publicclassPracticeApplication{publicstaticvoidmain(String[]args){SpringApplication.run(PracticeApplication.class,args);}}123456789101112131415161718192021222324259.创建配置文件1.resource创建一个mapper文件夹2.application.ymlserver:port:3012#DataSourceConfigspring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql:///sun_club?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=falseusername:password:N2THnj7YlFIA4zrfxaOq1tBpLjnG3NTOM4BL6kJMMSSoTW9xE/jNW+xjtLotTXZjKw6Jk1eDbW6BjCgTMDnTbA==#加密后的密码type:com.alibaba.druid.pool.DruidDataSource#druid连接池druid:connectionProperties:config.decrypt=true;config.decrypt.key=${publicKey};#开启配置解密,读取公匙initial-size:20#初始化连接数min-idle:20#最小连接数max-active:100#最大连接数max-wait:60000#最大等待时间,单位毫秒stat-view-servlet:enabled:true#是否开启监控url-pattern:/druid/*#监控路径login-username:#登录用户名login-password:#登录密码filter:stat:enabled:true#是否开启慢sql监控slow-sql-millis:2000#慢sql阈值,单位毫秒log-slow-sql:true#是否打印慢sqlwall:enabled:true#是否开启防火墙config:enabled:true#开启配置,可以解密redis:password:#Redis服务器密码database:0#默认数据库为0号timeout:10000ms#连接超时时间是10000毫秒lettuce:pool:max-active:8#最大活跃连接数,使用负值表示没有限制,最佳配置为核数*2max-wait:10000ms#最大等待时间,单位为毫秒,使用负值表示没有限制,这里设置为10秒max-idle:200#最大空闲连接数min-idle:5#最小空闲连接数cluster:nodes:logging:config:classpath:log4j2-spring.xml#日志配置文件publicKey:/hiiSS5+2angp9vKAt3Dn71mVJAp/cKcoqrtERZqcr0+/aGtsE+JOlQgquOs5cCAwEAAQ==mybatis-plus:configuration:log-implrg.apache.ibatis.logging.stdout.StdOutImpl#打印sql123456789101112131415161718192021222324252627282930313233343536373839404142434445464748493.bootstrap.ymlspring:application:name:sub-club-practice#服务名称profiles:active:dev#激活的环境cloud:nacos:config:server-addr::8848#Nacos地址prefix{spring.application.name}#配置前缀为服务名,sub-club-practice-dev为配置文件名groupEFAULT_GROUP#配置分组namespace:#命名空间,如果在public命名空间则不需要配置file-extension:yamldiscovery:enabled:true#启用服务发现server-addr::8848#Nacos地址123456789101112131415164.log4j2-spring.xml
|
|