一。优雅的进行线程池异常处理 在Java开发中,线程池的使用必不可少,使用无返回值execute()方法时,线程执行发生异常的话,需要记录日志,方便回溯,一般做法是在线程执行方法内trycatch处理,如下:Testpublicvoidtest()throwsException{ThreadPoolExecutorthreadPoolExecutornewThreadPoolExecutor(5,10,60,TimeUnit。SECONDS,newArrayBlockingQueue(100000));FutureIntegersubmitthreadPoolExecutor。execute((){try{inti10;}catch(Exceptione){log。error(e。getMessage(),e);}});}复制代码 但是当线程池调用方法很多时,那么每个线程执行方法内都要trycatch处理,这就不优雅了,其实ThreadPoolExecutor类还支持传入ThreadFactory参数,自定义线程工厂,在创建thread时,指定setUncaughtExceptionHandler异常处理方法,这样就可以做到全局处理异常了,代码如下:ThreadFactorythreadFactoryr{ThreadthreadnewThread(r);thread。setUncaughtExceptionHandler((t,e){记录线程异常log。error(e。getMessage(),e);});};ThreadPoolExecutorthreadPoolExecutornewThreadPoolExecutor(5,10,60,TimeUnit。SECONDS,newArrayBlockingQueue(100000),threadFactory);threadPoolExecutor。execute((){log。info();inti10;});复制代码二。线程池决绝策略设置错误导致业务接口执行超时 先介绍下线程池得四种决绝策略AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,这是线程池默认的拒绝策略DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。CallerRunsPolicy:由调用线程处理该任务 如下是一个线上业务接口使用得线程池配置,决绝策略采用CallerRunsPolicy某个线上线程池配置如下ThreadPoolExecutorthreadPoolExecutornewThreadPoolExecutor(50,最小核心线程数50,最大线程数,当队列满时,能创建的最大线程数60L,TimeUnit。SECONDS,空闲线程超过核心线程时,回收该线程的最大等待时间newLinkedBlockingQueue(5000),阻塞队列大小,当核心线程使用满时,新的线程会放进队列newCustomizableThreadFactory(task),自定义线程名newThreadPoolExecutor。CallerRunsPolicy()线程执行的拒绝策略);复制代码 在某些情况下,子线程任务调用第三方接口超时,导致核心线程数、最大线程数占满、阻塞队列占满的情况下执行拒绝策略时,由于使用CallerRunsPolicy策略,导致业务线程执行子任务时继续超时,进而导致接口执行异常,这种情况下,考虑到子线程任务得重要性,不是很重要得话,可以使用DiscardPolicy策略,要是很重要,可以发送到消息队列中持久化子线程任务数据待后续处理三。优雅的单例模式懒加载帮助类代码实现 博主推荐通过静态内部类实现单例模式,并实现懒加载效果,代码如下使用静态内部类完成单例模式封装,避免线程安全问题,避免重复初始化成员属性Slf4jpublicclassFilterIpUtil{privateFilterIpUtil(){}privateListStringstringsnewArrayList();代码块在FilterIpUtil实例初始化时才会执行{在代码块中完成文件的第一次读写操作,后续不再读这个文件System。out。println(FilterIpUtilinit);try(InputStreamresourceAsStreamFilterIpUtil。class。getClassLoader()。getResourceAsStream(filterIp。txt)){将文件内容放到string集合中IoUtil。readUtf8Lines(resourceAsStream,strings);}catch(IOExceptione){log。error(e。getMessage(),e);}}publicstaticFilterIpUtilgetInstance(){returnInnerClassInstance。}使用内部类完成单例模式,由jvm保证线程安全privatestaticclassInnerClassInstance{privatestaticfinalFilterIpUtilinstancenewFilterIpUtil();}判断集合中是否包含目标参数publicbooleanisFilter(Stringarg){returnstrings。contains(arg);}}复制代码四。使用ip2region实现请求地址解析 在博主之前公司得项目中,ip解析是调用淘宝IP还有聚合IP接口获取结果,通常耗时200毫秒左右,并且接口不稳定时而会挂。都会影响业务接口耗时,后来在github上了解到ip2region这个项目,使用本地ip库查询,查询速度微秒级别,精准度能达到90,但是ip库还是有少部分ip信息不准,建议数据库中把请求ip地址保存下来。简介如下: ip2regionv2。0是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率,提供了众多主流编程语言的xdb数据生成和查询客户端实现基于xdb文件的查询,下面是一个Spring项目中ip2region帮助类来实现ip地址解析ip2region工具类Slf4jComponentpublicclassIp2region{privateSValue({ip2region。path:})privateStringip2regionPPostConstructprivatevoidinit(){1、从dbPath加载整个xdb到内存。StringdbPathip2regionP1、从dbPath加载整个xdb到内存。byte〔〕cBtry{cBuffSearcher。loadContentFromFile(dbPath);searcherSearcher。newWithBuffer(cBuff);}catch(Exceptione){log。error(failedtocreatecontentcachedsearcher:{},e。getMessage(),e);}}publicIpInfoBeangetIpInfo(Stringip){if(StringUtils。isBlank(ip)){}3、查询try{longsTimeSystem。nanoTime();国家区域省份城市ISPStringregionsearcher。search(ip);longcostTimeUnit。NANOSECONDS。toMicros((long)(System。nanoTime()sTime));log。info({region:{},ioCount:{},took:{}s},region,searcher。getIOCount(),cost);if(StringUtils。isNotBlank(region)){String〔〕splitregion。split();IpInfoBeanipInfonewIpInfoBean();ipInfo。setIp(ip);if(!。equals(split〔0〕)){ipInfo。setCountry(split〔0〕);}if(!。equals(split〔2〕)){ipInfo。setProvince(split〔2〕);}if(!。equals(split〔3〕)){ipInfo。setCity(split〔3〕);}if(!。equals(split〔4〕)){ipInfo。setIsp(split〔4〕);}returnipI}}catch(Exceptione){log。error(failedtosearch({}):{},ip,e);}4、关闭资源该searcher对象可以安全用于并发,等整个服务关闭的时候再关闭searchersearcher。close();备注:并发使用,用整个xdb数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个searcher对象做成全局对象去跨线程访问。}}复制代码 要注意得就是ip2regionv2。0版本使用的xdb文件不建议放在项目resources下一起打包,存在编码格式问题,建议通过指定路径加载得方式单独放在服务器目录下五。优雅得Springbootmybatis配置多数据源方式 Springbootmybatis得项目中一般通过MapperScan注解配置dao层包目录,来实现dao层增强,其实项目中配置一个MapperScan是指定一个数据源,配置两个MapperScan就可以指定两个数据源,通过不同得dao层包目录区分,来实现不同数据源得访问隔离。 比如下面代码中,com。xxx。dao。master目录下为主数据源dao文件,com。xxx。dao。slave为从数据源dao文件,这个方式比网上得基于aop加注解得方式更加简洁好用,也没有单个方法中使用不同数据源切换得问题,因此推荐这种写法主数据源Slf4jConfigurationMapperScan(basePackages{com。xxx。dao。master},sqlSessionFactoryRefMasterSqlSessionFactory)publicclassMasterDataSourceConfig{Bean(nameMasterDataSource)Qualifier(MasterDataSource)ConfigurationProperties(prefixspring。datasource。master)publicDataSourceclickHouseDataSource(){returnDruidDataSourceBuilder。create()。build();}Bean(nameMasterSqlSessionFactory)publicSqlSessionFactorygetSqlSessionFactory(Qualifier(MasterDataSource)DataSourcedataSource)throwsException{MybatisSqlSessionFactoryBeansessionFactoryBeannewMybatisSqlSessionFactoryBean();sessionFactoryBean。setDataSource(dataSource);sessionFactoryBean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(classpath:mappermaster。xml));log。info(MasterDataSource配置成功);returnsessionFactoryBean。getObject();}}从数据源Slf4jConfigurationMapperScan(basePackages{com。xxx。dao。slave},sqlSessionFactoryRefSlaveSqlSessionFactory)publicclassMasterDataSourceConfig{Bean(nameSlaveDataSource)Qualifier(SlaveDataSource)ConfigurationProperties(prefixspring。datasource。slave)publicDataSourceclickHouseDataSource(){returnDruidDataSourceBuilder。create()。build();}Bean(nameSlaveSqlSessionFactory)publicSqlSessionFactorygetSqlSessionFactory(Qualifier(SlaveDataSource)DataSourcedataSource)throwsException{MybatisSqlSessionFactoryBeansessionFactoryBeannewMybatisSqlSessionFactoryBean();sessionFactoryBean。setDataSource(dataSource);sessionFactoryBean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(classpath:mapperslave。xml));log。info(SlaveDataSource配置成功);returnsessionFactoryBean。getObject();}}复制代码 数据源yml配置spring:datasource:type:com。alibaba。druid。pool。DruidDataSourcedriverClassName:com。mysql。cj。jdbc。Driver主库数据源master:url:jdbc:mysql:localhost:3306db1?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT2B8username:rootpassword:slave:url:jdbc:mysql:localhost:3306db2?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT2B8username:rootpassword:复制代码 博主刚开始编码一、两年得时候一个项目中遇到了多数据源使用得问题,那时候题主便在网上搜索Spring多数据源得帖子,大多数都是基于Spring提供得AbstractRoutingDataSourceAOP注解来做动态切换,包括现在流行得Mybatisplus官方得多数据源解决方案也是这种做法,这种做法解决了博主当时得多数据源使用问题,后来加了一个需求,在一个定时任务中,查询两个数据源得数据,才发现动态切换在单个方法中不好用了,最后使用得原生jdbc数据源解决。多年后,博主在另一家公司得项目中又遇到了多数据源问题,但是这次博主在网上搜索得是Mybatis多数据源,才发现了这个优雅得解决方案,进而推荐给大家六。SpringSecurity项目中,使用MDC实现接口请求调用追踪,以及用户ID记录MDC介绍 MDC(MappedDiagnosticContext,映射调试上下文)是log4j、logback及log4j2提供的一种方便在多线程条件下记录日志的功能。MDC可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的MDC的内容。当需要记录日志时,只需要从MDC中获取所需的信息即可。 虽然MDC能够方便得实现接口请求调用追踪功能,但是它在子线程中会丢失父线程中添加得键值对信息,解决方法是通过父线程中调用线程池前调用MDC。getCopyOfContextMap(),然后在子线程中第一个调用MDC。setConextMap()获取键值对信息,完整实现代码如下:自定义Spring线程池,解决子线程丢失reqestid问题publicclassThreadPoolExecutorMdcWrapperextendsThreadPoolTaskExecutor{Overridepublicvoidexecute(Runnabletask){super。execute(ThreadMdcUtil。wrap(task,MDC。getCopyOfContextMap()));}OverridepublicTFutureTsubmit(CallableTtask){returnsuper。submit(ThreadMdcUtil。wrap(task,MDC。getCopyOfContextMap()));}OverridepublicF?submit(Runnabletask){returnsuper。submit(ThreadMdcUtil。wrap(task,MDC。getCopyOfContextMap()));}}MDC帮助类,添加reqestidpublicclassThreadMdcUtil{publicstaticfinalStringREQUESTID设置请求唯一IDpublicstaticvoidsetTraceIdIfAbsent(){if(MDC。get(REQUESTID)null){MDC。put(REQUESTID,IdUtil。getUid());}}存在userId则添加到REQUESTID中paramuserIdpublicstaticvoidsetUserId(StringuserId){StringsMDC。get(REQUESTID);if(s!null){MDC。put(REQUESTID,suserId);}}publicstaticvoidremoveTraceId(){MDC。remove(REQUESTID);}publicstaticTCallableTwrap(finalCallableTcallable,finalMapString,Stringcontext){return(){if(contextnull){MDC。clear();}else{MDC。setContextMap(context);}setTraceIdIfAbsent();try{returncallable。call();}finally{MDC。clear();}};}publicstaticRunnablewrap(finalRunnablerunnable,finalMapString,Stringcontext){return(){if(contextnull){MDC。clear();}else{MDC。setContextMap(context);}设置traceIdsetTraceIdIfAbsent();try{runnable。run();}finally{MDC。clear();}};}}复制代码 在SpringSecurity中添加token过滤器token过滤器验证token有效性authorruoyiSlf4jComponentpublicclassJwtAuthenticationTokenFilterextendsOncePerRequestFilter{AutowiredprivateTokenServicetokenSOverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainchain)throwsServletException,IOException{try{入口传入请求IDThreadMdcUtil。setTraceIdIfAbsent();LoginUserDetailloginUsertokenService。getLoginUser(request);if(Objects。nonNull(loginUser)Objects。isNull(SecurityContextHolder。getContext()。getAuthentication())){记录userIdThreadMdcUtil。setUserId(String。valueOf(loginUser。getMember()。getId()));tokenService。verifyToken(loginUser);UsernamePasswordAuthenticationTokenauthenticationTokennewUsernamePasswordAuthenticationToken(loginUser,null,loginUser。getAuthorities());authenticationToken。setDetails(newWebAuthenticationDetailsSource()。buildDetails(request));SecurityContextHolder。getContext()。setAuthentication(authenticationToken);}chain。doFilter(request,response);}finally{出口移除请求IDThreadMdcUtil。removeTraceId();}}}复制代码 最后在logback。xml中添加X{requestid}propertynamepatternvalued{yyyyMMddHH:mm:ss。SSS}〔X{requestid}〕〔thread〕〔5level〕logger{36}:LMmsgn复制代码 日志打印效果如下:2022112721:29:48。008〔86c76336100c414dbe9217aeb099ccd512〕〔httpnio82exec2〕〔INFO〕c。w。m。a。s。impl。IHomeServiceImpl:56getHomeIndexDataCompletableFuturegetHomeIndexDataCompletableFuture:com。wayn。common。util。R701f7b8e〔code200,msg操作成功,map{bannerList〔{createTime:2020062619:56:03,delFlag:false,id:14,imgUrl:https:m。360buyimg。commobilecmss700x280jfst111733539138372630995f291a83E8ba761d05c0460445cb28248。jpg!cr1125x4490166!q70。jpg。dpg,jumpUrl:http:82。157。141。70malldetail1155015,sort:0,status:0,title:hh2,updateTime:2022061909:16:46},{createTime:2020062619:56:03,delFlag:false,id:15,imgUrl:https:m。360buyimg。commobilecmss700x280jfst12020962611652265782616acb67E4fcdc9ac8d7cdfbb6c934e67。jpg!cr1125x4490166!q70。jpg。dpg,jumpUrl:detail1155015,sort:0,status:0,title:hh,updateTime:2022061909:04:58}〕,newGoodsList〔{actualSales:0,brandId:0,brief:酥脆奶香,甜酸回味,categoryId:1008015,counterPrice:56。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1116011,id:1116011,isHot:true,isNew:true,isOnSale:true,keywords:,name:蔓越莓曲奇200克,picUrl:http:yanxuan。nosdn。127。net767b370d07f3973500db54900bcbd2a7。png,retailPrice:36。00,shareUrl:,sort:5,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:粉彩色泽,记录生活,categoryId:1012003,counterPrice:49。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1127047,id:1127047,isHot:false,isNew:true,isOnSale:true,keywords:,name:趣味粉彩系列笔记本,picUrl:http:yanxuan。nosdn。127。net6c03ca93d8fe404faa266ea86f3f1e43。png,retailPrice:29。00,shareUrl:,sort:2,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:慢回弹海绵,时尚设计。,categoryId:1008002,counterPrice:66。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1134030,id:1134030,isHot:false,isNew:true,isOnSale:true,keywords:,name:简约知性记忆棉坐垫,picUrl:http:yanxuan。nosdn。127。netaa49dfe878becf768eddc4c1636643a6。png,retailPrice:46。00,shareUrl:,sort:12,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:慢回弹海绵的呵护,萌趣添彩。,categoryId:1008002,counterPrice:69。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1134032,id:1134032,isHot:false,isNew:true,isOnSale:true,keywords:,name:趣味粉彩系列记忆棉坐垫,picUrl:http:yanxuan。nosdn。127。net8b30eeb17c831eba08b97bdcb4c46a8e。png,retailPrice:49。00,shareUrl:,sort:13,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:100桑蚕丝,丝滑润肤,categoryId:1008009,counterPrice:2619。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1135002,id:1135002,isHot:false,isNew:true,isOnSale:true,keywords:,name:宫廷奢华真丝四件套,picUrl:http:yanxuan。nosdn。127。net45548f26cfd0c7c41e0afc3709d48286。png,retailPrice:2599。00,shareUrl:,sort:1,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:自由海军领探索未来梦,categoryId:1020003,counterPrice:89。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1135072,id:1135072,isHot:false,isNew:true,isOnSale:true,keywords:,name:经典海魂纹水手裙(婴童),picUrl:http:yanxuan。nosdn。127。net43e57d4208cdc78ac9c088f9b3e798f5。png,retailPrice:69。00,shareUrl:,sort:3,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:经典海魂纹自由海军领,categoryId:1020003,counterPrice:89。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1135073,id:1135073,isHot:false,isNew:true,isOnSale:true,keywords:,name:海魂纹哈衣水手服(婴童),picUrl:http:yanxuan。nosdn。127。net53052b04ae001d289c040e09ea92231c。png,retailPrice:69。00,shareUrl:,sort:4,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:5,brandId:0,brief:差旅好伴侣,categoryId:1032000,counterPrice:119。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1152031,id:1152031,isHot:true,isNew:true,isOnSale:true,keywords:,name:魔兽世界伊利丹颈枕眼罩套装,picUrl:http:yanxuan。nosdn。127。netfd6e78a397bd9e9804116a36f0270b0a。png,retailPrice:99。00,shareUrl:,sort:4,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:5,brandId:0,brief:桌面整理神器,categoryId:1032000,counterPrice:519。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1152095,id:1152095,isHot:false,isNew:true,isOnSale:true,keywords:,name:魔兽世界联盟暴风城堡垒收纳盒,picUrl:http:yanxuan。nosdn。127。netc86b49f635fa141decebabbd0966a6ef。png,retailPrice:499。00,shareUrl:,sort:6,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:0,brandId:0,brief:3重透气,清爽柔滑,categoryId:1008009,counterPrice:479。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1152161,id:1152161,isHot:false,isNew:true,isOnSale:true,keywords:,name:竹语丝麻印花四件套,picUrl:http:yanxuan。nosdn。127。net977401e75113f7c8334c4fb5b4bf6215。png,retailPrice:459。00,shareUrl:,sort:6,unit:件,updateTime:2018020100:00:00,virtualSales:10}〕,categoryList〔{createTime:2020120823:09:12,delFlag:false,iconUrl:http:cdn。wayn。xin9fc3c52571aa38a1466f114c2dc892fc。png,id:2,jumpType:0,name:大牌手机,picUrl:http:cdn。wayn。xin2545dd00ca4575759024af2811949a9d。jpg,sort:1,status:0,updateTime:2020121219:26:48,valueId:5,valueUrl:http:baidu。com},{createTime:2020120613:27:54,delFlag:false,iconUrl:http:82。157。141。70upload20220221a23fa32c8f4004a8c9fbbb1784462163。jpg,id:1,jumpType:0,name:滋补保健2,picUrl:http:cdn。wayn。xind4de7172eb7ae4178ae4dafd6a973d8f。jpg,sort:2,status:0,updateTime:2022061909:17:20,valueId:2},{createTime:2020120823:26:15,delFlag:false,iconUrl:http:cdn。wayn。xin6bc0f8a131d2d16b8fc2004d4aa4860c。png,id:3,jumpType:1,name:不锈钢锅,picUrl:http:cdn。wayn。xin314d87257f7a2ff03d5f4c5183797912。jpg,sort:2,status:0,updateTime:2020121219:27:24,valueId:1036000},{createTime:2020121219:28:08,delFlag:false,iconUrl:http:cdn。wayn。xin5a90531d901529967885279d7dc826e1。png,id:14,jumpType:0,name:进口牛奶,picUrl:http:cdn。wayn。xin0b1f6ab63d5e222c52c83a2d0581e44c。jpg,sort:3,status:0,valueId:5},{createTime:2020121219:28:33,delFlag:false,iconUrl:http:cdn。wayn。xin33530951827ca7e59940d51cda537d84。png,id:15,jumpType:0,name:量贩囤货,picUrl:http:cdn。wayn。xinbb6daee3b3e51c3008db97585249f513。jpg,sort:4,status:0,valueId:2},{createTime:2020121219:28:50,delFlag:false,iconUrl:http:cdn。wayn。xin7d337f25111b263b29d5d12589015c46。png,id:16,jumpType:0,name:清洁用品,picUrl:http:cdn。wayn。xinbe8995bda39d03b17349b8ec0dcab3d5。jpg,sort:5,status:0,valueId:2},{createTime:2020121219:29:10,delFlag:false,iconUrl:http:cdn。wayn。xin2e632ec0173bb477dcdb601495e0412a。png,id:17,jumpType:0,name:洗护用品,picUrl:http:cdn。wayn。xin53fb88c9d1245caa882aa3fc29187d0b。jpg,sort:6,status:0,valueId:4},{createTime:2020121219:29:28,delFlag:false,iconUrl:http:cdn。wayn。xin942323c0e74677cf2aa15f09a1e63bca。png,id:18,jumpType:0,name:日用百货,picUrl:http:cdn。wayn。xin8587f91db2edcb43e57da9835cc7ec76。jpg,sort:7,status:0,valueId:2},{createTime:2020121219:29:46,delFlag:false,iconUrl:http:cdn。wayn。xin18d9d860ba9b8b28522e050f11a8a8e0。png,id:19,jumpType:0,name:明星乳胶,picUrl:http:cdn。wayn。xin65273c7fb2273e90958e92626248a90a。jpg,sort:8,status:0,valueId:6},{createTime:2020121219:30:15,delFlag:false,iconUrl:http:cdn。wayn。xin7c790577afda91eebc3c95586e190957。png,id:20,jumpType:0,name:口碑好物,picUrl:http:cdn。wayn。xin210011b35be4ceee39e6a466b40b8e22。jpg,sort:9,status:0,updateTime:2021040120:13:08,valueId:5}〕,expiretime1669549170235,hotGoodsList〔{actualSales:1,brandId:1001045,brief:一级桑蚕丝,吸湿透气柔软,categoryId:1036000,counterPrice:719。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1006013,id:1006013,isHot:true,isNew:false,isOnSale:true,keywords:,name:双宫茧桑蚕丝被空调被,picUrl:http:yanxuan。nosdn。127。net583812520c68ca7995b6fac4c67ae2c7。png,retailPrice:699。00,shareUrl:,sort:7,unit:件,updateTime:2021080811:19:36,virtualSales:10},{actualSales:1,brandId:1001045,brief:双层子母被,四季皆可使用,categoryId:1008008,counterPrice:14199。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1006014,id:1006014,isHot:true,isNew:false,isOnSale:true,keywords:,name:双宫茧桑蚕丝被子母被,picUrl:http:yanxuan。nosdn。127。net2b537159f0f789034bf8c4b339c43750。png,retailPrice:1399。00,shareUrl:,sort:15,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:6,brandId:1001000,brief:加大加厚,双色精彩,categoryId:1036000,counterPrice:219。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1011004,id:1011004,isHot:true,isNew:false,isOnSale:true,keywords:,name:色织精梳AB纱格纹空调被,picUrl:http:yanxuan。nosdn。127。net0984c9388a2c3fd2335779da904be393。png,retailPrice:199。00,shareUrl:,sort:2,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:6,brandId:0,brief:共享亲密2人时光,categoryId:1008008,counterPrice:219。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1019002,id:1019002,isHot:true,isNew:false,isOnSale:true,keywords:,name:升级款护颈双人记忆枕,picUrl:http:yanxuan。nosdn。127。net0118039f7cda342651595d994ed09567。png,retailPrice:199。00,shareUrl:,sort:10,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:6,brandId:0,brief:健康保护枕,categoryId:1008008,counterPrice:119。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1019006,id:1019006,isHot:true,isNew:false,isOnSale:true,keywords:,name:植物填充护颈夜交藤枕,picUrl:http:yanxuan。nosdn。127。net60c3707837c97a21715ecc3986a744ce。png,retailPrice:99。00,shareUrl:,sort:7,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:6,brandId:0,brief:厚实舒适,categoryId:1008001,counterPrice:59。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1021000,id:1021000,isHot:true,isNew:false,isOnSale:true,keywords:被,name:埃及进口长绒棉毛巾,picUrl:http:yanxuan。nosdn。127。net7191f2599c7fe44ed4cff7a76e853154。png,retailPrice:39。00,shareUrl:,sort:7,unit:条,updateTime:2018020100:00:00,virtualSales:10},{actualSales:6,brandId:1001020,brief:浪漫毛线绣球,简约而不简单,categoryId:1008009,counterPrice:319。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1022000,id:1022000,isHot:true,isNew:false,isOnSale:true,keywords:,name:意式毛线绣球四件套,picUrl:http:yanxuan。nosdn。127。net5350e35e6f22165f38928f3c2c52ac57。png,retailPrice:299。00,shareUrl:,sort:18,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:32,brandId:1001000,brief:柔软纱布,婴童可用,categoryId:1036000,counterPrice:269。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1027004,id:1027004,isHot:true,isNew:false,isOnSale:true,keywords:,name:色织六层纱布夏凉被,picUrl:http:yanxuan。nosdn。127。net6252f53aaf36c072b6678f3d8c635132。png,retailPrice:249。00,shareUrl:,sort:3,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:32,brandId:0,brief:原生苦荞,健康护颈,categoryId:1008008,counterPrice:119。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1036002,id:1036002,isHot:true,isNew:false,isOnSale:true,keywords:,name:高山苦荞麦枕,picUrl:http:yanxuan。nosdn。127。netffd7efe9d5225dff9f36d5110b027caa。png,retailPrice:99。00,shareUrl:,sort:5,unit:件,updateTime:2018020100:00:00,virtualSales:10},{actualSales:32,brandId:0,brief:5cm记忆绵的亲密包裹,categoryId:1008008,counterPrice:619。00,createTime:2018020100:00:00,delFlag:false,goodsSn:1037011,id:1037011,isHot:true,isNew:false,isOnSale:true,keywords:,name:安睡慢回弹记忆绵床垫,picUrl:http:yanxuan。nosdn。127。neta03ea6f4509439acdafcb7ceba1debe0。png,retailPrice:599。00,shareUrl:,sort:22,unit:件,updateTime:2018020100:00:00,virtualSales:10}〕}〕2022112721:29:48。336〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPagempCount:137debugPreparing:SELECTCOUNT()AStotalFROMshopgoodsWHEREdelflag0ANDisonsale12022112721:29:48。387〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPagempCount:137debugParameters:2022112721:29:48。426〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPagempCount:137debugTotal:12022112721:29:48。430〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPage:137debugPreparing:selectid,goodssn,name,picurl,counterprice,retailprice,actualsales,virtualsalesfromshopgoodsWHEREdelflag0andisonsale1orderbycreatetimedescLIMIT?2022112721:29:48。452〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPage:137debugParameters:6(Long)2022112721:29:48。476〔9d919fb6d33c4652ba28ff87ae21080912〕〔httpnio82exec3〕〔DEBUG〕c。w。c。c。m。s。G。selectGoodsListPage:137debugTotal:6复制代码 最后分析上诉日志:通过86c76336100c414dbe9217aeb099ccd5实现接口调用追踪,通过12用户ID,实现用户调用追踪七。alibabaexcel导出时自定义格式转换优雅实现 官网介绍:EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 EasyExcel是alibaba出的一个基于javapoi得excel通用处理类库,他的优势在于内存消耗。对比easypoi方案,EasyExcel在内存消耗、知名度(大厂光环)上更出众些。 博主在使用过程中发现导出excel,官网对自定义格式字段提供了converter接口,但只简单提供了CustomStringStringConverter类代码,达不到博主想要得优雅要求,如下:publicclassCustomStringStringConverterimplementsConverterString{OverridepublicC?supportJavaTypeKey(){returnString。}OverridepublicCellDataTypeEnumsupportExcelTypeKey(){returnCellDataTypeEnum。STRING;}这里读的时候会调用paramcontextreturnOverridepublicStringconvertToJavaData(ReadConverterC?context){return自定义:context。getReadCellData()。getStringValue();}这里是写的时候会调用不用管returnOverridepublicWriteCellD?convertToExcelData(WriteConverterContextStringcontext){returnnewWriteCellData(context。getValue());}}复制代码 在以上代码中,打个比方想要实现性别字段得自定义格式转换,就需要在convertToExcelData方法中,添加如下代码OverridepublicWriteCellD?convertToExcelData(WriteConverterContextStringcontext){Stringvaluecontext。getValue();if(man。equals(value)){returnnewWriteCellData(男);}else{returnnewWriteCellData(女);}}复制代码 可以看到,非常得不优雅,对于这种类型字段,博主习惯使用枚举类来定义字段所有类型,然后将枚举类转换为map(value,desc)结构,就可以优雅得实现这个自定义格式得需求一、先定义int字段抽象转换类,实现通用转换逻辑publicabstractclassAbstractIntConverterimplementsConverterInteger{abstractListConverterDTOgetArr();publicWriteCellD?convertToExcelData(Integervalue,ExcelContentPropertycontentProperty,GlobalConfigurationglobalConfiguration){ListConverterDTOvaluesgetArr();MapInteger,Stringmapvalues。stream()。collect(toMap(ConverterDTO::getType,ConverterDTO::getDesc));Stringresultmap。getOrDefault(value,);returnnewWriteCellData(result);}staticclassConverterDTO{privateIprivateSpublicIntegergetType(){}publicvoidsetType(Integertype){this。}publicStringgetDesc(){}publicvoidsetDesc(Stringdesc){this。}publicConverterDTO(Integertype,Stringdesc){this。this。}}}二、定义通用状态字段转换类publicclassStatusConverterextendsAbstractIntConverter{OverrideListConverterDTOgetArr(){StatusEnum〔〕valuesStatusEnum。values();returnArrays。stream(values)。map(sexEnumnewConverterDTO(sexEnum。getType(),sexEnum。getDesc()))。toList();}状态枚举enumStatusEnum{MAN(0,启用),WOMAN(1,禁用);privateIprivateSStatusEnum(Integertype,Stringdesc){this。this。}publicIntegergetType(){}publicStringgetDesc(){}}}复制代码 最后再导出ExcelProperty中甜腻加StatusConverter,就优雅得实现了自定义格式得需求publicclassUserextendsBaseEntity{。。。用户状态0启用1禁用ExcelProperty(value用户状态,converterStatusConverter。class)privateIntegeruserS。。。}复制代码八。Springboot默认redis客户端lettuce经常连接超时解决方案 不知道大家有没有遇到这种情况,线上项目使用lettuce客户端,当操作redis得接口一段时间没有调用后(比如30分钟),再次调用redis操作后,就会遇到连接超时得问题,导致接口异常。博主直接给出分析过程:通过wireshark抓包工具,发现项目中redis连接创建后,一段时间未传输数据后,客户端发送psh包,未收到服务端ack包,触发tcp得超时重传机制,在重传次数重试完后,最终客户端主动关闭了连接。 到这里我们就知道这个问题,主要原因在于服务端没有回复客户端(比如tcp参数设置、防火墙主动关闭等,都是针对一段时间内没有数据传输得tcp连接会做关闭处理),造成了客户端得连接超时 面对这个问题有三种解决方案:redis操作异常后进行重试,这篇文章有介绍生产环境Redis连接,长时间无响应被服务器断开问题启用一个心跳定时任务,定时访问redis,保持redis连接不被关闭,简而言之,就是写一个定时任务,定时调用redis得get命令,进而保活redis连接基于Springboot提供得LettuceClientConfigurationBuilderCustomizer自定义客户端配置,博主这里主要针对第三种自定义客户端配置来讲解一种优雅得方式 Springboot项目中关于lettuce客户端得自动配置是没有启用保活配置得,要启用得话代码如下:自定义lettuce客户端配置returnLettuceClientConfigurationBuilderCustomizerBeanpublicLettuceClientConfigurationBuilderCustomizerlettuceClientConfigurationBuilderCustomizer(){returnclientConfigurationBuilder{LettuceClientConfigurationclientConfigurationclientConfigurationBuilder。build();ClientOptionsclientOptionsclientConfiguration。getClientOptions()。orElseGet(ClientOptions::create);ClientOptionsbuildclientOptions。mutate()。build();SocketOptions。KeepAliveOptions。Builderbuilderbuild。getSocketOptions()。getKeepAlive()。mutate();保活配置builder。enable(true);builder。idle(Duration。ofSeconds(30));SocketOptions。BuildersocketOptionsBuilderclientOptions。getSocketOptions()。mutate();SocketOptions。KeepAliveOptionskeepAliveOptionsbuilder。build();socketOptionsBuilder。keepAlive(keepAliveOptions);SocketOptionssocketOptionssocketOptionsBuilder。build();ClientOptionsclientOptions1ClientOptions。builder()。socketOptions(socketOptions)。build();clientConfigurationBuilder。clientOptions(clientOptions1);};}复制代码 添加lettuce客户端的自定义配置,在KeepAliveOptions中启用enable,这样lettuce客户端就会在tcp协议规范上启用keepalive机制自动发送心跳包九。redis客户端lettuce启用epoll 直接给官网连接,配置很简单,添加一个nettyall得依赖,lettuce会自动检测项目系统是否支持epoll(linux系统支持),并且是否有nettytransportnativeepoll依赖(nettyall包含nettytransportnativeepoll),都满足得话就会自动启用epoll事件循环,进一步提升系统性能dependencygroupIdio。nettygroupIdnettyallartifactIddependency复制代码十。Springbootweb项目优雅停机 web项目配置了优雅停机后,在重启jar包,或者容器时可以防止正在活动得线程被突然停止(kill9无解,请不要使用这个参数杀线上进程,dockercompose项目尽量不要用dockercomposedown命令关闭项目,使用dockercomposermsvf可以触发优雅停机),造成用户请求失败,在此期间允许完成现有请求但不允许新请求,配置如下:server:shutdown:graceful复制代码十一。nginx配置通用请求后缀 先说下这个配置产生得前提,博主公司pc客户项目是基于electron打包得网页项目,每次项目大版本更新时,为了做好兼容性,防止客户端网页缓存等,会使用一个新网页地址,打个比方: 老网页地址,v1。1。0版本网页访问地址:api。dev。compageV110 新网页地址,v1。2。0版本网页访问地址:api。dev。compageV120 那么项目得nginx配置则则需要新加一个v1。2。0得配置如下:server{listen80;servernameapi。dev。clientmaxbodysize10m;老网页v1。1。0配置locationpageV110{aliashomewwwrootapi。dev。compageV110;indexindex。htmlindex。}新网页v1。2。0配置locationpageV120{aliashomewwwrootapi。dev。compageV120;indexindex。htmlindex。}}复制代码 那么博主在每次项目发布得时候就需要配合前端发版,配置一个新网页,故产生了这个通用配置得需求,如下:server{listen80;servernameapi。dev。clientmaxbodysize10m;配置正则localtionlocationpageV(。){sets1;定义后缀变量aliashomewwwrootapi。dev。compageVs;indexindex。htmlindex。}}复制代码 在nginx配置文件语法中,location语句可以使用正则表达式,定义sets1变量,实现了通用配置十二。关于开发人员的自我提升和突破 博主这里主要总结了四点:多和他人沟通,沟通能把复杂问题简单化,有时候开发阶段一个需求多问几句,可以减少因为个人理解差异导致的需求不一致问题,进而减少开发时间建立长短期目标,观看技术视频、书籍给自己充电,比如7天利用业余时间看完一本电子书,三十天从零开始一个新项目等善于总结,对于项目中的疑难bug,踩坑点要有记录,防止下次遇到再掉坑里敢于尝试、担责,对项目、代码里明确不合理的地方要敢于跟他人沟通,修改问题代码,达到优化目的。对于自己造成的问题要承担,不要推卸责任。对于线上问题要重视,优先解决线上问题。 作者:wayn 链接:https:juejin。cnpost7171770606940061727