
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、版本说明🔥环境:spring3.0 + mybatis 3.3 + spring-mybatis-1.3.0 + jsp三、事故发生缘由🔥 事情的经过很奇妙,我之前不是在带着技术组赶项目嘛,然后不知道老大在哪里接手了一个老旧项目,说当时开发结束了一直扔着就没有下文,现在要求我把这个项目给成功运行,且部署到服务器。我看到这条消息,我心想,几年前的项目,要还是使用的是不分离且页面使用jsp写的我就真奔溃了。 老大接着发给了我项目,我打开一看目录结构,我深深陷入了沉思。果然被我猜对了,还真是spring + mybatis + jsp。传统的ssm框架写的。 我先是将sql源文件导入进数据库,还好,mysql用的是5.6版本下的,顺利执行成功。看到这不规范的表结构设计,我有点堪忧!这还是能项目吗?写的这么这么不规范。 我看了下pom依赖配置,导入的是真的多,项目依赖陆续下载完成后,我运行application启动类。不用想,必报错。 还真是,那只能怎么办?耽误点时间看看报啥错呗,然后解决,总要先把项目运行不报错先,至于要迭代还是咋就不关我的事了。四、排错分析🔥控制台关键是爆了这么一段错:java.lang.NoClassDefFoundError: org/apache/ibatis/annotations/Mapper... 看到这段错,我回忆回忆,好像还真没有遇到过,那我得瞅瞅,代码中是否有报错的地方,没有,那我只能科学上网,查看下是否有相关解决方案,试了前三个解决方案,说是将@Mapper注解改成@MapperScan,就能解决,没有用。 最后,原来问题出在这里。这项目中用的spring 3.0 + spring-mybatis-1.3.0 + mybtis 3.3。好家伙,用的还是这种组合版本,版本之间冲突而已。 所以解决办法就是找一组兼容版本替换当前两jar依赖版本即可。控制台错误:打印截图:五、解决方案🔥更新使用的mybatis的版本为3.4.0版本以上即可!如下:<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> <scope>compile</scope> </dependency> 升级 mybatis版本后,我们再来重启项目,ok!大功告成。 想说句题外话,项目我是成功运行起来了,但是不敢保证里头的业务逻辑是否不会报错,这我也不知道,要是遇到逻辑报错,那更是头秃,我给老大回报完,我成功运行,也能正常登陆系统,结果来一句,带几个伙伴,把这个项目熟悉一下,熟悉一下?不是吧!我是真心不想看啊,写成这样。给你们举个例子啊,Controller是逻辑从头写到尾,基本不封装接口层与实现层,一个Controller接口几百行代码。艾玛... 家人们,如果是你们被安排,你们会接手并进行代码熟悉吗?... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期推荐🔥springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出七、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥**环境:**jdk1.8 + springboot 2.3.1.RELEASE + mysql 5.6三、排错分析🔥 非常的奇怪,巨离谱!项目本地运行,时不时给你卡一下,然后控制台直接报错哗哗的一行行打印,且不光印象接口访问,navicat连接刷新数据库也是,直接卡住,这才多少数据,百来条,就直接查询也不至于啊? 前端放来狠话,说我接口频繁报错,一会儿又好了,给我紧急修复掉,太影响接口测试了。这把我给整的,很烦呀,这任谁也忍不了呀,必须解决! 放眼望去,从何开始定位问题呢?先申明一下不是锁表锁库所导致!这点是可以排除的。有哪位懂行的老哥能帮帮我的么?四、报错展示🔥如下是我项目控制台实际报错:WARN 4196 --- [nio-8889-exec-6] com.zaxxer.hikari.pool.PoolBase : master - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@4270a6d1 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value. WARN 4196 --- [nio-8889-exec-6] com.zaxxer.hikari.pool.PoolBase : master - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@74713539 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value. WARN 4196 --- [nio-8889-exec-6] com.zaxxer.hikari.pool.PoolBase : master - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@3b7db2ff (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value. WARN 4196 --- [nio-8889-exec-6] com.zaxxer.hikari.pool.PoolBase : master - Failed to validate connection com.mysql.cj.jdbc.ConnectionImpl@15d8fec0 (No operations allowed after connection closed.). Possibly consider using a shorter maxLifetime value.五、解决方案🔥 单从报错信息入手:验证连接com.mysql.cj.jdbc失败,连接关闭后不允许操作。可以考虑减小的maxLifetime值。 但从报错信息入手,好像有点眉头,我们可以先查看一下你们的数据库超时参数配置。很有可能是超时则断开连接导致。 如下是两种尝试解决方案,仅供参考,如果方案1设置不好使请尝试方案2。不能保证方案对你们的情况是否有效,但是总比不尝试强!方案1: 执行如下这段命令查看数据库的各种超时配置参数:show variables like '%timeout%';实际查询结果如下: 从上述截图结果上可以明显看到:连接超时时间使用的是默认的8小时(28800秒),查阅资料发现在mysql 5以上的版本修改my.cnf这个文件,自定义配置wait_timeout 与 interactive_timeout,这个文件的位置位于你服务器里根目录下的 /etc/my.cnf路径下。vim编辑打开文件后,在 [mysqld] 的最后一行加上如下参数;wait_timeout= 86400我们直接修改超时等待时长为:wait_timeout= 86400实际修改截图:下面重启下mysqlservice mysqld restart实际运行截图:OK,我们可以执行命令检查一下。show variables like '%timeout%';方案2: 如果有的小伙伴修改了设置了my.cnf配置依旧参数没有变化,连接超时时间没有变更,那我们就在服务器直接通过命令连接,然后再设置参数试试:连接数据库命令:mysql -u root -h ip -p 然后输入数据库连接密码,后回车。 我们再直接执行一遍查询命令:show variables like ‘%timeout%‘; 可以看到,默认是8小时(28800秒),然后我们通过命令式的设置进行修改参数:set interactive_timeout=604800; set wait_timeout=604800;实际设置截图:都显示设置成功了。那我们直接查询一下: 这样只是说明你设置好了,但是要想立即生效,那我们还得重启下服务器:service mysqld restart实际设置截图: 可以看到,数据库已经重启好了,那我们再来监听看看,后端服务是否还会报连接超时问题吧!可以检查下数据库服务有没有重启OK,确保下。service mysqld status实际运行截图:... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期推荐🔥springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存(上篇)Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出七、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥环境:jdk1.8 + springboot 2.3.1.RELEASE + centos7.6三、教程正文🔥 说明:如下是直接从第3步开始讲起,至于第1步跟第2步在我上一篇文章中,如果有直接能略过的那就直接跟着第3步开始操作吧,如果不会第1步跟第2步的同学那就看我上一期的内容,再折返回来接着看就行了。至于为什么要拆分成两篇文章?理由很简单,你们懂的,我就不解释了哈。《springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存(上篇)》1️⃣通过idea打jar包💭... ...2️⃣jar包上传服务器💭... ...3️⃣检查java环境💭... ...4️⃣jar后台启动🔅 我们都知道,jar运行命令是 java -jar xxx.jar 。对吧,但是有一点,我们要实现项目后台启动,如果你就常规启动,那么你的运行窗口不能关闭,只要你xshell断开连接或者关闭,项目就终止,所以接下来我教你如何后台启动jar包。 这个时候,我们就要用到nohup命令了。nohup 全称:no hang up(不挂起),也就是说,当前交互命令行退出的时候,程序还在运行。怎么关闭此进程呢?你只需要执行如下命令查找到该服务进程id,然后kill掉即可:ps -ef | grep jar名 kill -9 进程id实际演示如下:所以实际执行组合命令如下:nohup java -jar xxx.jar &5️⃣控制台日志输出保存🔅 如果你要实时记录控制台输出日志,那你就可以这么玩儿,在启动命令后加上:> log.file 2>&1 &拓展:2>&1 :表示 将标准错误 2 重定向到标准输出 &1 ,标准输出 &1 再被重定向输入到 log.file 文件中。0 :stdin (standard input,标准输入)1 :stdout (standard output,标准输出)2 :stderr (standard error,标准错误输出)所以启动命令可以是这样:nohup java -jar xxx.jar > log.file 2>&1 &6️⃣外部配置文件启动🔅 我们都知道,springboot jar可以指定外部配置文件启动,对吧,那么这究竟得有啥好处呢?那就是当你修改配置文件,你不不需要再重新打jar包,而只需要修改yaml然后重启项目即可。所以,我们都会将config这个文件夹单独放与xxx.jar包同级。比如: 然后再通过启动命令指定你要运行的配置环境即可。那具体怎么指定呢?其实也是有语法的。在你启动java -jar xxx.jar 后面直接这样指定,然后test是你application-test.yaml的配置指向,这个我就不多解释。--spring.profiles.active = test总结以上,所以最终jar启动命令如下:nohup java -jar review-server.jar --spring.profiles.active = test > log.file 2>&1 &如下是实际启动命名截图: 可以看到,启动了并返回了该服务的进程id。我们也可以来验证一下,是不是该项目服务的进程id。7️⃣查看实时滚动日志🔅 想要查看项目滚动日志,那我们就用到了 tail 命令了。命令格式:tail [参数] [文件] 默认将每个 FILE 的最后 10 行打印到标准输出。实际使用截图:tail -f nohup.out 新的日志行添加至 nohup.out 文件时,tail 命令会继续显示这些行。显示一直继续,按ctrl+c可以退出实时查看,再输入其他指令。 你也就可以看到会自动生成这两文件: log.file是我用来记录项目运行日志的。而nohup.out则包含了项目控制台发到终端显示器上的所有输出,输出会追加到现有的nohup.out文件中。8️⃣总结🔅 对于有的小伙伴不想看我仔细分析,那么你直接看这里,我把本文章最核心的告诉你,那就是jar的完整启动命令。仅供参考:nohup java -jar review-server.jar --spring.profiles.active = test > log.file 2>&1 &其中log.file是用于记录项目控制台日志打印,test是你项目启动的环境配置。... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期推荐🔥springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存(上篇)Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出五、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥环境:jdk1.8 + springboot 2.3.1.RELEASE + centos7.6三、需求分析🔥 今天临时被安排,由于没有运维岗人员,所以项目部署的活自然就落在了后端的肩膀上,这还能咋推辞,自然就只能我来做了呗,虽然我都有过接触,但是在不涨工资的基础上,要兼任这么多角色工作,内心表示有点委屈啊。 在linux服务器进行后端服务部署,有两种方式,一是war包部署,二是jar包部署,服务器还是台裸机,那我一切从简,首先是jdk安排及配置,这里我就不详细介绍了。 所以,面临的第一件事,就是把项目从打jar包开始。四、教程正文🔥1️⃣通过idea打jar包🔅 基本玩idea的同学都知道,idea有提供一个maven快捷操作栏,maven构建现在基本项目都使用吧。所以我接下来就还是以maven构架来举例吧。 而我们打jar包,只需要点击package,即可,等待一会儿,将会在你项目根目录下的target文件夹下,新增一个名为 xxx-1.0.jar与xxx-1.0.jar.original的文件,其xxx-1.0.jar 就是我们所想要的文件,言称jar包。 如下是通过maven打包,执行package完成的截图:2️⃣jar包上传服务器🔅 我们上传到服务器,如果你是使用的xshell 进行远程ssh连接,那么你既可以通过拖拽的方式也可以通过命名将文件发送到服务器,这里我给大家演示后一种,如何通过命令的方式将文件发送到服务器吧? 不知道sz 、rz命令大家有没有玩过,运行sz、rz比ftp简单的多,你压根都不需要配置FTP服务。它是Linux同Windows进行ZModem文件传输的命令行工具。一般是默认不安装的,你可以通过一条安装命令即可拥有:linux系统安装:yum install lrzszUbuntu系统安装:sudo apt-get install lrzsz然后讲解一下sz、rz分别执行作用是什么?sz:从服务器下载文件到本地。rz:从本地上传文件到服务器。 安装该命令后,所以我们直接在服务器中,执行rz上传命令,只需要输入rz,然后回车,即会跳转打开本地桌面文件夹,然后选择我们打包好的xxx.jar。 然后选择好后,点击打开,我们就可以看到jar包在进行上传。 随着jar包的大小,上传时间也有所不同,越大则上传越慢。我们稍等片刻,毕竟我的jar包有66.3MB呢。如下提示是:jar包是上传完成了。3️⃣检查java环境🔅 我们来进行java环境配置是否正常。查看下java版本即可。java -version如下是执行java -version截图: 如上,我们可以看到jdk安装没有,jdk版本是1.8。 至于如下操作,我们可以看我的下篇:springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存(下篇)。保证内容不会过多。4️⃣jar后台启动💭... ...5️⃣控制台日志输出保存💭... ...6️⃣外部配置文件启动💭... ...7️⃣查看实时滚动日志💭... ...8️⃣总结💭... ... 剩下的教学内容,我打算放到下一期来讲,这一期我就先讲这么多。 ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~五、往期推荐🔥springboot之jar包Linux后台启动部署及滚动日志查看且日志输出至文件保存(下篇)Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出六、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥环境:阿里云服务器 + centos7.6三、 排错分析🔥 今天发生这件事,我是非常好奇啊。由于我是本地直接通过 [ rz ] 命令上传,结果发现一件非常神奇的事,有个文件我竟然删不掉,蛤?总不会一直都存在服务器了吧?这可不行啊,那以后再遇到该命名的文件,不是越积越多,服务器要是内存容量大那就忽略不计,但是我看着就不爽,碍眼。 我要想办法解决它!哪怕是今晚不睡了,于是乎...如下是具体报错截图: 我现在唯一的心思就是绞尽脑汁把这个名为 review-web(2).zip 的压缩包给删掉。于是乎,一个小时过去了,皇天不负有心人,我共研究出两种解决方案,供小伙伴们参考...四、 解决方案🔥我是才发现 linux5.0 版本之后,命令是不能带有括号的,如若需要带括号便需要转译。如下是两种转义方式:**方式1:**只需在括号前后加上反斜杠[ \ ]。转义括号为可读。**方式2:**在括号的两端加上[ " " ],注意:这是英文输入法下的双引号哦。五、实例演示🔥演示方式1: 我直接输入[ review-web\( ],然后键盘摁[ Tab ]键,它会自动补全并找到上述的review-web(2).zip,这样就能选中带括号的目标文件了。 比如我目标是删除这个文件,那我就直接可以进行进行rm -rf + targetFile 命令进行删除了。演示方式2: 在你的目标文件中,在带括号的两边分别用""包裹起来。具体请看如下演示截图: 或者直接用双引号""包裹带括号的文件名,切记是英文输入法下的双引号哦。具体请看如下演示截图: 如上就是解决此报错的两种方案,如果你有更好的解决方案,欢迎评论区留言分享给bug菌呀,一起学习才能变得更强,懂得分享才能获得更多快乐,毕竟我一路写过来,其实也没有说要为了干啥,第一是为了记录自己的程序人生,二来是对自己以后的自己有个交代,自己做了哪些,这些都是完整的记录在博客之中,所以也很喜欢在博客平台记录自己解决问题的一些看法及一些解决措施,得之于你赠之与你。... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期推荐🔥如何使用Mybatis-plus实现字段内容的自动填充?不会我就教你。如何在swagger2中配置header请求头等参数信息?(若不会,我便手把手教你)Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出七、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥环境:jdk1.8 + springboot 2.3 + 阿里云centos7.6三、排错 🔥 git提交本地修改内容push到gitlab代码库时,竟然出现如下报错:remote: You are not allowed to push code to this project. 就很离谱啊,我能拉取,但是无法push,莫不是没给我操作权限?很怀疑啊,于是我自己借来root账户,进行一波侦查,因为我老大一直说他赋权限了,我表示很怀疑,他是不是记混了。四、解决方案🔥 首先我们来确定一下当前git账号是否拥有该项目的写入操作权限?如下是具体操作步骤:你们可以跟着步骤走,如果有小伙伴gitlab版本不同而导致列表展示不是一致的,那也差不多就是在项目中的项目信息栏目里。第一步:打开项目成员列表 找到在gitlab的对应项目,然后找到项目信息,鼠标悬浮项目信息会展示一个右侧菜单,我们点击成员。第二步:查看git用户的操作权限。 点击成员后,会打开一个成员列表页,项目成员中有一列。专门展示对应操作权限,即如下截图中的最大角色列。把对应的操作权限加上即可。 实际截图很明显可以看到该用户还是[ Guest ] 角色。顾名思义,就是访客角色,角色当中权限最低的一个,当然就没有编辑push代码等操作权限啦。 我们直接将其角色改成 [ Maintainers ]好了,除Owner外的最大角色,名为:项目维护者。这样再进行git push操作试试,查看是否还会报上述错。 经测试, push成功了。验证成功。五、gitlab角色科普🔥 再者,我们来温习一下,上述最大角色中所涉及到的五种角色,分别如下:Guest (访客)可操作权限:可以创建issue、发表评论。不可操作权限:不能读写版本库。Reporter(记者)可操作权限:可以克隆代码。(QA、PM可以赋予这个权限)不可操作权限:不能提交。Developer(开发者)可操作权限:可以克隆代码、开发、提交、push。(RD可以赋予这个权限)不可操作权限:不能进行读写版本库。Maintainers(维护者)可操作权限:可创建项目、添加tag、保护分支、添加项目成员、编辑项目,核心RD负责人可以赋予这个权限。不可操作权限:不能读写版本库。Owner(所有者)可设置项目访问权限 - Visibility Level、删除项目、迁移项目、管理组成员。(开发组leader可以赋予这个权限)。其中,Gitlab中的组和项目又分三种访问权限:分别为:Private、Internal、Public。... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期推荐🔥如何使用Mybatis-plus实现字段内容的自动填充?不会我就教你。如何在swagger2中配置header请求头等参数信息?(若不会,我便手把手教你)Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出七、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、前言🔥**环境:**jdk1.8 + springboot 2.3 + 阿里云centos7.6三、摘要 🔥java.net.SocketTimeoutException: connect timed out报错完整截图:这是我查看本地nohup日志所截图。访问swagger在线接口文档也是,500?四、排错 🔥 无奈至极,前端还急着要接口文档,我这咋部署失败了,虽然是项目第一次部署,但是我拍着胸膛说半分钟搞定,这眼瞅着一分钟都要过去了。 情急之下,我尝试了一个大胆的举动,我怀疑是不是服务器防火墙开启的缘故,因为我查看了8889项目启动端口之前是有配置过的, 因为刚开始开通阿里云服务器就统计了端口配置,我便配置了8889端口,所以我把怀疑的苗头指向了防火墙。 于是我 telnet 测试一下映射端口和远程访问主机,果然连接不上。 然后本机也测试连接了一下,也是连不上; 我们都知道阿里云它有外部防火墙跟内部防火墙。外部防火墙,我是已经自定义端口了。如果你不会,你也用的是阿里云服务器,那你进入阿里云控制台,找到配置规则,点击一下会出现有防火墙规则设置的,然后添加规则就行。 所以,我尝试了下,把内外防火墙都给关了,果不其然,8889端口可以访问上了。如下是关闭防火墙再测试连接截图:五、解决方案🔥 所以如果你遇到此问题,你开启指定端口或者直接关闭防火墙后就行了!就这么简单,不行都试试。但是不建议这么做啊,毕竟服务器容易被黑,被注入挖矿,哇咔咔。... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期推荐🔥如何使用Mybatis-plus实现字段内容的自动填充?不会我就教你。如何在swagger2中配置header请求头等参数信息?(若不会,我便手把手教你)Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出七、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言🔥👨🎓作者:bug菌✏️博客:CSDN、掘金等💌公众号:猿圈奇妙屋🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。 哈喽,小伙伴们,我是bug菌呀👀,不知不觉就一年又4月,正是踏青郊游的好时节。可上海疫情,除了在家远程办公就是看点技术顺便卷点文章。这不是4月又出了更文活动,逼自己一把,坚持每天做点内容输出,养成写作习惯,将来你会回来感谢曾经的自己坚持了一把才有此刻的辉煌成就。 小伙伴们在批阅文章的过程中如果觉得文章对您有一丝丝帮助,还请别吝啬您手里的赞呀,大胆的把文章点亮👍吧,您的点赞三连(收藏⭐️+关注👨🎓+留言📃)就是对bug菌我创作道路上最好的鼓励与支持😘。时光不弃🏃🏻♀️,掘金不停💕,加油☘️二、环境说明🔥**环境:**jdk1.8 + idea2019.3 + centos7.6 + redis 6.2.6三、摘要 🔥 说起来我这项目被开垦的,项目配置了redis对吧,但是项目启动的时候,redis是否能连通上这任何信息也没有打印,这就很荒唐了。若是上生产,对于我们这些娄娄来说,压根没有生产账号分配,就算领导有,可能会在第一时间给你先预测一下么,很有可能就因为redis服务挂了或者项目redis配置有问题而导致系统无法正常登陆,这就坐等被用户喷,登不上!辣鸡系统,什么破玩意... emmm,貌似被喷也得接受,无法供给正常操作,所以这肯定是不行的,能预测的事,为啥不做呢,所以啊,你可以在自启动的时候就把redis连接加上,就跟你配置mysql连接是一样的,项目启动的第一眼就能看到控制台消息打印。 所以呀?在项目中如何手撸代码自测系统与redis能否连接上呢?这样也好歹能提前定义问题并且解决问题。 老板:这个需求很简单,下班就要!!!四、实现方案🔥 我们都知道,redis 有提供一个名为 jedis[redis.clients.jedis.JedisPool] 的玩意儿,对吧,那它是什么呢? Jedis是集成了redis的一些命令操作,既封装了redis的java客户端,并且提供连接池管理。这是重点,它提供连接池管理,说明肯定是可以通过它来自测连接redis服务。 如下请看,Jedis类中就有提供入参,我圈选出来的就是通过它可以设置redis的服务ip,端口。 接下来我就来new 一个Jedis类。然后再调用Jedis类自带的ping()方法。怎么测试呢?写个测试类测试一波,看看会返回什么。 如果redis有设置了密码,那么你还得再加个redis密码认证。具体如下:jedis.auth(password);//密码大家请看debug截图:你发现了什么没有? Redis的Ping 命令使用客户端向Redis服务器发送一个 ping,如果服务器运作正常的话,会返回一个 [ PONG ],否则返回一个连接错误。所以这就是确定redis服务与本项目是否连通的依据。 现在测试redis是否能连接成功的方法已经写好了,但是要项目自启的过程中就执行该test方法,这又怎么办呢?这肯定大部分小伙伴都知道的,实现方式很多,我就演示一种最常见的方式来实现吧!实现ApplicationRunner类的run()方法即可。import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Date; /** * 继承Application接口后项目启动时会按照执行顺序执行run方法 * 通过设置Order的value来指定执行的顺序 */ @Component @Order(value = 1) public class RedisMediator implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { System.out.println("调用你的redis测试方法"); } }五、测试🔥我们来实际测试一下:重新启动项目,我们可以看下控制台的打印消息: 这redis连接成功的内容被打印了,证明redis服务与本项目是互通的。六、附上完整源码🔥 附上完整代码:由于我本项目有设置redis密码,所以加了密码校验。@Slf4j @Component public class RedisMediator implements ApplicationRunner { //读取redis ip @Value("${spring.redis.host}") private String host; //读取redis 端口 @Value("${spring.redis.port}") private Integer port; //读取redis 密码 @Value("${spring.redis.password}") private String password; /** * 测试redis连接是否正常。 */ public void testConnectRedis() { //连接本地的 Redis 服务 Jedis jedis = new Jedis(host, port); jedis.auth(password);//密码 //查看服务是否运行 try { jedis.ping(); System.out.println("---------------redis预连接成功!---------------"); } catch (Exception e) { log.error("Could not connect to Redis at " + host + ":" + port + " Connection refused!"); throw new RuntimeException("redis连接异常!"); } } @Override public void run(ApplicationArguments args) throws Exception { //调用测试方法。 testConnectRedis(); } }... ... ok,以上这样就好啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~七、往期推荐🔥如何使用Mybatis-plus实现字段内容的自动填充?不会我就教你。如何在swagger2中配置header请求头等参数信息?(若不会,我便手把手教你)Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出八、文末🔥 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。 我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!
一、前言 上一期,我是带着大家入门了springboot集成RabbitMq,今天我再来一期kafka的零基础教学吧。不知道大家对kafka有多少了解,反正我就是从搭建开始,然后再加一个简单演示,这就算是带着大家了个门哈,剩下的我再后边慢慢出教程给大家说。二、什么是kafka kafka是linkedin开源的分布式发布-订阅消息系统,目前归属于Apache的顶级项目。主要特点是基于pull模式来处理消息消费,追求高吞吐量,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统。 一开始的目的是日志的收集和传输。0.8版本开始支持复制,不支持事务,对消息的丢失,重复,错误没有严格要求 适用于产生大量数据的互联网服务的数据收集业务。在廉价的服务器上都能有很高的性能,这个主要是基于操作系统底层的pagecache,不用内存胜似使用内存。 综上所述,kafka是一款开源的消息引擎系统(消息队列/消息中间件) 分布式流处理平台。三、Windows安装kafka1、下载kafka安装包下载地址:www.apache.org/dyn/closer.…下载完后是这么个东西:2、下载好后,进行解压3、配置修改进入config目录,修改server.properties文件把 log.dirs的值改成 ./logs4、kafka启动在你的安装目录下的bin\windows目录上直接输入cmd然后回车。执行命令:输入kafka-server-start.bat ../../config/server.properties然后可以看到控制台启动报错:可以看到,kafka还依赖于zookeeper。所以我们接下来再安装zookeeper,启动zookeeper后再启动kafka试试。四、安装zookeeper1、下载zookeeper安装包下载地址:mirrors.cnnic.cn/apache/zook…2、解压解压到指定的目录下;然后再将apache-zookeeper-3.7.0-bin也一并下载,进行解压完后,将目录下的lib文件夹复制到apache-zookeeper-3.7.0主目录下:否则后续启动肯定会报错。3、修改配置修改zoo_sample.cfg 文件名(./conf) 为 zoo.cfg同样再编辑它指定日志位置,具体配置文件如下:(这里使用notepad++小绿本进行编辑);我这里是指定在同级目录下了。这个你根据实际情况而定。#原目录;直接注释掉 #dataDir=/tmp/zookeeper #指定新目录 #保存数据的目录 dataDir=./data # 保存日志文件的目录 dataLogDir=./log4、启动zookeeper进入到bin目录,并且启动zkServer.cmd,这个脚本中会启动一个java进程。如果你们遇到这个报错,请看上边第2点zookeeper解压。报错是因为找不到类包缺少lib这个jar包文件夹,所以你得下载bin包并把lib依赖都复制过来。输入如下命名进行启动zookeeperzkServer.cmd具体启动如下:正常启动截图:再启动后jps可以看到QuorumPeerMain的进程。直接win+R 输入cmd然后再输入如下命令即可进行查询。启动命令如下:jps -l -v启动客户端连接一下:进入到/bin目录下,执行如下命令:zkCli.cmd 127.0.0.1:2181如上可以看到, zookeeper启动ok。至此,zookeeper就安装完成啦。所以我们再来启动一下kafka看看,是否还会跟刚才一样报错。切记不要关zookeeper启动服务小黑窗,也就是你执行那串命令的窗口,若是关了你再重新启动即可。zkServer.cmdkafka启动成功截图:五、kafka项目集成1、pom引入<!--kafka依赖--> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency>2、配置kafka#配置kafka 服务器 spring: kafka: bootstrap-servers: 127.0.0.1:9092 producer: # 发生错误后,消息重发的次数。 retries: 0 #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。 batch-size: 16384 # 设置生产者内存缓冲区的大小。 buffer-memory: 33554432 # 键的序列化方式 key-serializer: org.apache.kafka.common.serialization.StringSerializer # 值的序列化方式 value-serializer: org.apache.kafka.common.serialization.StringSerializer # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。 # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。 # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。 acks: 1 consumer: # 自动提交的时间间隔 在spring boot 2.X 版本中这里采用的是值的类型为Duration 需要符合特定的格式,如1S,1M,2H,5D auto-commit-interval: 1S # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理: # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录) # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录 auto-offset-reset: earliest # 是否自动提交偏移量,默认值是true,为了避免出现重复数据和数据丢失,可以把它设置为false,然后手动提交偏移量 enable-auto-commit: false # 键的反序列化方式 key-deserializer: org.apache.kafka.common.serialization.StringDeserializer # 值的反序列化方式 value-deserializer: org.apache.kafka.common.serialization.StringDeserializer listener: # 在侦听器容器中运行的线程数。 concurrency: 5 #listner负责ack,每调用一次,就立即commit ack-mode: manual_immediate missing-topics-fatal: false3、topic初始化package com.example.demo.config; import org.apache.kafka.clients.admin.NewTopic; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * topic初始化 * * @author luoYong * @version 1.0 * @date 2022/2/28 17:39 */ @Configurationpublic class KafkaConfig { /** * 创建一个名为topic.test的Topic并设置分区数为8,分区副本数为2 */ @Bean public NewTopic initialTopic() { return new NewTopic("topic.test", 8, (short) 2); } }4、定义一个kafka消息发送端package com.example.demo.component.kafka; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.support.SendResult; import org.springframework.stereotype.Component; import org.springframework.util.concurrent.ListenableFutureCallback; /** * kafka消息发送端 * * @author luoYong * @version 1.0 * @date 2022/2/28 17:40 */ @Component @Slf4j public class KafkaProducer { @Autowired private KafkaTemplate<String, Object> kafkaTemplate; public void send(Object obj) { String obj2String = JSONObject.toJSONString(obj); // 发送消息 kafkaTemplate.send("topic.test", obj).addCallback(new ListenableFutureCallback<SendResult<String, Object>>() { @Override public void onFailure(Throwable throwable) { // 发送失败的处理 log.info("topic[{}] 生产者 发送消息失败[{}]", "topic.test", throwable.getMessage()); } @Override public void onSuccess(SendResult<String, Object> stringObjectSendResult) { // 成功的处理 log.info("topic[{}] 生产者 发送消息成功[{}]", "topic.test", stringObjectSendResult.getProducerRecord().value()); } }); } }5、定义一个kafka消息消费端package com.example.demo.component.kafka; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.springframework.kafka.annotation.KafkaListener; import org.springframework.kafka.support.Acknowledgment; import org.springframework.kafka.support.KafkaHeaders; import org.springframework.messaging.handler.annotation.Header; import org.springframework.stereotype.Component; import java.util.Optional; /** * 消息接收端-支持多端消费 * * @author luoYong * @version 1.0 * @date 2022/2/28 17:42 */ @Component@Slf4jpublic class KafkaConsumer { @KafkaListener(topics = "topic.test", groupId = "topic.group1") public void topicTest(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) { Optional message = Optional.ofNullable(record.value()); if (message.isPresent()) { Object msg = message.get(); log.info("客户端 A 消费了: Topic[{}] Message[{}]", topic, msg); ack.acknowledge(); } } @KafkaListener(topics = "topic.test", groupId = "topic.group1") public void topicTest1(ConsumerRecord<?, ?> record, Acknowledgment ack, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) { Optional message = Optional.ofNullable(record.value()); if (message.isPresent()) { Object msg = message.get(); log.info("客户端 B 消费了: Topic[{}] Message[{}]", topic, msg); ack.acknowledge(); } } }6、定义一个测试类进行测试/** * @author luoYong * @version 1.0 * @date 2022/2/24 17:02 */ @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class KafkaTest { @Autowired private KafkaProducer kafkaProducer; @Test public void testSendMsg() { String msg = "hello"; kafkaProducer.send(msg); } }7、测试结果如下 ... ... ok,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~六、往期热门推荐Springboot系列(十七):集成RabbitMqSpringboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出、word导出功能等,对吧。不知道你们都掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习,查缺补漏,方能成长的快。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板导出多页面功能集成easypoi实现excel模板图片导出集成easypoi实现excel多sheet导入导出 其实日常除了以上这些文档操作以外,还有一种场景就是导出以pdf格式的文档,不知道你们在平时开发中是否有遇到过?就是将excel模板直接以pdf文档格式导出,虽然完全可以手动转pdf,但是就是要求开发能实现以excel模板导成pdf。 其实实现过程上还是挺简单的,毕竟我已经教过大家如何通过easypoi实现excel导出,我们只需要在excel导出的基础上,再把流重新写入到pdf格式文档上就可以了。基本思路就是这样。 但是别担心,我今天就是来带着大家从零敲一遍,希望大家能提前备好课,届时如果真遇到了这个需求,那你肯定是全场最靓的仔。 接下来我就开始啦,同学们可得竖起耳朵好好听讲哦~我会带着大家一步一步实现它,至于怎么实现,接着往下看。二、环境配置 实现excel模板导出这部分我们还是得依赖于easypoi来做。所以你们只需要在你们的pom.xml依赖中加上如下easypoi的starter依赖包即可,添加过的同学就可以不用再重复导入了。<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> 另外,我们还需要引用一个包,专门用于excel转pdf工具。所以我们也要导入一下。但是这个包目前人家是要收费的,远程maven镜像是拉取不到本地的,借此我们需要本地导一下这个包就行。 如下是我提供一种导入方式,就是指定引用本地jar而不自动去maven源拉取。具体细节就不详说啦,你们按照如下格式即可。<!--excel转pdf--> <dependency> <groupId>e-iceblue</groupId> <artifactId>spire.xls.free</artifactId> <version>2.2.0</version> <scope>system</scope> <systemPath>${project.basedir}/libs/spire.xls.free-2.2.0.jar</systemPath> </dependency> 直接引入本地目录jar就可以,具体请看下方截图:至于jar包下载,你们就自己下吧哈,原来我是添加了云盘地址,但是显示不出被屏蔽了,所以这个大家需要就自己下载一下吧,给大家提供一个下载地址下载链接。三、多sheet工作表excel导入1、定义一个excel模板 由于要进行excel模板的读取,所以我们肯定是要定义好excel模板,然后将模板进行数据渲染即可,所以我们还是老样子,通过fe进行赋值内部循环。具体请看截图:2、Controller添加excel导入方法 我们先来定义一个pdf导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * 导出pdf文件 */ @GetMapping("/export-for-pdf") @ApiOperation(value = "导出pdf文件", notes = "导出pdf文件") public void exportPdfUsers(HttpServletResponse response) { userService.exportPdfUsers(response); }3、定义导入pdf接口/** * 导出pdf文件 */ void exportPdfUsers(HttpServletResponse response);4、实现导入方法(核心) 如下这个导入实现类就很关键了,我们还是直接使用easypoi提供的ExcelExportUtil工具类的importExcel()方法,然后通过它返回的Workbook,来进一步进行流的写入,最后通过刚导入的spire.xls.free写入pdf导出至指定目录下保存。大致的思路就是这样,具体请看如下的实现代码,如果你有更好的实现方式,请记得que我哦。具体实现代码如下:/** * 导出pdf文件 */ @Override public void exportPdfUsers(HttpServletResponse response) { //准备导出数据 Map<String, Object> mapList = new HashMap<>(); //准备导出数据 List<Map<String, Object>> listUsers = new ArrayList<>(); try { //指定excel模板;我这是在项目根目录下创建了一个template文件夹存放excel导出模板文件 TemplateExportParams params = new TemplateExportParams("./template/导出excel模板.xlsx"); //从数据库查询到数据 List<UserEntity> users = this.list(); //定义一个原子序列 AtomicInteger atomicInteger = new AtomicInteger(1); users.forEach(user -> { Map<String, Object> map = new HashMap<>(); map.put("id", atomicInteger.getAndIncrement()); map.put("name", user.getName()); map.put("age", user.getAge()); map.put("sex", user.getSex()); map.put("address", user.getAddress()); map.put("describes", user.getDescribes()); //添加到集合中,一个map就是一行 listUsers.add(map); }); mapList.put("users", listUsers); //调用exportExcel() Workbook workbook = ExcelExportUtil.exportExcel(params, mapList); //定义一个字节输出流 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); //将excel文件写入到新的输出流 workbook.write(outputStream); //将字节数组放置到内存里面 ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); //导入xls包引用Workbook区分poi提供的Workbook com.spire.xls.Workbook wb = new com.spire.xls.Workbook(); wb.loadFromStream(inputStream); //设置字段在一页中显示全 wb.getConverterSetting().setSheetFitToPage(true); //指定并指定目录保存文件 wb.saveToFile("D:/pdf/学生基本信息表.pdf"); } catch (Exception e) { e.printStackTrace(); } }注意:第一点:我是将pdf导出指定了目录为本地D:/pdf/学生基本信息表.pdf下,没有pdf文件夹则会自动创建一个。但如果你们是需求开发的话,你们得根据自己的实际开发环境来获取存放目录,我这里只是演示。其实也可以在页面开个口子,由用户进行手动选址,然后再将用户所选择的目录地址作为入参,这样就可以不用考虑用户保存在何位置了。第二点:与excel模板导出方法一致,后续这个导出方法可进行封装调用。第三点,如下是我的演示项目存放excel导出模板的目录结构截图,所以我就直接写定了模板的相对位置。 接下来,交给我们的就只剩下测试了。5、浏览器测试 我们打开浏览器,在地址栏,输入我们刚才在Controller暴露出来的接口地址,比如我的:http://localhost:8080/user/export-for-pdf 。而你就按你的接口地址填写然后回车进行访问即可。具体浏览器访问截图如下: 输入地址回车之后,查看一下控制台有无报错,我们再检查一下,指定文件夹是否已经存有该xxx.pdf文件。实际结果可以看到xxx.pdf文件已经存在该pdf文件夹下,证明pdf导出方法没有问题,还是很成功嘛,剩下的就是检查pdf文档数据及排版格式是否都按预定设置的一样? 打开pdf文档,我是没想到,字段在一页展示不全,竟然分页展示,这样就不是很友好啊,那这肯定得想办法解决啊。不要急, bug菌替大家查阅了相关资料才知道有个属性可以设置不分页展示字段列,.setSheetFitToPage()设置为true即可实现字段列在一页中展示全。如下是未设置了该属性导出的pdf截图: 我们把它加上这个属性并设置为true,我们重启项目导出再来看下效果。//设置字段在一页中显示全 wb.getConverterSetting().setSheetFitToPage(true); 添加完这个属性之后,具体如下效果截图,相比上面的效果,这很明显是所有字段都归于一页展示了,而没有再分页。 ... ... ok,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~三、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十六):集成easypoi实现excel多sheet表导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,word的导入导出,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板导出多页数据集成easypoi实现word模板遍历显示所有数据集成easypoi实现word模板图片导出集成easypoi实现excel图片导出集成easypoi实现excel多sheet表导入 在前几期我们是学习了以上内容,比如如何实现excel单sheet的导入导出,excel多sheet的导入等,谈及多sheet组导入,那肯定会想到如何实现多sheet组的导出?哈哈哈,肯定有办法进行针对多sheet表的导出。上一期我们是重点讲解了如何集成easypoi进行excel表多sheet工作表的导入,所以这期与上一期相呼应,bug菌这就来给大家讲讲如何通过easypoi实现多sheet表的excel导出。 别担心,我今天就是带着大家从零敲一遍,希望大家都能提前备好课,届时如果在项目中真遇到了这个需求,那你肯定会是全场最靓的仔。 "这个需求很简单,我几分钟给你搞定。" 接下来我就开始啦,同学们可得竖起耳朵好好听讲哦~我会带着大家一步一步实现它,至于怎么实现,接着往下看。二、环境配置 实现excel多sheet工作表的导入导出功能,我们还是得依赖于easypoi来做。所以你们只需要在你们的pom.xml依赖中加上如下easypoi的starter依赖包即可,添加过的同学就可以不用再重复导入了。<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>三、多sheet工作表excel导出如下我们先分别来定义两个excel导出vo,目的是为了让大家看清,分不同的sheet存放不同的数据。届时后边也需要用到的,毕竟对应不同的vo进行导出。1、先来定义两个导入实体类。ExportExcelLog.java@Data public class ExportExcelLog implements Serializable { private static final long serialVersionUID = 1L; /** * @Excel 作用在一个filed上面,对列的描述 * @param name 列名 * @param orderNum 下标,从0开始。 */ @Excel(name = "url", width = 20.0) private String url; @Excel(name = "ip", width = 20.0) private String ip; //字段是Date类型则不需要设置databaseFormat @Excel(name = "请求时间", format = "yyyy-MM-dd", width = 20.0) private Date operationTime; @Excel(name = "接口返回状态码", width = 20.0) private int code; }ExportExcelUser.java@Data public class ExportExcelUser implements Serializable { private static final long serialVersionUID = 1L; /** * @Excel 作用在一个filed上面,对列的描述 * @param name 列名 * @param orderNum 下标,从0开始。 */ @Excel(name = "姓名", width = 10.0) private String name; @Excel(name = "年龄", width = 10.0) private Integer age; //字段是Date类型则不需要设置databaseFormat @Excel(name = "出生年月", format = "yyyy-MM-dd", width = 20.0) private Date bornDate; //如果数据库如果是string类型,这个需要设置这个数据库时间格式 format:输出时间格式 @Excel(name = "入学时间", databaseFormat = "yyyyMMdd", format = "yyyy-MM-dd", width = 20.0) private String enterSchoolTime; //replace:单元格下拉框,_0表示下拉顺序 suffix:文字后缀 比如:男->男生 @Excel(name = "性别", width = 10.0, replace = {"男_0", "女_1"}, suffix = "生", addressList = true) private String sex; @Excel(name = "地址", width = 30.0) private String address; @Excel(name = "头像", type = 2, width = 30.0, height = 30.0, imageType = 1) private String image; @Excel(name = "用户描述", width = 20.0) private String describes; } 如上我是分别定义了user跟logInfo类,你们也可以随便可以那两个实体进行定义,这个不一定非要跟我一样的。2、Controller添加excel导入方法 接下来,我们定义一个excel导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试代码的完整性。具体代码实现如下:/** * excel多sheet导出 */ @GetMapping("/export-for-sheets") @ApiOperation(value = "excel多sheet导出", notes = "excel多sheet导出") public void exportSheetUsers(HttpServletResponse response) { userService.exportSheetUsers(response); }3、定义导入接口/** * excel多sheet导出 */ void exportSheetUsers(HttpServletResponse response);4、实现导入方法(核心) 整篇文的核心就在于此了,主要是要理解怎么按照步骤进行数据封装,然后使用那个excel导出方法,其他的跟你导出excel单sheet是一样的。具体实现代码如下:UserServiceImpl.java/** * excel多sheet导出 */ @Override public void exportSheetUsers(HttpServletResponse response) { //功能描述:把同一个表格多个sheet测试结果重新输出, Workbook workBook = null; try { // 创建参数对象(用来设定excel的sheet1内容等信息) ExportParams userExportParams = new ExportParams(); // 设置sheet得名称 userExportParams.setSheetName("用户表"); // 设置sheet表头名称 userExportParams.setTitle("用户列表"); // 创建sheet1使用得map Map<String, Object> userExportMap = new HashMap<>(); // title的参数为ExportParams类型,目前仅仅在ExportParams中设置了sheetName userExportMap.put("title", userExportParams); // 模版导出对应得实体类型 userExportMap.put("entity", ExportExcelUser.class); //转成导出vo类型 List<ExportExcelUser> users = this.changeType(this.list()); // sheet1中要填充得数据 userExportMap.put("data", users); // 创建参数对象(用来设定excel的sheet2内容等信息) ExportParams logInfoExportParams = new ExportParams(); logInfoExportParams.setTitle("日志列表"); logInfoExportParams.setSheetName("日志表"); // 创建sheet2使用的map Map<String, Object> logInfoExportMap = new HashMap<>(); logInfoExportMap.put("title", logInfoExportParams); logInfoExportMap.put("entity", ExportExcelLog.class); //查询log数据 List<LogInfo> logInfoEntitys = logInfoMapper.selectList(new QueryWrapper<>()); //转成导出vo类型 List<ExportExcelLog> logInfos = this.changeInfoType(logInfoEntitys); // sheet2中要填充得数据 logInfoExportMap.put("data", logInfos); // 将sheet1、sheet2使用得map进行包装 List<Map<String, Object>> sheetsList = new ArrayList<>(); //后续增加sheet组,则后面继续追加即可; sheetsList.add(userExportMap); sheetsList.add(logInfoExportMap); // 执行方法 workBook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF); //设置编码格式 response.setCharacterEncoding(StandardCharsets.UTF_8.name()); //设置内容类型 response.setContentType("application/octet-stream"); //设置头及文件命名。 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("用户及操作日志导出.xls", StandardCharsets.UTF_8.name())); //写出流 workBook.write(response.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } finally { if (workBook != null) { try { //强行关流 workBook.close(); } catch (IOException e) { e.printStackTrace(); } } } } 这里,我们先来进行断点一下,主要是想看一下,我们所赋值的数据是否被添加进去。如下截图,很明显我们可以看到数据都有被put进去。数据是存放在data中的。总共分两个sheet,所有数组size = 2。 以上,有几点我要特别解释一下:查看exportExcel(List<Map<String, Object>> list, ExcelType type)方法,可得知,进行map.put(),其中的key必须是"title","entity"和"data"。已经可以在源码方法可进行查看,所以设置参数类型等,都得按照这仨个key_name 进行put赋值。分几个sheet就得封装几个Map<String, Object>,然后再将map添加进List<Map<String, Object>>中即可。5、浏览器测试接口 接下来,万事俱备,只欠东风啦,我们只需要进行对接口进行访问,一试便知,你的接口代码是否有问题,目前就是自己也不是很笃定自己写的就一定没问题,所以对于导出接口,最好的方式就是直接通过浏览器进行导出调用,在浏览器地址栏,输入:http://localhost:8080/user/export-for-sheets,你就按你定义的访问接口地址进行访问即可。 很明显, 可以正常导出,一看458kb大小,可想而知,数据内容肯定是有的,关键就是是否有数据分sheet表分?这得打个问题,等待会儿,下载好打开一看便知。导出excel实际截图: 如下是sheet1导出的实际数据,很明显,与自己设置的导出vo设置是完全一致的。说明sheet1导出数据功能是没有问题。 接下来就是切换到sheet2查看了,看看是否数据、字段、标题是否都被设置上。 看如上截图,很明显啊,在一个excel中,成功将不同的数据导出到了同一个excel中的两个sheet表中,这点是值得庆幸的,哈哈哈哈,毕竟我也害怕导出效果差人强意,误人子弟,还好,基本达到了本期重点教学内容的实现要求了,所以,如果对屏幕前的你而言,看到这里,还等什么,赶紧给bug菌一个赞吧,实在是太强了,哈哈哈哈,算这几期内容中,比较难的知识点了。 ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导入Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,word的导入导出,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板导出多页数据集成easypoi实现word模板遍历显示所有数据集成easypoi实现word模板图片导出集成easypoi实现excel图片导出集成easypoi实现excel多sheet导出 所以,今天我们就来玩点高级的,针对前几期讲述的内容,excel都是在单sheet表中进行数据的导入或导出,但是真实环境肯定会不止一个sheet工作表,如果涉及多个sheet表在一张excel中,那么excel的导入导出功能,你们都玩过么?假设现在的需求就是要你读取一张excel表中的数据进行数据的导入,以及根据不同的sheet工作表进行数据的导出,这两个需求,要求你们最快实现,越快越好!哈哈哈哈,完蛋,好强烈的工作模式,是滴,没错,这就是战场,这就是实战。 别担心,我今天就是带着大家从零敲一遍,希望大家能提前备好课,届时如果真遇到了这个需求,那你肯定是全场最靓的仔。 接下来我就开始啦,同学们可得竖起耳朵好好听讲哦~我会带着大家一步一步实现它,至于怎么实现,接着往下看。二、环境配置 实现excel多sheet工作表的导入导出功能,我们还是得依赖于easypoi来做。所以你们只需要在你们的pom.xml依赖中加上如下easypoi的starter依赖包即可,添加过的同学就可以不用再重复导入了。<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency> 复制代码三、多sheet工作表excel导入1、先来定义一个导入实体类。/** * excel导入user参数 * * @author luoYong * @version 1.0 * @date 2022/2/15 14:34 */ @Data public class ImportUser implements Serializable { private static final long serialVersionUID = 1L; /** * @Excel 作用在一个filed上面,对列的描述 * @param name 列名 * @param orderNum 下标,从0开始。 */ @Excel(name = "姓名", orderNum = "0",width = 10.0) private String name; @Excel(name = "年龄", orderNum = "1",width = 10.0) private Integer age; @Excel(name = "性别", orderNum = "2",width = 5.0) private String sex; @Excel(name = "地址", orderNum = "3",width = 30.0) private String address; @Excel(name = "用户描述", orderNum = "4",width = 20.0) private String describes; }2、Controller添加excel导入方法 我们先来定义一个excel导入方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * excel多sheet导入 */ @GetMapping("/import-for-sheets") @ApiOperation(value = "excel多sheet导入", notes = "excel多sheet导入") public void importForSheetUsers(@RequestParam("file") MultipartFile file) throws IOException { userService.importForSheetUsers(file); }3、定义导入接口/** * excel多sheet导入 */ void importForSheetUsers(MultipartFile file) throws IOException;4、实现导入方法(核心) 如下这个导入实现类就很关键了,我们还是直接使用easypoi提供的importExcel()方法,讲到这里,我就可以告诉大家ImportParams()有提供一个startSheetIndex属性,目的就是指定sheet工作表,所以我们就可以在excel导入方法的方式上再指定该sheet表即可,默认sheet是从下标0开始。 即我们就可以封装出一个excel导入方法了,这样不管是单sheet还是多sheet表都适用。详细使用请参考我写的:EasyPoiUtils.javapackage com.example.demo.util; import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.entity.ImportParams; import java.io.InputStream;import java.util.List; import java.util.NoSuchElementException; /** * @author luoYong * @version 1.0 * @date 2022/2/21 10:40 */ public class EasyPoiUtils { /** * 功能描述:根据接收的Excel文件来导入多个sheet,根据索引可返回一个集合 * * @param inputStream excel输入流 * @param sheetIndex 导入sheet索引 * @param titleRows 表标题的行数 * @param headerRows 表头行数 * @param pojoClass Excel实体类 */ public static <T> List<T> importExcel(InputStream inputStream, int sheetIndex, Integer titleRows, Integer headerRows, Class<T> pojoClass) { // 根据file得到Workbook,主要是要根据这个对象获取,传过来的excel有几个sheet页 ImportParams params = new ImportParams(); // 第几个sheet表页 params.setStartSheetIndex(sheetIndex); //设置表标题行数 params.setTitleRows(titleRows); //设置表头行数 params.setHeadRows(headerRows); List<T> list = null; try { //读取的excel数据集合 list = ExcelImportUtil.importExcel(inputStream, pojoClass, params); } catch (NoSuchElementException e) { throw new RuntimeException("模板不能为空"); } catch (Exception e) { e.printStackTrace(); } return list; } } 以上就是对exce读取多sheet实现的封装,剩下的就好办了,我们的实际业务有几个sheet就连续调用几次即可,然后就是把读取到的数据进行进行的表插入。 由于我这里只是为了演示图方便,就没有制定几个不同的导入类,而都是使用了同一个ImportUser,一般情况下,不同的sheet表一般对应的都是不同的数据内容,从多sheet表导入功能才被需要,这样一来省略每次的制定excel表的时间,提高了excel导入的拓展性与高效性,否则每导入一种excel就需要开发一个接口,这肯定是不被允许的,你们大家想想是不是?UserServiceImpl.java/** * excel多sheet导入 */ @Override public void importForSheetUsers(MultipartFile file) throws IOException { //分批进行读取excel的sheet工作表 //读取第一个sheet表 List<ImportUser> sheetOneUsers = EasyPoiUtils.importExcel(file.getInputStream(), 0, 1, 1, ImportUser.class); //读取第二个sheet表 List<ImportUser> sheetTwoUsers = EasyPoiUtils.importExcel(file.getInputStream(), 1, 1, 1, ImportUser.class); //批量插入 this.saveUsers(sheetOneUsers); this.saveUsers(sheetTwoUsers); } 如下是对用户数据进行插入的方法,我这里也贴一下吧,我是集成了mybatis-plus组件,所以针对单表操作还是很有好的,需要的小伙伴可以翻我前几期集成mybatis-plus组件的文章,手把手教学,一遍就会。/** * 批量插入用户数据 */ private boolean saveUsers(List<ImportUser> users) { //存放user List<UserEntity> userList = new ArrayList<>(); //转成user实体 for (ImportUser user : users) { //验空 if (user != null) { UserEntity userEntity = new UserEntity(user); userList.add(userEntity); } } //批量插入 return this.saveBatch(userList); }5、postman测试接口 由于要测试接口入参为MultipartFile 类,所以常规接口调用肯定不行,比如用swagger或者直接浏览器地址,所以这里我就推荐大家使用Postman工具,若是没有安装的,直接官网下载一个,一键安装即可,然后使用起来你只需要跟我下方截图一样即可。按照你指定的接口填好对应的接口地址及选好请求方式,然后就是file参数选择file文件类,传入你创建的excel模板文件,最后点击Send 即可。 实际截图如下:供大家参考。 调用完毕之后,由于无返回值且也没有500代码报错,查看下控制台是否有报错,最后就是验证数据库数据是否有被插入即可,这也是最直接最关键的验证步骤了,成功是否就在于此了,虽然很明显控制台sql打印了执行语句,其实就验证了数据进行了insert,但是我给大家进行代码演示就必须透明给大家,一一得到验证才是最好的教学,你们觉得呢? 如上图数据库表截图所示,查看了该项目所指定的数据库user表,很明显我们制定在excel中两个sheet表中的用户数据都insert进来了,这就验证了一件事,我们进行多sheet工作表excel导入功能点,所有代码已测试无误,大家尽可放心拿去学习或者直接cv使用啦。注意:如果你有遇到会插入空白数据的情况,我教你怎么去除,第一你先debug断点看实际读取到的excel数据size(total)是多少,然后有效数据(sj)是多少,做个减法(total-sj = n),表示有n条空数据,也就是你在excel有效数据的下方有n行是空行,可能是你填写了虽然肉眼看着是空,其实在读取的时候还是可以读取上的。物理法就是你只需要选中有效数据下方这n行,行删除且保存后,你再进行导入试试,就不会存在有读取到空行的现象了,或者就是逻辑过滤,针对为空的数据直接跳过。亲测有效,欢迎采纳!!四、excel模板附录 如下附上我导入的excel模板样例:sheet1截图:sheet2截图: ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~五、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十六):集成easypoi实现excel多sheet表导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,word的导入导出,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板导出多页数据集成easypoi实现word模板遍历显示所有数据集成easypoi实现word模板图片导出 既然word的导出都实现了多样化,那数据上要是如果携带图片,excel导出该怎么办呢?比如啊,导出所有用户的基本信息,而用户表中,就存有每一个用户的自画像,比如一寸照啥的,像这个情况,那excel导出应该得怎么实现呢?还是跟以前一样么?? 接下来我就开始啦,同学们可得竖起耳朵好好听讲哦~我会带着大家一步一步实现它,至于怎么实现,接着往下看。 我们先来看一眼,跟着本文实现下来的最终效果。如下图所示: 所以,如果你们对此有任何的疑问点,那么我都会带着你从零到一的学会它,基本事例代码我都会贴上,目的就是帮助大家更好的核对实现不出效果的同学有个基本的对照。二、引入pom依赖 实现excel图片导出,我们还是得依赖于easypoi来做。所以你们只需要在你们的pom.xml依赖中加上如下easypoi的starter依赖包即可,添加过的同学就可以不用再重复导入了。<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>三、实现Excel的图片导出1、先来定义一个导出实体类。 我这里就教大家采用注解的方式来进行excel的导出,后续有时间再给大家讲解如何通过excel模板的形式导出,好吧。主要就是基于@Excel 注解来实现的。具体实现代码大家请看下边,中途有些重点,我会在代码下方进行拓展讲解的,这点大家可以放心。ExportExcelUser.java/** * excel导入user参数 * * @author luoYong * @version 1.0 * @date 2022/2/15 14:34 */ @Data public class ExportExcelUser implements Serializable { private static final long serialVersionUID = 1L; /** * @Excel 作用在一个filed上面,对列的描述 * @param name 列名 * @param orderNum 下标,从0开始。 */ @Excel(name = "姓名", width = 10.0) private String name; @Excel(name = "年龄", width = 10.0) private Integer age; //字段是Date类型则不需要设置databaseFormat @Excel(name = "出生年月", format = "yyyy-MM-dd", width = 20.0) private Date bornDate; //如果数据库是string类型,这个需要设置这个数据库时间格式 format:输出时间格式 @Excel(name = "入学时间", databaseFormat = "yyyyMMdd", format = "yyyy-MM-dd", width = 20.0) private String enterSchoolTime; //replace:单元格下拉框,_0表示下拉顺序 suffix:文字后缀 比如:男->男生 @Excel(name = "性别", width = 10.0, replace = {"男_0", "女_1"}, suffix = "生", addressList = true) private String sex; @Excel(name = "地址", width = 30.0) private String address; //imageType 导出类型;1:从file读取;2:是从数据库中读取,默认是文件;同样导入也是一样的 @Excel(name = "头像", type = 2, width = 30.0, height = 30.0, imageType = 1) private String image; @Excel(name = "用户描述", width = 20.0) private String describes; }拓展:@Data:该注解为Lombok提供,目的是为了省略手动添加get set方法。@Excel(databaseFormat="xxxx"):如果数据库字段是string类型,则需要添加该属性,并加上该时间格式,比如:databaseFormat = "yyyyMMdd"。@Excel(format="xxxx"):format属性为excel展示格式。@Excel(replace={"xxx_0", "xxx_1","xxx_2"},addressList = true):表示单元格下拉框展示,_0、_1表示下拉值的前后顺序,从0往后排。要实现字段下拉,addressList属性必不可少。@Excel(suffix="xxx"):表示自动添加该xxx为你字段文字的后缀,比如:"98"-->98%。@Excel(imageType="xxx"):表示导出类型,imageType=1:从file读取;imageType=2:从数据库中读取;默认是文件,同样导入也是一样的。 其余的注解说明就直接跳过了,想了解的可以去看我前几期的文章,特别是第一期准备篇《springboot集成Easypoi之准备篇》。2、Controller添加excel导出方法 我们先来定义一个excel导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * excel批量用户导出 */ @GetMapping("/export") @ApiOperation(value = "excel批量用户导出", notes = "excel批量用户导出") public void exportUsersToExcel(HttpServletResponse response) { userService.exportUsersToExcel(response); }3、定义导入接口/** * excel批量用户导出 */ void exportUsersToExcel(HttpServletResponse response);4、实现导出方法(核心) 如下这个导出实现类就很关键了,我们还是直接使用easypoi提供的exportExcel()方法,详细使用请参考我写的:代码具体设置如下:/** * excel批量用户导出 */ @Overridepublic void exportUsersToExcel(HttpServletResponse response) { try { //从数据库查询到数据 List<UserEntity> users = this.list(); //设置信息头,告诉浏览器内容为excel类型 response.setHeader("content-Type", "application/vnd.ms-excel"); //设置下载名称 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("学生信息表.xls", StandardCharsets.UTF_8.name())); //字节流输出 ServletOutputStream out = response.getOutputStream(); //设置excel参数 ExportParams params = new ExportParams(); //设置sheet名名称 params.setSheetName("学生列表"); //设置标题 params.setTitle("学生信息表"); //转成对应的类型;要不然会报错,虽然也可以导出成功 List<ExportExcelUser> exportUsers = this.changeType(users); //导入excel Workbook workbook = ExcelExportUtil.exportExcel(params, ExportExcelUser.class, exportUsers); //写入 workbook.write(out); } catch (Exception e) { e.printStackTrace(); } } 我由于是使用了mybatis-plus指定了实体类型,所以为了将实体类型与导出vo类一致,我将简单写一个重新赋值给导出vo类即可。代码具体设置如下:/** * 转成导出vo * * @param users */ private List<ExportExcelUser> changeType(List<UserEntity> users) { List<ExportExcelUser> res = new ArrayList<>(); for (UserEntity user : users) { ExportExcelUser exportUser = new ExportExcelUser(user); res.add(exportUser); } return res; } 很好奇,我的图片是怎么进行赋值的。那你看到 ExportExcelUser类了没,我肯定是进行构造体了啊,是吧,要不然写在实体类中,就有点难看了。public ExportExcelUser(UserEntity user) { this.name = user.getName(); this.age = user.getAge(); this.address = user.getAddress(); this.sex = user.getSex(); this.describes = user.getDescribes(); //设置出生时间 this.bornDate = new Date(); //设置入学时间 this.enterSchoolTime = "20220210"; //正常情况是获取数据库中每个用户对象的头像地址 --> this.image = user.Img(); //设置一张图片地址;演示我就地址写死 this.image = "./template/image/刘亦菲.jpg"; }注意:我是直接在项目本地创建了一个template/image文件夹,然后写死了一张图片,所以接下来的剧情就是每个用户数据对应的都是这张图片啦。留意对比一下 bornDate 与 enterSchoolTime 这两个字段,一个传Date()类,一个传String。5、浏览器测试接口 我们打开浏览器,在地址栏,输入我们刚才在Controller暴露出来的接口地址:比如我的:http://localhost:8080/user/export 你按你的接口地址进行访问即可。 输入完直接回车,可以看到有一个文件正在被下载,剩下的就看内容是否都被设置上了,特别是这期的重点,图片的导出。 如上图,基本都达到了要求,sheet命名,文档标题,然后就是对于性别字段,也展示了下拉框筛选,其次就是性别也自动添加了文字后缀;特别的是咱们的头像字段该列,图片都导出成了,这是值得开心的。 还有就是出生时间与入学时间,这两列,虽然数据类型不一样,但是都输出了”yyyy-MM-dd“指定的时间格式,对吧。@Excel(name = "出生年月", format = "yyyy-MM-dd", width = 20.0) private Date bornDate; @Excel(name = "入学时间", databaseFormat = "yyyyMMdd", format = "yyyy-MM-dd", width = 20.0) private String enterSchoolTime; ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,word的导入导出,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板导出多页数据功能集成easypoi实现word模板遍历显示所有数据功能 既然word的导出都实现了多样化,那数据上要是如果携带图片,那该怎么办呢?比如啊,导出所有用户的基本信息,而用户表中,就存有每一个用户的自画像,比如一寸照啥的,像这个情况,那word导出应该得怎么实现呢?还是跟以前一样么?? 接下来我就开始啦,同学们可得竖起耳朵好好听讲哦~我会带着大家一步一步实现它,至于怎么实现,接着往下看。二、引入pom依赖 像你如果要实现word携带图片导出,还是引入前几期的easypoi版本肯定是不能用,比如easypoi3.2.0虽然提供了拓展图片导出,但是bug菌的的确确是尝试过了,没有导出成功,可能是作者在这些版本中,压根就没有加入对该图片解析的代码吧。大家请看: 接着本来我是直接封装了一个对图片进行设置的方法,但凡是一涉及该实体,就被划线处理,应该就是被废弃了。 所以在查询资料发现,easypoi4.3版本及以上却支持了word图片导出,就很完美,这下对于有word导出图片需求的同学来说,这就是福音啊。即我们就以easypoi4.3为例给大家演示如何实现word图片导出,好吧?<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.3.0</version> </dependency>切记!注意依赖版本 4.3.0+才支持多图片循环导出。三、实现word图片导出1、先来定义一个word模板文件 我们分别以单word模板导出多页数据和word模板导出多数据两种模式进行演示,这样大家日后遇到此需求,就不用再查阅相关资料了,直接上手就是开撸(代码)。模式一:模式二: 以上是两种不同的word模板,接下来我们就根据这两份不同的模板依次为大家演示如何导出该画像图片。2、Controller添加word导出方法 我们先来定义一个word文档导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * word文档导出到一个模板中 */ @GetMapping("export-word-all") @ApiOperation(value = "word文档导出到一个模板中", notes = "word文档导出到一个模板中") public void exportUsersToWordAll(HttpServletResponse response) throws Exception { userService.exportUsersToWord(response); } /** * word文档导出 */ @GetMapping("export-word") @ApiOperation(value = "word文档导出", notes = "word文档导出") public void exportUsersToWord(HttpServletResponse response) throws Exception { userService.exportUsersToWord(response); } 不需要任何返回值及参数,你只需要携带你调用接口时的请求HttpServletResponse 即可。3、定义导入接口/** * word文档导出到一个模板中 */ void exportUsersToWordAll(HttpServletResponse response) throws Exception; /** * word文档导出 */ void exportUsersToWord(HttpServletResponse response) throws Exception;4、实现导出方法(核心) 如下这个导出实现类就很关键了,我们还是直接使用easypoi提供的exportWord07()方法,具体细节就看我上两期教学内容啦,这里我们着重演示添加图片:代码具体设置如下://添加简单图片map.put("image", this.imgFormatting("static/image/刘亦菲.jpg", 100, 100)); 我就直接不贴所有代码了啊,毕竟其余代码上几期教学内容中都全部有,还有,我这里只是作为演示,所以图片地址固定死了,如遇实际环境,肯定是直接传入该用户的图片地址即可。附上设置图片封装的方法,仅供参考:/** * 图片格式化,Word导出图片格式 * * @param imgPath 图片路径 */ private ImageEntity imgFormatting(String imgPath, int width, int height) { //设置图片 ImageEntity image = new ImageEntity(imgPath, width, height); //表格外添加简单图片 image.setType(ImageEntity.URL); return image; }5、浏览器测试接口 我们打开浏览器,在地址栏,输入我们刚才在Controller暴露出来的接口地址:比如我的:http://localhost:8080//user/export-word-all 和 http://localhost:8080//user/export-word你按你的接口地址进行访问即可。 我分别截图给大家看一下,两种模式分别展示图片是何种效果:模式一:直接导出单模板多页数据 以上就只截图了一页模板内容啦,反正图片就是一样的,你们的国民女神。模式二:直接导出word模板多内容显示 对比出来了么,这两种导出方式,差异还是有点大,对吧,但是这期内容重点不是这两种导出模式,而是图片在这两种模式下,都展示成功了,还以为要翻车,嘿嘿,内容教你们啦,剩下的就靠大家去摸索咯。 ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入功能Springboot系列(十六):集成easypoi实现Excel导出功能Springboot系列(十六):集成easypoi实现单word模板导出多页面功能Springboot系列(十六):集成easypoi实现word模板多数据导出功能Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel导入功能集成easypoi实现excel导出功能集成easypoi实现单word模板生成多页数据集成easypoi实现word模板内循环导出多数据 对于上一期所言,我们是讲了如何实现word模板的导出生成多页数据,比如下边截图: 对吧,但是除了上边这种多页面导出的情况外,我还接过一种需求就是需要把所有数据在一个模板内全部展示,像这种情况,我们上一期讲解的的就不是那么适用了,所以这一期,我就来讲讲,如何在一个word模板内遍历输出所有的数据,具有很好的学习价值,大家好好听哦。二、引入pom依赖 由于EasyPoi可以很方便的通过一个word模板,然后通过填充模板的方式生成我们想要的word文档。所以我们今天依旧是基于easypoi来实现一个word模板内遍历输出所有的数据。<!--easypoi依赖,excel导入导出--> <dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.2.0</version> </dependency>三、实现word导出1、先来定义一个word模板文件 我还是以用户类进行导出吧,所以我就在word中创建一个表格, 表格输出所有用户的详细信息。所以我指定的word模板文件如下:拓展: 模板里头就包含正常的标签以及fe遍历了,fe遍历了,fe遍历了,fe遍历应该是使用最广的遍历,用来解决遍历后下面还有数据的处理方式我们要生成的是这个需要一些list集合和一些单纯的数据。fe写法:语法:fe标志 冒号 list数据 单个元素数据(默认t,可以省略不写) 第一个元素实例演示如下,以作为参考:{{$fe: userList t t.id }} userList对应于你代码中传入exportWord07(String url, Map<String, Object> map)的map中含有list的那个map键。具体请看如下:2、Controller添加word导出方法 我们先来定义一个word文档导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * word文档导出到一个模板中 */ @GetMapping("export-word-all") @ApiOperation(value = "word文档导出到一个模板中", notes = "word文档导出到一个模板中") public void exportUsersToWordAll(HttpServletResponse response) throws Exception { userService.exportUsersToWord(response); } 不需要任何返回值及参数,你只需要携带你调用接口时的请求HttpServletResponse 即可。3、定义导入接口/** * word文档导出到一个模板中 */ void exportUsersToWordAll(HttpServletResponse response) throws Exception;4、实现导出方法(核心) 如下这个导出实现类就很关键了,我们还是直接使用easypoi提供的exportWord07()方法,该方法就可以将你的数据进行赋值转化到指定的word模板中,但是涉及到内部遍历问题,还是与word导出的赋值方式不太一样。详细使用请参考我写的:代码具体设置如下:/** * word文档导出到一个模板中 */ @Override public void exportUsersToWord(HttpServletResponse response) throws Exception { //准备导出数据 Map<String, Object> mapList = new HashMap<>(); List<Map<String, Object>> listUsers = new ArrayList<>(); //查询所有用户数据 List<UserEntity> users = this.list(); //设置一个原子整型。 AtomicInteger i= new AtomicInteger(1); //循环添加到一个集合中。 users.forEach(user -> { Map<String, Object> map = new HashMap<>(); //生成序号 map.put("id", i.getAndIncrement()); map.put("name", user.getName()); map.put("age", user.getAge()); map.put("sex", user.getSex()); map.put("address", user.getAddress()); map.put("describes", user.getDescribes()); //添加到集合中,一个map就是一行 listUsers.add(map); }); //添加到返回集合中 mapList.put("users", listUsers); //设置班级 mapList.put("class", "六(1)班"); //设置操作人 mapList.put("operator","admin"); //设置当前时间 mapList.put("createTime","2022/02/17"); //导出word并指定word导出模板 XWPFDocument doc = WordExportUtil.exportWord07("./template/用户导出模板_all.docx", mapList); //设置编码格式 response.setCharacterEncoding(StandardCharsets.UTF_8.name()); //设置内容类型 response.setContentType("application/octet-stream"); //设置头及文件命名。 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("学生基本信息表.docx", StandardCharsets.UTF_8.name())); //写入 doc.write(response.getOutputStream());} 如上代码需要的留意的就是两种赋值,一种是map添加基本数据类型,一种是map添加集合。这也就奠定了在你word模板中数据取值方式啦。5、浏览器测试接口 我们打开浏览器,在地址栏,输入我们刚才在Controller暴露出来的接口地址:比如我的:http://localhost:8080//user/export-word-all 你按你的接口地址进行访问即可。 输入完直接回车,可以看到有一个文件正在被下载,剩下的就看内容是否被回填了。 看到上方截图,是不是就觉得比我上一期单模板生成多页的模式好太好了,有木有,然后就是我们设置的单取值与遍历取值,都在word中渲染出来了。 我们也可以对比一下,上一期生成的单模板多页的word模式,看你的需求想实现那种方式了,就使用那种。 两种展示方法上,代码写法上都95%都一致,唯独就是在导出word遍历成一个模板方式上调用exportWord07()多加了一个map进行嵌套传入,还有就是模板写法写法!这需要特别注意,写错了模板可是取不到值呢。 对比一下,就是参数上的改变,从而导致可以进行内部遍历,其实你用单模板也可以跟当前模板写法一致,但是当前模板只适用于字段在每一列上,要是行展示!是不行的,这点大家需要注意。 如下是exportWord07()方法的重载,适配多种数据类型的入参,从而也奠定了我们可以实现内循环数据的基础。exportWord07(String url, Map<String, Object> map) exportWord07(String url, List<Map<String, Object>> list) ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入Springboot系列(十六):集成easypoi实现Excel导出Springboot系列(十六):集成easypoi实现单word模板导出多页面Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 前几期呢,我们主要是讲了如何集成easypoi实现excel的导入导出功能,对吧。不知道你们掌握的如何,如果还对以下任意一篇有疑问,还请大家多多提问,哈哈哈,虽然不是什么大佬,但是我会尽全力相授,一起学习查缺补漏。springboot如何集成easypoi集成easypoi实现excel的导入集成easypoi实现excel的导出集成easypoi实现word模板内循环导出多数据集成easypoi实现word模板图片导出 但是日常,除了导出excel,还很大几率需要接触到word的导入导出、pdf导出等其他格式的文档导出导出业务,比如把数据导出到word文档中且本地下载,最常见的一个应用场景:用户个人档案导出。 场景:就是先定一个word模板,然后将个人数据进行自动填充到模板上,最后导出成word文档,代替人工手填的一个过程。这个需求我以前在开发人事OA系统的时候摸过,还是挺有意思的,所以今天我就打算带着大家从零摸索,实现一个单word模板导出多页数据的功能 ,可以吧? 接下来我就开始讲啦,同学们可得竖起耳朵好好听讲哦~二、引入pom依赖 由于EasyPoi可以很方便的通过一个word模板,然后通过填充模板的方式生成我们想要的word文档。所以我们今天依旧是基于easypoi来实现word文档导出。<!--easypoi依赖,excel导入导出--><dependency> <groupId>cn.afterturn</groupId> <artifactId>easypoi-spring-boot-starter</artifactId> <version>4.2.0</version> </dependency>三、实现word导出1、先定义一个word模板文件 我还是以用户类进行导出吧,所以我就在word中创建一个表格, 表格输出某个用户或者所有用户的详细信息。所以我指定的word模板文件如下:2、确定模板取值命名 你在进行模板自动填值的过程中,就得通过唯一命名来进行赋值了,所以你需要在你word模板中进行取值,而取值就是直接{{name}},其中name就是你所给该位置需要取值的字段名。简言之采用的写法就是{{}}代表表达式,然后根据表达式里面的数据取值。比如如下:3、Controller添加word导出方法 我们先来定义一个word文档导出方法,目的是提供一个口子,好方便自己通过浏览器访问进行测试。/** * word文档导出 */ @GetMapping("export-word") @ApiOperation(value = "word文档导出", notes = "word文档导出") public void exportUsersToWord(HttpServletResponse response) throws Exception { userService.exportUsersToWord(response); } 不需要任何返回值及参数,你只需要携带你调用接口时的请求HttpServletResponse 即可4、定义导入接口/** * word文档导出 */ void exportUsersToWord(HttpServletResponse response) throws Exception;5、实现导出方法(核心) 如下这个导出实现类就很关键了,我们就可以直接使用easypoi提供的exportWord07()方法,该方法就可以将你的数据进行赋值转化到指定的word模板中。详细使用请参考我写的:代码具体设置如下:/** * word文档导出 */ @Override public void exportUsersToWord(HttpServletResponse response) throws Exception { //准备导出数据 List<Map<String, Object>> listUsers = new ArrayList<>(); //查询所有用户数据 List<UserEntity> users = this.list(); users.forEach(user -> { //一个map就对应一个模板 Map<String, Object> map = new HashMap<>(); map.put("name", user.getName()); map.put("age", user.getAge()); map.put("sex", user.getSex()); map.put("address", user.getAddress()); map.put("describes", user.getDescribes()); //添加 listUsers.add(map); }); //导出word并指定word导出模板 XWPFDocument doc = WordExportUtil.exportWord07("./template/用户导出模板.docx", listUsers); //设置编码格式 response.setCharacterEncoding(StandardCharsets.UTF_8.name()); //设置内容类型 response.setContentType("application/octet-stream"); //设置头及文件命名。 response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("学生基本信息表.docx", StandardCharsets.UTF_8.name())); //写入 doc.write(response.getOutputStream()); } 其中,第一点你们需要注意的就是,一般word文档是放于指定服务器位置上的或者放于项目中,我是直接在项目根目录下创建了一个名为:template的文件夹,专门用于存放指定模板类的文件,否则直接引用本地绝对位置文件,换台电脑或者其他同事本地跑就不是很方便。 如下是我项目template 文件夹位置实际截图: 第二点就是,指定下载文件名及指定response的请求类型,意味着告诉浏览器你是在干一件什么类型的事,所以设置response.setContentType("application/octet-stream"). 接下来word导出方法我们就已经写完了,剩下的就是进行对应的测试啦。6、浏览器测试接口 我们打开浏览器,在地址栏,输入我们刚才在Controller暴露出来的接口地址:比如我的:http://localhost:8080//user/export-word 你按你的接口地址进行访问即可。 输入完直接回车,可以看到有一个文件正在被下载,剩下的就看内容是否被回填了。 如上图所示,看到数据填写的正是数据库的一条数据,这就说明导出word模板方法是没有问题的。 下载完打开一看,很nice所有的用户信息都获取到了,一个map对应的就是一个word模板文档,总共查询到了9个用户,但是这里总共分了10页,最后一页是空白页,啥内容也没有,不知道究竟是什么?如果有知道的小伙伴,欢迎交流啊,互相学习~ 但是一般情况下我们是不会全部都进行word写出的,一般都是携带用户唯一id查询数据然后再进行导出,这样用户信息就只有一条!这就看需求啦,有的可能是导出所有内容,就看针对的用户需求是什么,不过这肯定很好调整啊,也可以更贴近用户想法,导出多少条,由用户自己来控制就好了,有的是按页导出,有的是按条导出等。 当然,还有就是对于一个模板导出所有数据的实现功能,我将在下期进行讲解。 ... ... 好啦,以上就是我这期的全部内容啦,如果还想学习更多,你可以看看我的往期热文推荐哦,每天积累一个奇淫小知识,日积月累下去,你一定能成为令人敬仰的大佬的。好啦,咱们下期见~四、往期热门推荐Springboot系列(十六):集成easypoi实现Excel的导入导出(准备篇)Springboot系列(十六):集成easypoi实现Excel导入功能Springboot系列(十六):集成easypoi实现Excel导出功能Springboot系列(十六):集成easypoi实现word模板内循环导出多数据Springboot系列(十六):集成easypoi实现word模板图片导出Springboot系列(十五):AOP实现自定义注解记录业务日志!你玩过吗?Springboot系列(十四):redis零基础教学,你值得拥有!Springboot系列(十三):如何项目中集成swagger在线接口文档,你会吗?Springboot系列(十二):如何代码实现发送邮件提醒,你写过吗?... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一:前言 今天临时接到一个需求,要求bug菌实现一个定时发送邮件的功能,并要求一天搞定。接收到这个需求的第一反应,好家伙,这不是很简单,告诉我发件人收件人邮箱地址等相关信息,我一刻钟就给它搞定,小伙伴们,你们有写过类似发送邮件业务么?如果没有,那可以看看bug菌是怎么实现的吧。好了,咱们就开始这期的内容吧。二、准备工作 先给大家透露下,我会把这个功能点分多篇进行零一教学,目的是为了不造成篇幅太长从而导致小伙伴们视觉疲劳,没有欲望阅读完。所以还请部分小伙伴多担待哈~~~就以qq邮箱为例给大家做演示吧。1、开通POP3/SMTP服务1、如何开通你发送邮箱的POP3/SMTP服务呢?我会教大家的,咱们往下看。至于为什么开通,我待会给大家演示一下,如果不开通,会出现什么问题?先教大家如何开通,如下是步骤:a->b->c->d->ea、网页登录qq邮箱,首页点击邮箱设置,然后点击面包屑-账户b、往下滑,找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 ;然后点击POP3 最右边的开启。c、点击开启会弹出一个框,让你进行短信验证开通。如下:d、首先提示框先不关,放着,然后你到你的手机端进行短信发送,具体发送内容及发送人我下边已给,大家复制粘贴即可。发送完后,再点击弹框的【我已发送】。短信内容为:‘配置邮件客户端’发送至号码:‘1069070069’e、短信发送成功后,点击完[我已发送],邮箱界面会再次弹出一个窗口,具体展示如下。弹框表示成功开启POP3服务。切记,这串授权码务必拷贝下来,随便找个文档保存好,后边必须要用到它,要不然你还得再申请一个授权码,就会很麻烦啦。 这个授权码就是我们用来第三方客户端登录的密码,也就是你进行代码发送邮件的关键一环。2、引入spring-boot-starter-mail 依赖。 由于Spring推出了关于Mail的JavaMailSender类,基于该类Spring Boot又对其进行了进一步封装,从而实现了轻松发送邮件的集成。而且JavaMailSender类提供了强大的邮件发送能力,支持各种类型的邮件发送。那我们就直接使用Spring Boot提供对mail-starter进行操作吧,后续再扒源码进行深入研究。 即我们在pom文件中引入该starter-mail依赖即可。<!--邮件通知--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency>3、application-dev.yaml文件配置 如下是在系统配置文件中配置,如果你使用的是.properties格式的配置文件,那么你就将如下的配置格式改成对应格式即可,比如语法spring.mail.hots = smtp.qq.com 。.yaml改成.properties修改方式为:冒号改成[ . ] 然后值用[ = ] 即可。 然后是跟我用一样配置文件格式的,你们就直接复制过去,注意两点。第一,你们active所本地运行的环境是哪个,就把这段配合加到那个环境上面,我是配在dev环境上。第二,也就是提醒你们要保存下来的那段授权码,对应下边的password。具体配置如下:仅供参考。spring: mail: host: smtp.qq.com # 配置 smtp 服务器地址 port: 25 #smtp 服务器的端口 username: xxxx@qq.com #配置邮箱用户名;你自己的邮箱 password: iptxxxxxkbffjbdhg #配置申请到的授权码;这里填写刚才短信申请到的授权码 default-encoding: UTF-8 #配置邮件编码 protocol: smtp #协议 按顺序完成如上3个步骤,发送邮件的环境配置就弄完了,磨刀不误砍柴工嘛,接下来我再来回答大家的疑问吧。三、不开通POP3/SMTP服务会如何? 我不开通,直接配置完,然后直接写了个test测试类进行邮件发送,报错啦,不是吧?哦吼,原来如此,再执行一遍,真报错啦啊?然后针对报错进行研究。配置文件的password 我一开始就是填的是邮箱号密码,结果就报错,然后根据报错,才反应,qq邮箱指引我要开启该服务。不过,你们也可以试试,这样印象会加深一点。如下是报错源码:org.springframework.mail.MailAuthenticationException: Authentication failed; nested exception is javax.mail.AuthenticationFailedException: 535 Login Fail. Please enter your authorization code to login. More information in http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=28&&no=1001256 麻烦自己解读一下,哈哈哈,这下就明白了吧,报错肯定是不予推荐,这也是qq邮箱账户的保护机制啦,要不然被窃取到账户密码,随便就能拿来做发送源,你们想想,是不是这样?... ... OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。四、附录-邮件属性 如下是Spring Boot通用配置邮件属性,供大家参考一下。 以上是spring配置提供对mail的一些配置属性,具体配置的就跟我上边给的那几个主要的属性比较常见,基本就是针对邮件发送这一块业务的话,基本那几个配置属性就够了,其余的,大家也可以自行摸索,因为我也不懂呀🤓,哈哈哈,菜的很真实。五、往期热门推荐Springboot系列(十二):如何代码实现简单邮件发送(上篇)Springboot系列(十二):如何代码实现图片等附件邮件发送(中篇)Springboot系列(十二):如何代码实现静态模板邮件发送(下篇)Springboot系列(十二):如何代码实现邮件发送之大复盘(总结篇)... ... 如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 正常情况下我们在开发系统的时候都是使用一个数据源,但是由于有些项目同步数据的时候不想造成数据库io消耗压力过大,便会一个项目对应多个数据源,即就会有个问题,Springboot 配置db都是默认加载数据源连接,连接池默认配置,但是配置多个数据库url,这该怎么实现呢? 不用担心,我们就是为了解决而写的,不用复杂的实现方式,什么Springboot+mybatis在配置文件中配置多个数据源,然后mapper指定连接配置等,不,不需要,我嫌太麻烦了,今天我就要给你们安利它: **dynamic-datasource-spring-boot-starter,一个基于springboot的快速集成多数据源的启动器,开箱即用,超级方便。**接下来我就为大家一一讲解,虽然配置很简单,但是坑也比较多,bug菌都为大家给淌过啦,你们就直接直接拿去用即可!二、dynamic-datasource-spring-boot-starter一、简介dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。二、特性支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。支持数据库敏感配置信息 加密 ENC()。支持每个数据库独立初始化表结构schema和数据库database。支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。支持 自定义注解 ,需继承DS(3.2.0+)。提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。提供 自定义数据源来源 方案(如全从数据库加载)。提供项目启动后 动态增加移除数据源 方案。提供Mybatis环境下的 纯读写分离 方案。提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。提供 **基于seata的分布式事务方案。提供 本地多数据源事务方案。三、使用方法1、老规矩,先引依赖包。引入dynamic-datasource-spring-boot-starter;<!--配置多数据源--> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency>2、配置数据源。spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源 datasource: master: driver-class-name: com.mysql.cj.jdbc.Driver #3.2.0开始支持SPI可省略此配置 url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 slave_1: driver-class-name: com.mysql.cj.jdbc.Driver #3.2.0开始支持SPI可省略此配置 url: jdbc:mysql://127.0.0.1:3306/test_db2?setUnicode=true&characterEncoding=utf8 username: root password: 123456拓展:同时也支持多主多从,多种不同库及混合配置。3、实战演示:我们先来指定配置环境:配置好配置环境后,先启动下项目,查看库是否都正常连接。如下控制台启动打印的日志,表示两库都正常连接。接着,我们先创建两个实体,分别对应db1和db2的user表:接着写一个controller类;接着接口实现层:这里我就为大家讲解一下了。数据源切换,使用的是提供的 @DS 注解。其中@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。(所以你们看上边,注解在类上使用的是主库,但是基于下边某个方法就是制定了从库,但是我这样放一起是为了给大家看比较,一般会直接分类写,不会一个类上出现多个数据源。)注:@DS("dsName") dsName可以为组名也可以为具体某个库的名称。4、访问接口查看结果。先是访问接口1:localhost:8888/dbUser/query-users-for-db-one如下请求结果,大家请看,成功拿到了db1库中的数据。再请求下接口2:localhost:8888/dbUser/query-users-for-db-two如下请求结果,大家请看,成功拿到了db2库中的数据。再做个测试,我们把db1与db2中的数据同时返回,看看能否成功:我们先写两接口,然后分别将数据用map返回:请求结果如下:然后给大家看下数据库数据,以免被大家说是同一个库中的数据。好啦,以上就是同mysql数据源的配置流程及实例演示啦,如果还有啥不清楚的小伙伴,欢迎下方留言。接着就是针对后边两种配置方式,就自行尝试啦,看上去都是一样的。有需求的小伙伴,看完之后,使用起来是不是很简单,压根不需要用传统的mybatis配置多个连接器,mybatis-plus都帮我们封装好啦,开箱即用。... ...OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。五、往期推荐========================================================Springboot<一>:零基础入门之springboot及idea搭建Springboot<二>:零基础入门之yaml、properties配置文件介绍及实战使用Springboot<三>:多环境切换,profile实例演示Springboot<四>:stater入门教学Springboot<五>:springboot之常用注解大全Springboot<六>:mysql配置及数据库查询,实现增删改查Springboot<七>:mybatis-plus入门及实战使用实现增删改查Springboot<八>:mybatis-plus条件构造器使用手册Springboot<九>:mybatis-plus之自定义sql零基础教学Springboot<十>:mybatis之xml映射文件>、<=等特殊符号写法... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 mybatis-plus的基本使用,前两期基本讲的差不多,够日常使用,但是有的小伙伴可能就会抱怨了,若是遇到业务逻辑比较复杂的sql,都使用swagger 进行拆分组装?mybatis-plus动态拼接sql满足单表查询,若是遇到多表关联且条件复杂涉及分组就不是那么的灵活,那有办法满足该种特殊需求么?不好意思,还真有,mybatis-plus也早就预料到了会存在该种需求,便对该特殊有了特殊处理,继续沿用了他的兄弟mybatis自定义sql的功能,没想到吧!二、如何自定义SQL 接下来我将为大家介绍两种解决方案。一是直接使用mybatis的注解@Select。二是创建.xml文件的形式。1、使用注解 @Select() 还记得我们创建一整套控制器的时候,有一个文件夹命名为dao,它今天就是为了干这件事的。首先我们现在dao层先创建一个UserMapper,然后定义几个实例接口给大家看看,让大家熟悉怎么接口上加自定义SQL。传参类型为String,Long,Integer等,传参直接使用。使用@Param("xxx")设置参数 即可参数就是使用@Param设置的value值 即可。 代码演示:@Select("select * from user where id = #{userId}") UserEntity getUserById(@Param("userId") String userId);使用postman请求一下:localhost:8080/user/getUser-by-id?userId=1;查看控制台打印SQL:ok!参数设置成功,查询结果一条。传参类型为.class类,比如XxxModel,XxxEntity等。获取参数就是直接通过你指定的Param的value对象,对象点属性,这样,比如如下指定的是model那要获取model的sex值,那就是model.sex 即可。代码演示:@Select("select * from user u left join grade g on u.id = g.student_id where u.sex = #{model.sex} and g.name = #{model.className}") List<UserEntity> getUsers(@Param("model")QueryUserInfoModel model);使用postman请求一下,设置好参数。请看返回结果:传参类型为集合类,比如List等。像这种集合形式,那得就通过xml文件的形式配置啦。2、使用xxx.xml文件 先新建一个UserMapper.xml 然后指定 mapper namespace 即可。传参类型为String,Long,Integer等,传参直接使用。持久层UserMapper.javaUserEntity getUserById(@Param("userId") String userId);UserMapper.xml<!--根据userId查询--> <select id="getUserById" resultType="com.example.demo.Entity.UserEntity"> select * from user where id = #{userId} </select>post测试,结果显而易见。单参数传递,参数名直接用 #{ param } 就可以获取到。若传参类型为class类等,比如QueryUsersModel、UserEntity等。UserMapper.java 配置如下:List<UserInfoVo> getUsers(@Param("model")QueryUserInfoModel model);UserMapper.xml 配置如下:跟你sql语句没多大区别,唯独就是参数获取方式,这个大家得注意一下。<!--根据性别和班级名称查询--> <select id="getUsers" resultMap="BaseResultMap"> select u.name as name,g.name as className from user u left join grade g on u.id = g.student_id where u.sex = #{model.sex} and g.name = #{model.className} </select>postman接口测试一下,给定参数。然后Send,返回结果如下。传参类型为集合,比如ArrayLis userIds,String [] ids等。UserMapper.java//userIds 用户id集合List<UserInfoVo> getUsersByIds(@Param("userIds")List<Integer> userIds) ;UserMapper.xml <!--根据用户ids遍历查询--> <select id="getUsersByIds" resultMap="BaseResultMap"> select u.name as name , g.name as className from user u left join grade g on u.id = g.student_id where 1=1 <if test="userIds.size() !=0"> and u.id in <foreach collection="userIds" item="userId" open="(" separator="," close=")" > #{userId} </foreach> </if> </select>postman测试一下,看看是否查询出指定id("userIds":[1,2,3])所对应的用户信息;结果也是直接返回。证明接收数组也是没有任何问题。接着不知道你们有没有 注意到,我在sql上多拼接了这一句:" where 1=1 ",有哪位小伙伴知道这是为何多此一举么?欢迎评论区告诉bug菌。提示大家一部分,我userIds传了个空进来,查询出了所有数据结果,结果是正常的。三、拓展:1、.xml 常用参数说明一句话总结来说就是:resultType用于返回已经定义好的domain,pojo或者jdk定义的基本数据类型,返回的属性需要和domain的属性是一样的,否则是绑定不上的; 而 resultMap是指向一个外部的引用resultMap,当表名和列表不一致时使用resultMap做个映射,但在处理返回结果是基本类型的时候是无能为力的;比如我代码里用到的BaseResultMap,其实就是表字段名与对象中属性名做的映射,column属性指定的是sql返回集对于的字段名,而property指定的是你pojo中的属性字段名称。2、.xml foreach语法解读如下边这段<foreach collection="userIds" item="userId" open="(" separator="," close=")" > #{userId} </foreach>解读:其实很好理解。foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有 item,index,collection,open,separator,close。其中item表示集合中每一个元素进行迭代时的别名, index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置, open表示该语句以什么开始, separator表示在每次进行迭代之间以什么符号作为分隔符, close表示以什么结束。 collection属性,是必须指定的。可以是list,array数组、map等。 注意:如果传入的是单参数且参数类型是一个List的时候,collection属性值为list #{item} 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array。<foreach collection="array" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach>如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可。<select id="selectFor" parameterType="java.util.HashMap" resultType="Blog"> select * from tb_log where title like "%"#{title}"%" and id in <foreach collection="ids" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select>四、附上部分完整源码:如下我就把持久层跟xml配置给小伙伴完成展示一下,其余用到的model 、vo等就按照自己的习惯啦,真正需要的也可以评论区告诉我,我会为大家一一解答的。UserMapper.java@Component public interface UserMapper extends BaseMapper<UserEntity> { UserEntity getUserById(@Param("userId") String userId); List<UserInfoVo> getUsers(@Param("model")QueryUserInfoModel model); List<UserInfoVo> getUsersByIds(@Param("userIds")List<Integer> userIds) ; }UserMapper.xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.demo.dao.UserMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.example.demo.Vo.UserInfoVo"> <result column="name" property="name" /> <result column="className" property="className" /> </resultMap> <!--根据userId查询--> <select id="getUserById" resultType="com.example.demo.Entity.UserEntity"> select * from user where id = #{userId} </select> <!--根据性别和班级名称查询--> <select id="getUsers" resultMap="BaseResultMap"> select u.name as name,g.name as className from user u left join grade g on u.id = g.student_id where u.sex = #{model.sex} and g.name = #{model.className} </select> <!--根据用户ids遍历查询--> <select id="getUsersByIds" resultMap="BaseResultMap"> select u.name as name , g.name as className from user u left join grade g on u.id = g.student_id where 1=1 <if test="userIds.size() !=0"> and u.id in <foreach collection="userIds" item="userId" open="(" separator="," close=")" > #{userId} </foreach> </if> </select> </mapper>/** * 用户基本信息实体 */ @TableName("user") @Data public class UserEntity implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) //表示该id为自增,新增时候不需要手动设置id。 private Integer id; @TableField(value = "name") private String name; @TableField(value = "age") private Integer age; @TableField(value = "sex") private String sex; @TableField(value = "address") private String address; @TableField(value = "describes") private String describes; @Override public String toString() { return this.id + " - " + this.name+ " - " +this.getSex(); } }/** * 班级信息实体 */ @TableName("grade") @Datapublic class GradeEntity { @TableId(value = "id", type = IdType.AUTO) //表示该id为自增,新增时候不需要手动设置id。 private Integer id; @TableField(value = "name") private String name; @TableField(value = "student_id") private Integer studentId; @TableField(value = "create_time") private Date createTime; @TableField(value = "update_time") private Date updateTime; }/** * 返回用户班级信息 */ public class UserInfoVo { private String name; private String className; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } }接口分发层就自己琢磨啦。其实看bug菌postman调用url及请求方式,一看就都明白啦!有些还是得多靠小伙伴们自己从细节摸索,这样比我百分百教大家要理解的更为深刻。... ...OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。五、往期推荐========================================================Springboot<一>:零基础入门之springboot及idea搭建Springboot<二>:零基础入门之yaml、properties配置文件介绍及实战使用Springboot<三>:多环境切换,profile实例演示Springboot<四>:stater入门教学Springboot<五>:springboot之常用注解大全Springboot<六>:mysql配置及数据库查询,实现增删改查Springboot<七>:mybatis-plus入门及实战使用实现增删改查Springboot<八>:mybatis-plus条件构造器使用手册Springboot<九>:mybatis-plus之自定义sql零基础教学Springboot<十>:mybatis之xml映射文件>、<=等特殊符号写法... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 通过前几期的基础教学,想必大家都已经掌握springboot项目的创建及项目启动等基本内容了吧(如果还没掌握,请去复习我上几期的内容,好伐?)。 今个儿我要来整点高级的,使用组件的方式来轻松实现数据库交互,大家想不想学?好的,看到大家的积极性如日中天啊,老夫深感欣慰。那bug菌将不余余力的传授给大家,同时也希望大家在学习的过程中,打好基础,认真听哦。 上一期我们是通过注入jdbc模板类进行数据库查询对吧,虽然这种交互方式可以,但是面对不常用数据类型,就不好使了,所以我要教大家一种最省事最方便的,那就是mybatis-plus,超级好用的一个组件,省去大量手写sql的体力活,让你一切查询变得简单而又透彻。这期我的就目的就是引领你们入门。 具有很好的教学价值,希望小伙伴们根据这篇文章可以有所收获,建议小伙伴们先收藏后阅读哦。首先,我们先来了解一下,mybatis-plus为何物?与mybatis有何不同?最关键也是最重要的部分,它如何使用?好的,别着急,咱们接着往下看。二、mybatis-plus概览MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。愿景:我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。三、mybatis-plus优势:特性无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作支持数据库任何能使用 mybatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库#框架结构#代码托管github | Giteeps:以上来自mybatis-plus官网介绍四、mybatis-plus实战演练1、老规矩,使用前先引入pom依赖;添加如下:<!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> <!--代码自动生成器配置--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.2.0</version> </dependency>ps:如果有小伙伴图省事,可以使用代码生成器,超级方便,但由于此篇是入门级教学,那我就手把手带着大家手动创建控制器,分发器等文件吧,日后再带大家熟悉使用自动生成器,行吧?感兴趣的小伙伴也可以参考bug菌写的这篇《mybatis-plus代码生成器入门教学》,自己动手实战一下,很简单的。2、引入完依赖之后,结合阿里开发规范,对业务逻辑进行分层,本着低耦合高内聚目的而去。3、由于我前几期已经创建了一个 UserController 对吧,接着来创建实体类,UserService接口层,UserServiceImpl接口实现层,及最后的UserMapper 持久层。UserEntity**@TableName(“table_name”):**指定数据库表名。**@TableId :**指定type = IdType.AUTO 表示数据库id为自增型。**@TableField(value = "name") :**指定数据库字段名。添加get set方法即可。package com.example.demo.Entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName;/**用户基本信息实体*/ @TableName("user") public class UserEntity {@TableId(value = "id", type = IdType.AUTO) //表示该id为自增,新增时候不需要手动设置id。 private Integer id; @TableField(value = "name") private String name; @TableField(value = "age") private Integer age; @TableField(value = "sex") private String sex; @TableField(value = "address") private String address; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } }**然后再多添加几条数据吧!方便后续查询接口浏览查询。**UserService编写两个接口!分别为不带参数与带一个参数,作为演示:具体代码如下package com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.Entity.UserEntity; import java.util.List; /** * 用户管理业务层接口 */ public interface UserService extends IService<UserEntity> { /** * 不分页查询所有用户信息 */ List<UserEntity> getUsers(); /** * 根据性别查询所有用户 * * @param sex 性别 */ List<UserEntity> getUsersBySex(String sex); }UserServiceImpl继承接口实现上方两方法。由于是使用了该mybatis-plus的查询方式,顾语法有些不一样。这个后续给大家详细讲讲。package com.example.demo.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.Entity.UserEntity; import com.example.demo.dao.UserMapper; import com.example.demo.service.UserService; import org.springframework.stereotype.Service; import java.util.List; /** * 用户管理业务层 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService { /** * 不分页查询所有用户信息 */ @Override public List<UserEntity> getUsers() { //直接可使用IService 封装好的一些方法,这个自行点进去看。 return this.list(); } /** * 根据性别查询所有用户 * * @param sex 性别 */ @Override public List<UserEntity> getUsersBySex(String sex) { //条件构造器 QueryWrapper<UserEntity> wrapper = new QueryWrapper<>(); //eq 代表“ = ”;例如 eq("sex", "男") ---> sex = '男';等同于拼接在sql语句后边的where条件。 wrapper.eq("sex",sex); //将条件带入返回 List<UserEntity> list = this.list(wrapper); //返回数据 return list; } }UserMapperpackage com.example.demo.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.Entity.UserEntity; import org.springframework.stereotype.Component; /** * 用户管理持久层 */ @Component public interface UserMapper extends BaseMapper<UserEntity> { }以上 添加就大功告成了;最后是controller类,调用刚才我们写的那两接口进行访问,看看结果如何分发器代码如下:package com.example.demo.controller; import com.example.demo.Entity.UserEntity; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * 用户管理分发器 */ @RestController@RequestMapping("/user") public class UserController { @Autowired private UserService userService; /** * 不分页查询所有用户信息 */ @GetMapping("/get-users") public List<UserEntity> getUserList() { return userService.getUsers(); } /** * 根据性别查询所有用户 * * @param sex 性别 */ @GetMapping("/get-users-by-sex") public List<UserEntity> getUsersBySex(@RequestParam(name = "sex") String sex) { return userService.getUsersBySex(sex); } }接下来,不用我说大家也知道了吧!接口访问,一切从简,那就直接打开浏览器访问咯奥对了在访问之前,咱们再做一件事,添加一下控制台sql打印输出,方便我们查看到底执行了什么?对吧。做法只需在application-dev.yaml 中配置如下即可。# 日志设置 logging: level: # 持久层日志级别 com.example.demo.dao: debugok~咱们先来访问第一个接口;浏览器直接访问。http://localhost:8080/user/get-usersps:如果中途报错,找不到basemapper那你请在启动类 DemoApplication 中添加如下这行,表示指定扫描 dao。@MapperScan("com.example.demo.dao")完事之后,接着干正事,看看能否查询到五条数据?咱们拭目以待~很好。成功查询出了五条数据然后我们再看下控制台,看看执行了什么sql语句?请看如下:第一个查询语句,成功输出,也打印并返回五条数据值。接下来我们来访问第二个接口,带性格参数。看看能否成功?http://localhost:8080/user/get-users-by-sex?sex=男结果如下:从结果上看,明显是进行了条件查询,只查询出了性别为男的数据!证明带参也是没问题。我们来确认一下,回到idea控制台,请看下边,朋友们,sql语句已经告诉了我们一切,很显然,是把sex = '男' 作为了where 条件 进行了数据过滤!把参数换成 '女' 试试,能否数据过滤结果如下:也是根据性别参数成功过筛数据。好啦!接下来你们就随便玩随便造啦。想怎么玩就怎么玩,反正就是引领入门,修行靠个人啦~... ...OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。五、热文推荐========================================================Springboot<一>:零基础入门之springboot及idea搭建Springboot<二>:零基础入门之yaml、properties配置文件介绍及实战使用Springboot<三>:多环境切换,profile实例演示Springboot<四>:stater入门教学Springboot<五>:springboot之常用注解大全Springboot<六>:mysql配置及数据库查询,实现增删改查Springboot<七>:mybatis-plus入门及实战使用实现增删改查Springboot<八>:mybatis-plus条件构造器使用手册Springboot<九>:mybatis-plus之自定义sql零基础教学Springboot<十>:mybatis之xml映射文件>、<=等特殊符号写法... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 通过前几期的基础教学,想必大家都已经springboot项目创建及启动等基本内容了吧,今个儿我就来整点高级的,跟数据库交互交互。如何连接mysql数据库,创建数据库表,最后再成功查询数据库并打印数据内容?是这期要讲的内容,可能会比较简单,同时也希望大家不要掉以轻心,打好基础,认真听哦。二、添加mysql依赖我们先在pom.xml文件中引入mysql数据库依赖;添加如下<!--mysql依赖--><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--引入jdbc stater--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> 三、创建数据库表我们使用 navicat 数据库管理工具,先连接本地数据库,输入连接名,主机ip,端口(3306),用户名,密码是你当初安装mysql服务时填写的。完事之后,如果不会操作的,请参考bug菌写的这篇 navicat如何创建数据库及导入数据库文件1、先创建个数据库,取名这个大家随意啊,命名最好见名之意。2、选择刚才创建好的数据库下层的表,然后右键打击选择'新建表'3、随便创建几个字段,然后点击保存。附上创建表sql:CREATE TABLE `user` ( `id` int(11) NOT NULL COMMENT '主键id', `name` varchar(255) NOT NULL COMMENT '用户名', `age` int(11) DEFAULT NULL COMMENT '年龄', `sex` varchar(2) DEFAULT NULL COMMENT '性别', `address` varchar(255) DEFAULT NULL COMMENT '住址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;4、先随便造一条数据吧,待会儿能用得上。ok!以上跟着bug菌一步一步来哈,不要急,更不要遗漏任何一步骤,接下来我们就是配置连接数据库信息。四、yaml文件配置数据库连接我就以yaml文件为例,.properties文件也是一样的配置。注意,这个时候,前面一期有教大家为何要使用profile文件动态切换环境此刻就用上了,我们先指定开发环境,即:先在application.yaml 中指定 active = dev;spring: profiles: # 控制使用哪套环境变量 active: dev配置数据库连接信息如下:以application-dev.yaml为例,.properties也是一样的。这里就不着重展开讲啦。#开发环境数据库信息 spring: datasource: url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 #数据库名、用户名和密码改为自己的 driver-class-name: com.mysql.cj.jdbc.Driver这样就配置好了,启动项目,没有报错!但是就一定能保证项目与数据库之间连通是可以的吗?肯定不是,毕竟你只是配置了连接信息。所以接下来,我们再写一个controller 进行数据库连接。一探究竟!我们先来创建个 “UserController”类,然后注入JdbcTemplate jdbc模板类,具体请看如下:目的就是访问数据库并返回用户数据信息。package com.example.demo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List;import java.util.Map; @RestController @RequestMapping("/user") public class UserController { //注入JDBC模板接口 @Autowired private JdbcTemplate jdbcTemplate; /** * 查询所有用户信息 */ @GetMapping("/get-users") public List<Map<String, Object>> getUserList(){ //查询sql语句 String sql = "select * from user"; List<Map<String, Object>> list = jdbcTemplate.queryForList(sql); //返回结果 return list; } }写完之后重启项目,没有报错,ok!接着打开浏览器访问该get请求。看看能否成功返回查询数据?输入访问地址,如下:http://localhost:8080/user/get-users输完直接回车,ok!成功将我们刚才插入数据库的那条数据查出了并打印在页面上。证明数据库连接是没有问题的。接下来,大家可以自由尝试,比如通过接口调用的形式插入一条数据,通过主键id修改一条数据,删除一条数据等等,此处就不一一举例啦。学习过程中,产生任何疑问皆可评论区留言,bug菌一定第一时间给予大家帮助解答困惑。... ...OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。五、往期推荐:========================================================Springboot<一>:零基础入门之springboot及idea搭建Springboot<二>:零基础入门之yaml、properties配置文件介绍及实战使用Springboot<三>:多环境切换,profile实例演示Springboot<四>:stater入门教学Springboot<五>:springboot之常用注解大全Springboot<六>:mysql配置及数据库查询,实现增删改查Springboot<七>:mybatis-plus入门及实战使用实现增删改查Springboot<八>:mybatis-plus条件构造器使用手册Springboot<九>:mybatis-plus之自定义sql零基础教学Springboot<十>:mybatis之xml映射文件>、<=等特殊符号写法... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一、前言 学习了前三节内容,接下我要给大家介绍一个东西'**stater',**很多小伙伴就会好奇了,之前是不是引入过一个叫“spring-boot-starter-web”的依赖,是不是同一个东西啊?待会儿给你们解答,你们先往下看。 首先我们是知道传统的 Spring 项目想要运行,不仅需要导入各种依赖,还要对各种 XML 配置文件进行配置,十分繁琐,但 Spring Boot 项目在创建完成后,即使不编写任何代码,不进行任何配置也能够直接运行,这都要归功于 Spring Boot 的 starter 机制。所以本章节就给大家讲讲它。二、什么是stater? stater其实你就可以理解成一个「连接包」,它首先是一个包,一个集合,它把需要用的其他功能组件囊括进来,放到自己的 pom 文件中。然后它是一个连接,把它引入的组件和我们的项目做一个连接,并且在中间帮我们省去复杂的配置,力图做到使用最简单。你可以一站式的获取你所需要的spring和相关技术,而不需要依赖描述符的通过示例代码搜索和复制黏贴的负载,比如如果你想使用sping和JPA访问数据库,你只需要在项目里引入spring-boot-starter-data-jpa依赖项你就可以完美进行。实际上 starter 并不会包含多少功能代码。 常用的stater官方都帮我们封装好了,开箱即用,只需要引入相关stater即可。但是并不是所有的 starter 都是由 Spring Boot 官方提供的,也有部分 starter 是第三方技术厂商提供的,例如 druid-spring-boot-starter 和 mybatis-spring-boot-starter 等等。当然也存在个别第三方技术,Spring Boot 官方没提供 starter,第三方技术厂商也没有提供 starter。此刻来解答小伙伴上边的疑惑啊;就以stater(spring-boot-starter-web)为例,见名之意嘛,它是可以提供 Web 开发场景几乎所有所需要的依赖,因此在使用 Spring Boot 开发 Web 项目时,只需要引入该 Starter 即可,而不再需要额外的导入 Web 服务器和其他的 Web 依赖等别的依赖。这下你们就知道了stater使用起来是多么的省事方便了吧。下边再演示一下,如何在项目中引stater吧。1、在项目根目录下有个 pom.xml ,在里头加入如下,并只引入 spring-boot-starter-web,示例代码如下。<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!--SpringBoot父项目依赖管理--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--导入 spring-boot-starter-web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project>导入依赖后,你也可以查看下该项目的依赖树,执行以下 mvn 命令查看器依赖树。mvn dependency:tree由于执行结果过长,时间有限,这里就给大家分析一下结果表示啥意思。在执行完后,你们就可以看到 Spring Boot 导入了很多常用的依赖,比如: springframework、logging、jackson 以及 Tomcat 等,而这些正是我们在开发 Web 项目时所需要的。这就是你引入一个stater而它都帮你做了。是不是很方便呢~ 大家有没有注意到一个问题,即在以上 pom.xml 的配置中,引入依赖 spring-boot-starter-web 时,其实并没有指明其版本(version),但在依赖树中,我们却看到所有的依赖都带有版本信息,那么这些版本信息是在哪里控制的呢?你们想过没有?那我来告诉大家,其实,这些版本信息是由 spring-boot-starter-parent(版本仲裁中心) 统一控制的。我们在pom.xml 一开始就明确了父版本,这也就不奇怪了。接下来我就为大家介绍一下spring-boot-starter-parent吧~这样你们就全能理解了。三、spring-boot-starter-parent是什么?spring-boot-starter-parent ,它就是所有 Spring Boot 项目的父级依赖,被称为 Spring Boot 的版本仲裁中心,对项目内的部分常用依赖进行统一管理。如下就是指定父版本型号。<!--SpringBoot父项目依赖管理--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> </parent>中途我们也可以查看 spring-boot-starter- parent 的底层代码,其实也可以看到它其有一个父级依赖 spring-boot-dependencies。<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.4.5</version> </parent>所以当你明确父版本,其实你在引入一个stater如果不指定版本,其实就会将该父版本默认指定的那些依赖按版本帮你下载进来,这样就省去人工指定版本而烦恼了,设置还有的依赖版本会冲突等问题,这些springboot 的stater都帮你解决过了的。四、有哪些常用的stater?以下是我在Springboot源码中截的一部分部分starter,若想看全部,请参考springboot源码:传送门,自己可以去看,按版本分类。而我们常用的就有如下这些,仅供参考:spring-boot-starterSpring Boot的核心启动器,包含了自动配置、日志和YAML。spring-boot-starter-aop支持面向方面的编程即AOP,包括spring-aop和AspectJ。spring-boot-starter-data-elasticsearch支持ElasticSearch搜索和分析引擎,包括spring-data-elasticsearch。spring-boot-starter-data-jpa支持JPA(Java Persistence API. ,包括spring-data-jpa、spring-orm、Hibernate。spring-boot-starter-data-mongodb支持MongoDB数据,包括spring-data-mongodb。spring-boot-starter-jdbc支持JDBC数据库。spring-boot-starter-redis支持Redis键值存储数据库,包括spring-redis。spring-boot-starter-test支持常规的测试依赖,包括JUnit、Hamcrest、Mockito以及spring-test模块。spring-boot-starter-thymeleaf支持Thymeleaf模板引擎,包括与Spring的集成。spring-boot-starter-webS支持全栈式Web开发,包括Tomcat和spring-webmvc。spring-boot-starter-websocket支持WebSocket开发。spring-boot-starter-log4j支持Log4J日志框架。spring-boot-starter-logging引入了Spring Boot默认的日志框架Logback。spring-boot-starter-tomcat引入了Spring Boot默认的HTTP引擎Tomcat。... ...以上都是我开发这么多年以来比较常见的stater,虽然不是很全,但是bug菌是按印象总结了一部分,反正就是那么个意思啦,哈哈哈哈。同时也欢迎评论区补充啦~~~OK,以上就是这期所有的内容啦,如果有任何问题欢迎评论区批评指正,咱们下期见。五、热文推荐========================================================Springboot<一>:零基础入门之springboot及idea搭建Springboot<二>:零基础入门之yaml、properties配置文件介绍及实战使用Springboot<三>:多环境切换,profile实例演示Springboot<四>:stater入门教学Springboot<五>:springboot之常用注解大全Springboot<六>:mysql配置及数据库查询,实现增删改查Springboot<七>:mybatis-plus入门及实战使用实现增删改查Springboot<八>:mybatis-plus条件构造器使用手册Springboot<九>:mybatis-plus之自定义sql零基础教学Springboot<十>:mybatis之xml映射文件>、<=等特殊符号写法... ...如果还想要学习更多,小伙伴们可关注bug菌专门为大家创建的专栏《SpringBoot零基础入门》,从无到有,从零到一!希望能帮到大家。
一:前言 今天着手一个新的项目,是一个很远古的ssh项目,用的还是jdk1.7,外配tomcat ,写习惯了springboot,哪里还会记得这种配置,于是,项目必须给我一个钟头跑起来,我只要硬着头皮上,包括idea导入web项目,及配置多jdk;二、本地环境Windows10 + jdk1.8三、实现需求 在不影响别的项目运行的同时,也需要支持当前的项目,于是想法是jdk 如何配置多个并且切换容易,总不能真配置两套吧;于是我想到了,如下配置确实很节省时间,又方便!不信咱们往下看!首先,你必须先来了解一下: 当系统已经安装过一个版本jdk时,由于需要安装jdk7,这个时候安装好后,使用java -version发现并不是配置的环境变量的信息,这是因为安装jdk8时默认生成 C:\ProgramData\Oracle\Java\javapath 文件,并把java.exe javaw.exe javaws.exe放到此文件下,然后把该路径放到path的最前面,所以无论你怎么配置环境变量,使用java -version始终是安装的1.8; 所以,第一步要做的事就是,先把jdk1.8默认生成的配置先清空,比如你先安装过高版本的jdk再想安装低版本的jdk;四、实现步骤1.删除 C:\Windows\System32 下三个文件在第一次安装jdk1.7时,自动将java.exe、javaw.exe、javaws.exe三个可执行文件复制到了C:\Windows\System32目录,由于这个目录在Windows环境变量中的优先级高于JAVA_HOME设置的环境变量优先级。所以将C:\Windows\System32目录下的java.exe、javaw.exe、javaws.exe三个可执行文件 删除即可。2、删除系统环境变量中安装jdk8产生的 Path C:\ProgramData\Oracle\Java\javapath在安装jdk8的时候,安装过程中会在系统变量Path的最前面加上了C:\ProgramData\Oracle\Java\javapath;,这是安装jdk8的时候带出来的,并且在Path的最前面,所以无论修改注册表还是Java控制台都没有用,执行的指令在系统变量中搜寻命令时最先找到的就是C:\ProgramData\Oracle\Java\javapath;,始终是jdk8的。也删除。3、新加两个环境变量:按如下配置:如下图JAVA_HOME %JAVA7_HOME% JAVA7_HOME C:\Program Files\Java\jdk1.7.0_67 JAVA8_HOME C:\Program Files\Java\jdk1.8.0_162注意:如果要改变当前jdk版本,直接修改 JAVA_HOME 的值就好了,目前是读取的jdk1.74、配置CLASSPATH//CLASSPATH %JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar5、配置Path,【注意:path路径 加在最前面!!!】//Path %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;注意:修改环境变量之后,务必记得确定再确定即生效,然后cmd也要关闭重新打开!要不然容易误导自己;6、jdk1.7、jdk1.8win 等各个版本 传送门需要的加群自取哦,群号:708072830,你想要的群文件都有,闲余之时还能水水群,交流技术等等。学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!参考:1、win10 安装 jdk1.7 jdk1.8共存环境配置2、windows下安装两个jdk
问题描述: 生产环境redis 查询出错,排查原因才发现,在做login记录缓存到redis,发现有两处地方,set key 时同名了,导致在get key 获取 导致出错! 如下,你在“GET”只是对“SET” 为String 类型的key获取,而“Lists”类型 用“GET”获取,就会出现 WRONGTYPE Operation against a key holding the wrong kind of value 这种报错方式问题定位:由于你 set 了一个Lists类型的 key ,而你在获取 时却用了非 Lists 的操作方法获取。db.Get().Do("SET", userId+"login",1)db.Get().Do("LPUSH", userId+"login",1) 不同数据类型对应不同的获取 value 方式,SET key ==》GET key ; LPUSH ==》LINDEX 等;
第一步:点击电脑桌面左下角的开始菜单,选择“SQL Server 2017配置管理器”打开。第二步:如下图步骤2处,双击”TCP/IP“协议名称,会弹出协议、IP地址的选项卡,切换到“IP地址”选项。修改tcp端口 ,最后5步骤后点击应用;第三步、返回SQL Server配置管理器的主界面,在左侧菜单中,选择“SQL Server服务”,在右侧显示框中选择“SQL Server(MSSQLSERVER)”,在右键菜单中选择“重新启动”。至此,完成修改SQL Server 2017数据库默认端口。如下图参考1:安装 SqlServer 2017教程 参考2:windows10彻底卸载sql server 2017
QueryBuilder 是es中提供的一个查询接口private SearchResponse getApiResponseByDetail(SearchRequestBuilder responseBuilder,String condition) { String time1 = "2020-01-02T00:00:00.000Z"; String time2 = "2020-01-02T15:59:59.000Z"; RangeQueryBuilder rangequerybuilder = QueryBuilders //传入时间,目标格式2020-01-02T03:17:37.638Z .rangeQuery("@timestamp") .from(time1).to(time2); SearchResponse searchResponse = responseBuilder.setQuery( QueryBuilders.boolQuery() //must表示and .must(rangequerybuilder) //根据时间范围查询 .must(QueryBuilders.existsQuery("api_id")) .must(QueryBuilders.matchPhraseQuery("detail", condition)) ).setExplain(true).execute().actionGet(); return searchResponse; } 复制代码注意:es存储日志 是按照UTC时间格式存放,以@timestamp 作为时间范围查询条件,即from(Date1) to(Date2)Date1、Date2入参必须是标准的utc格式;本地时间转utc?不清楚的小伙伴可以看下这篇 java如何实现本地时间转成UTC时间格式?1、BoolQuery( ) 用于组合多个叶子或复合查询子句的默认查询must 相当于 与 & =must not 相当于 非 ~ !=should 相当于 或 | or filter 过滤boolQuery().must(termQuery("content", "test1")) .must(termQuery("content", "test4")) .mustNot(termQuery("content", "test2")) .should(termQuery("content", "test3")) .filter(termQuery("content", "test5")); 复制代码 2、Elasticsearch java api 常用查询方法QueryBuilder构造举例ps:以下来源:www.mamicode.com/info-detail…精确查询以下字段名用${fieldName}代替,具体值用${fieldValue}代替1、数字//单个 QueryBuilder qb1 = QueryBuilders.termQuery("${fieldName}", "${fieldValue}"); //批量 QueryBuilder qb1 = QueryBuilders.termsQuery("${fieldName}", "${fieldValues}"); 复制代码2、字符串//单个 QueryBuilder qb1 = QueryBuilders.termQuery("${fieldName}.keyword", "${fieldValue}"); //批量 QueryBuilder qb1 = QueryBuilders.termsQuery("${fieldName}.keyword", "${fieldValues}"); 复制代码模糊查询1、数字数字查询都为精确查询2、字符串QueryBuilder qb1 = QueryBuilders.moreLikeThisQuery(new String[]{"${fieldName}"}, new String[]{"${fieldValue}"}, null); 复制代码范围查询数字//闭区间查询 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").from(${fieldValue1}).to(${fieldValue2}); //开区间查询 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").from(${fieldValue1}, false).to(${fieldValue2}, false); //大于 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").gt(${fieldValue}); //大于等于 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").gte(${fieldValue}); //小于 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").lt(${fieldValue}); //小于等于 QueryBuilder qb1 = QueryBuilders.rangeQuery("${fieldName}").lte(${fieldValue}); 复制代码多条件查询QueryBuilder qb1 = QueryBuilders.moreLikeThisQuery(new String[]{"${fieldName1}"}, new String[]{"${fieldValue1}"}, null); QueryBuilder qb2 = QueryBuilders.rangeQuery("${fieldName2}").gt("${fieldValue2}"); QueryBuilder qb3 = QueryBuilders.boolQuery().must(qb1).must(qb2);
先科普一下;时间标准简介UTC(世界标准时间)协调世界时,又称世界标准时间或世界协调时间,简称UTC(从英文“Coordinated Universal Time”/法文“TempsUniversel Coordonné”而来),是最主要的世界时间标准,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间。GMT(格林尼治平时)格林尼治平时(又称格林尼治平均时间或格林尼治标准时间,旧译格林威治标准时间;英语:Greenwich MeanTime,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。理论上来说,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球在它的椭圆轨道里的运动速度不均匀,这个时刻可能与实际的太阳时有误差,最大误差达16分钟。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治时间已经不再被作为标准时间使用。现在的标准时间,是由原子钟报时的协调世界时(UTC)。CST(北京时间)北京时间,China Standard Time,中国标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8。ps:以上出自 blog.csdn.net/top_code/ar…Date日期格式的各种转换,可参考:blog.csdn.net/qq_35893120… 需求: 最终目标是获取指定的UTC时间格式; // 目标格式:2020-01-02T03:17:37.638Z分析: 1、获取指定时间串,比如:获取昨日凌晨时间 2020-01-02 00:00:00 2、String转Date类型,用到了parse 比如:Thu Jan 02 00:00:00 CST 2020 3、然后是Date转UTC格式类型,用到了format 比如:2020-01-02T00:00:00Z代码实现:如下; 方法一:public static void main(String[] args) throws ParseException { Calendar cal=Calendar.getInstance(); cal.add(Calendar.DATE,-1);//这里改为-1 获取昨日时间 Date time=cal.getTime(); String format1 = new SimpleDateFormat("yyyy-MM-dd 00:00:00").format(time);//获取昨日00:00:00时间 String format2 = new SimpleDateFormat("yyyy-MM-dd 23:59:59").format(time);//获取昨日23:59:59时间 System.out.println("获取指定时间且指定格式的时间串,format="+format1); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date1 = sdf.parse(format1); Date date2 = sdf.parse(format2); System.out.println("将获取到时间串转Date类型,date="+date1); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //由于我是指定时间转换,必不考虑utc+8 的时区差 String time1 = format.format(date1); String time2 = format.format(date2); //获取当前系统时间 UTC格式 String time3 = format.format(new Date()); System.out.println("输出指定utc格式:time="+time1); System.out.println("输出当前系统时间utc格式:time="+time3); } 复制代码控制台打印:获取指定时间且指定格式的时间串,format=2020-01-02 00:00:00 将获取到时间串转Date类型,date=Thu Jan 02 00:00:00 CST 2020 输出指定utc格式:time=2020-01-02T00:00:00Z 输出当前系统时间utc格式:time=2020-01-03T11:15:40Z 复制代码方式二:public static void main(String[] args) throws ParseException { String str="2020-01-02 23:59:59"; SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date date =sdf.parse(str); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int zoneOffset = calendar.get(Calendar.ZONE_OFFSET); int dstOffset = calendar.get(Calendar.DST_OFFSET); calendar.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset)); long timeInMillis = calendar.getTimeInMillis(); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); System.out.println(df.format(timeInMillis)); } 复制代码 控制台打印:2020-01-02T15:59:59.000Z
MAT是什么?MAT(Memory Analyzer Tool),一个基于Eclipse的内存分析工具,是一个快速、功能丰富的JAVA heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小,看看是谁阻止了垃圾收集器的回收工作,并可以通过报表直观的查看到可能造成这种结果的对象。安装 MAT和其他插件的安装非常类似,MAT 支持两种安装方式,一种是“单机版“的,也就是说用户不必安装 Eclipse IDE 环境,MAT 作为一个独立的 Eclipse RCP 应用运行;另一种是”集成版“的,也就是说 MAT 也可以作为 Eclipse 的一部分,和现有的开发平台集成。集成版的安装需要借助 Update Manager。------------------------------------------------------------------------------------------------------------------------------------------由于工作统一需要,使用idea ,此处便直接演示如何安装独立版的MAT分析工具;下载安装1、下载地址:www.eclipse.org/mat/downloa…2、选择Windows(x86_64); ps:根据自己系统下载相对应的版本,我这里下载的是Windows64位版的 3、点击Download;下载完得到一个zip文件,解压后得到mat文件; 4、双击MemoryAnalyzer.exe 打开MAT分析工具;使用说明:1、环境: 运行内存分析器的最低Java版本是 1.8.02、使用 Memory Analyzer Tools 获取堆存储 1、启动进程仅需单击两次。在菜单中,选择“file --> Acquire Heap Dump...” 或者有直接生成好的dump文件,则直接Open Heap Dump ,打开即可; 2、选择Acquire Heap Dump后。您可以看到正在运行的Java进程;选择你所需要分析的java进程,我这里是选择项目的主模块,因为项目为一个微服务架构体系。您会在底部注意到为堆转储文件自动生成的路径。顺便说一句,我建议选择一个仅包含转储的文件夹,接着finish即可。MAT除了它之外还生成许多其他文件。标题3、finish完后,等待堆转储写入磁盘并由MAT解析。对于巨大的Java堆大小,这些操作可能需要一些时间。右下角会有进度条加载标题4、加载完后,随后会弹出如下一个新的对话框,提示您进行快速概述,默认选择第一个(泄漏可疑报告),finish即可。 5、及自动展开如下该窗口,即可进行内存分析;6、实际上,这种方式要求该进程仍在运行。这是一个调试/开发用例 。7、介绍一下下面的Actions的几个标签;重点介绍Histogram 和 Leak Suspects ,其余的暂待研究,Histogram :可以列出内存中的对象,对象的个数以及大小。Dominator Tree:可以列出那个线程,以及线程下面的那些对象占用的空间。Top consumers:通过图形列出最大的object。Leak Suspects:通过MA自动分析泄漏的原因。------------------------------------------------------------------------------------------------------------------------------------------1、Histogram :Lists number of instances per classHistogram是我们使用最多的一个,可以列出内存中的对象,对象的个数及其大小Class Name : 类名称,java类名Objects : 类的对象的数量,这个对象被创建了多少个Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用Retained Heap :是shallow Heap的总和,也就是该对象被GC之后所能回收到内存的总和点击Objects 或者 Shallow Heap 可以直观看到,定位到项目上具体哪一个实体所耗内存情况, 然后单击右键,Lists Objects ---> with incoming references 可以具体看到那些对象引用了。一般来说,Shallow Heap堆中的对象是它的大小和保留内存大小相同的对象是堆内存的数量时,将释放对象被垃圾收集。保留设置一组主要的对象,例如一个特定类的所有对象,或所有对象的一个特定的类装入器装入的类或者只是一群任意对象,是释放的组对象如果所有对象的主要设置变得难以接近的。保留设置包括这些对象以及所有其他对象只能通过这些对象。保留大小是总堆大小中包含的所有对象的保留。------摘自eclipse 关于的详细讲解,建议大家可以查看Shallow heap & Retained heap,这是个很重要的概念。2、Dominator Tree: List the biggest objects and what they keep alive我们可以看到session 占比最大;3、Consumers: Print the most expensive objects grouped by class and by package.这张图展示的是占用内存比较多的对象的分布,下面是具体的一些类和占用。****按等级分布的类使用情况,其实也就是按使用次数查看,org.apache.shiro.web.session.mgt.DefaultWebSessionManager被排在第一还有一张图是我们比较关心的,那就是按包名看占用,根据包我们知道哪些公共用的到jar或自己的包占用;如此分析,就可以看到包和包中哪些类的占用比较高。4、Leak Suspects : includes leak suspects and a system overview自动分析内存内存泄漏的原因,可以直接定位到Class,且行数。 在报告上最醒目的就是一张简洁明了的饼图,从这份报告,看到该图深色区域被怀疑有内存泄漏,可以发现整个heap只有442.7M内存,深色区域就占了82%。所以,MAT通过简单的报告就说明了项目是有可疑代码的,具体点开详情来找到类;在图的下方还有对这个可疑对象的进一步描述。我们可以看到内存是由org.apache.shiro.web.session.mgt.DefaultWebSessionManager 的实例消耗的,org.springframework.boot.loader.LaunchedURLClassLoader 负责这个对象的加载。这段描述非常短,但我相信您已经可以从中找到很多线索了,比如是哪个类占用了绝大多数的内存,它属于哪个组件等等。接下来,我们应该进一步去分析问题,为什么一个 DefaultWebSessionManager 会占据了系统 82 的内存,谁阻止了垃圾回收机制对它的回收。接着点击 下方的 Details > ,如下:a、可以看到 Accumulated Objects by Class in Dominator Tree 【从根元素到内存消耗聚集点的最短路径】我们可以很清楚的看到整个引用链,内存聚集点是一个拥有大量对象的集合,如果你对代码比较熟悉的话,相信这些信息应该能给你提供一些找到内存泄露的思路了。接下来,我们再继续看看,这个对象集合里到底存放了什么,为什么会消耗掉如此多的内存。b、 Accumulated Objects in Dominator Tree 【内存消耗聚集对象信息】在这张图上,我们可以清楚的看到,这个对象集合中保存了大量ConcurrentHashMap对象的引用,就是它导致的内存泄露。至此,我们已经拥有了足够的信息去寻找泄露点,回到代码,你会发现原来是session 在暂存token的时候,业务需求,在每调用一次接口,session便要用map存一次token,定时任务的产生,导致项目不到一天便挂了,至此, 问题所导致内存泄露缘由已经找到,具体怎么解决暂代思考。------------------------------------------------------------------------------------------------------------------------------------------总结从上面的例子我们可以看到用 MAT 来进行堆转储文件分析,寻找内存泄露非常简单,尤其是对于新手而言,这是一个很好的辅助分析工具。但是,MAT 绝对不仅仅是一个“傻瓜式”内存分析工具,它还提供很多高级功能,比如 MAT 支持用 OQL(Object Query Language)对 heap dump 中的对象进行查询,支持对线程的分析等,有关这些功能的使用可以参考 MAT 的帮助文档。
P3c是阿里代码规范检查工具,该插件由阿里巴巴 P3C 项目组研发。代码已经开源,GitHub:github.com/alibaba/p3c阿里介绍文章:mp.weixin.qq.com/s/IbibsXlWH…该插件已支持了IDEA、Eclipse,在扫描代码后,将不符合规约的代码显示出来,甚至在 IDEA 上,还基于 Inspection 机制提供了实时检测功能,编写代码的同时也能快速发现问题所在,还实现了批量一键修复的功能。【支持版本】IDEA官方仓库:最低支持版本14.1.7,JDK1.7+Eclipse版插件:支持4.2(Juno,JDK1.8+)及以上版本【检测更新】可以通过 Help >> Check for Udates 进行插件新版检测 或者直接到安装该Alibaba Java Coding Guidelines 的那个窗口,直接点击upload(下图2处)【图1】 【插件卸载】在插件面板点击"uninstall"即可。------------------------------------------------------------------------------------------------------------------------------------------【基于IDEA 环境,简易安装教程】打开 IDEA,选择 File -> Settings -> Plugins -> Browse repositories 后,输入搜索 alibaba 选中 Alibaba Java Coding Guidelines;点击插件详情中的"install"按钮,按照其提示即可完成安装,安装完成后需重启IDEA(由于我是已安装,即截图展示为可更新update);【图2】 【功能体验】安装后完成后,需要重启IDEA,重启后就可以在菜单栏中看到它的功能按钮一、选中整个项目右键会出现两个功能按钮,点击绿色的【 编码规约扫描 】,即可开始扫描全局代码,或者在工程目录上右键也会出现检测的功能按钮。二、如果不想全部扫描,只扫描某一文件夹下的所有文件或者单一文件的话,选中该目录或者单一文件右键也会出现此功能按钮,效果就是扫描出该文件所包含的bug或者不规范。三、其中部分不规范的代码,单击右键支持一键修复;【总结】代码规范检测,有助于我们进行代码排错、提高编码效率、以及提升项目整体的运行速度。--------------------------------------------------------------------------------------------------------------------------------------
一:截图展示说明问题,二、问题描述: 查询不到ipv4 地址,如上图;三:环境说明: Windows 10 ,本地搭建centos 7,创建完虚拟机一切步骤之后,发现查询ip没有,四、解决办法: 1、查看网卡配置文件信息,直接进入vi编辑,把ONBOOT这一项改为yes,再执行 :wq! 强制保存 并退出;【说明:每个人对应的ifcfg-exxxxxx 文件名是不一样的,我的配置文件名为:ifcfg-eno16777736】vi /etc/sysconfig/network-scripts/ifcfg-eno16777736 2、重新启动网络服务:sudo service network restart 网络重启失败了,Job for network.service failed because the control process exited with error code如下是解决过程:1、先查看下你电脑有没有禁用了VMware DHCP service、VMware Workstation Service、VMware NAT service 这几个vm服务,如果禁用了则开启并设置自动。确保这三服务开启;2、如果你改成了静态ip别忘了将BOOTPROTO改为static【此处我没有修改,默认DHCP】3、接着执行 service network restart显示ok说明重启网络成功,ip也对应查询出来了,ip查询成功截图:如下
本怂亲测有效,如有任何问题欢迎下方留言!问题描述: 昨天还正常使用,一早打开发现如图一问题, VMware Workstation 无法连接到虚拟机。请确保您有权限运行该程序、访问改程序使用的所有目录以及访问所有临时文件目录。【图一】原因: 重启或者电脑关机的时候没关虚拟机导致服务VMware Authorization Service被关了【事后查看确实服务没开】解决思路:重启了下Vmware Authorization Service服务、可以在Vmware Authorization Service服务右击属性,进入恢复,将第一次失败等设置为重新启动服务操作步骤:1、Windows+R cmd 打开小黑框,输入services.msc(打开服务管理快捷方式) ->>Vmware Authorization Service->>选择自动、右击选择启动【图二】2、Vmware Authorization Service服务右击属性,进入恢复,将第一次失败等设置为重新启动服务【图二】图一:图二:
DataX本身作为数据同步框架,将不同数据源的同步抽象为从源头数据源读取数据的Reader插件,以及向目标端写入数据的Writer插件,理论上DataX框架可以支持任意数据源类型的数据同步工作。同时DataX插件体系作为一套生态系统, 每接入一套新数据源该新加入的数据源即可实现和现有的数据源互通。一、环境搭建Java安装(java>=1.6)Python安装(Python>=2.6)根据自己的系统选择相应的JDK二、本地下载datax 下载地址:github.com/alibaba/Dat…下载后解压至本地某个目录,进入bin目录,即可运行同步作业:三、准备好配置文件json文件把写好的配置文件,放到 bin目录下 ;从另一台服务器mysql数据拷贝到本地mysql服务器;具体读写json格式,我就不一一介绍,详情可见:github.com/alibaba/Dat…四、运行命令 在bin目录下 python datax.py ../job.json(配置文件)成功后会显示如下内容 :python datax.py {YOUR_JOB.json}解释一下:{YOUR_JOB.json} 指你写的带路径的配置文件;比如我写的demo路径;../job.json 表示bin路径的上一级目录下的job.json文件。需要注意俩表同步之间 字段名可以不同 但字段数量和类型必须一样;
@Override public Page<DemandEntity> selectByDepartmentDisplay(DemandEntity demandEntity) { EntityWrapper<DemandEntity> wrapper = new EntityWrapper<DemandEntity>(); wrapper.eq(!StringUtils.isNullOrEmpty(demandEntity.getNameDemandDepartmentDispaly()),"name_demand_department_dispaly",demandEntity.getNameDemandDepartmentDispaly()); Page<DemandEntity> demandEntityPage = this.selectPage(demandEntity.getPages(),wrapper); return demandEntityPage; } 只需要在eq条件构造器中只需要添加 一句判断即可:!StringUtils.isNullOrEmpty(demandEntity.getNameDemandDepartmentDispaly() 为true,就拼接where条件;为Flase就不拼接; eq(boolean condition, R column, Object val) 第一个参数 为boolean类型 true就拼接上 flase就不拼接;其中 StringUtils.isNullOrEmpty()方法,作用是:判断对象或对象数组中每一个对象是否为空: 对象为null,字符序列长度为0,集合类、Map为empty;并附上 isNullOrEmpty() 源码;/** * 判断对象或对象数组中每一个对象是否为空: 对象为null,字符序列长度为0,集合类、Map为empty * * @param obj * @return */ public static boolean isNullOrEmpty(Object obj) { if (obj == null) return true; if (obj instanceof CharSequence) return ((CharSequence) obj).length() == 0; if (obj instanceof Collection) return ((Collection) obj).isEmpty(); if (obj instanceof Map) return ((Map) obj).isEmpty(); if (obj instanceof Object[]) { Object[] object = (Object[]) obj; if (object.length == 0) { return true; } boolean empty = true; for (int i = 0; i < object.length; i++) { if (!isNullOrEmpty(object[i])) { empty = false; break; } } return empty; } return false; }---【拓展】---eqeq(R column, Object val) eq(boolean condition, R column, Object val)等于例: eq(“name”, “老王”) 等价于 name = ‘老王’
JENKINS_HOME 目录中的 /secrets/initialAdminPassword 文件明文保存 admin 用户的密码,直接查看即可。用笔记本打开即为初始密码:
今日怂怂通过Navicat导sql数据的时候,发现,数据量在大于1000条的表记录中,凡是只显示一千条,让我百思不得其解,我以为sql文件有问题,我就从id=1001手动插入,结果报错,说主键id值已重复,真奇怪,表中压根就没有id为1001的记录啊,主键重复,怎么就能重复呢,这不是在逗我,结果新大陆就此被我发现了;这是由于主键id重复报错界面: 才发现是navicat分页了,它是一页只能显示1000行,超出1000行就在下一页显示了,点击右下角的向左向右箭头就可以看到其他部分的数据了。 你也可以点击设置来修改默认值,取消限制记录就在一页全部显示,或者调整一页显示的行数数量(如图被怂怂改成了一页显示2000条数据),但实质上并作用不大,总不能数据太多就一直调节一页显示的数量吧,显然不科学。
前言:今天在写项目的时候,由于发现子项目命名错了,结果在使用idea重命名的同时,遇到了一个巨坑;正文:结果发现重命名之后同一类型的文件夹名称也被一并改了分析:发现是自己重命名(Rename)的时候默认选了搜索全局文本;忘了取消默认选项,结果项目中任何有同原名的文件名或方法名等都被一统重命名了,这小心脏受不鸟啊; 结论: idea Rename 下方出现的两默认勾选的到底是什么?【如上图红框圈选】 1、search in comments and strings中译:在注释和字符串中搜索 2、search for text occurrences中译:搜索出现的文本切记:只针对修改独个名称,这两个选项不要选,选了会替换掉没有引用到地方;
今天怂怂就为大家分享一篇清除input输入框历史记录的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随怂怂过来看看吧清除input输入框的历史记录,即在input中加入 autocomplete="off" 即可;<form action="demo_form.asp" method="get"> E-mail: <input type="email" name="email" autocomplete="off" /><br /> <input type="submit" /> </form>autocomplete 属性规定输入字段是否应该启用自动完成功能。自动完成允许浏览器预测对字段的输入。当用户在字段开始键入时,浏览器基于之前键入过的值,应该显示出在字段中填写的选项。input 的属性autocomplete 默认为on,其含义代表是否让浏览器自动记录之前输入的值注释:autocomplete 属性适用于 <form>,以及下面的 <input> 类型:text, search, url, telephone, email, password, datepickers, range 以及 color。语法<input autocomplete="value">属性值
今日在做项目的时候,遇到一个小问题,在同一页面,使用Element的弹框组件,却发现第二次弹框得鼠标点击之后才会显示变亮,似乎被遮住了!(如图效果); 才发现,element 的dialog嵌套问题,之所以第二次弹出的会被遮住,是因为没有给定 append-to-body正常情况下,我们不建议使用嵌套的 Dialog,如果需要在页面上同时显示多个 Dialog,可以将它们平级放置。对于确实需要嵌套 Dialog 的场景,我们提供了append-to-body属性。将内层 Dialog 的该属性设置为 true,它就会插入至 body 元素上,从而保证内外层 Dialog 和遮罩层级关系的正确。如果需要在一个 Dialog 内部嵌套另一个 Dialog,需要使用 append-to-body 属性。<template> <div id="datagovernIssu"> <div id="catalogTotalChart" style="height: 500px;width: 100%;"></div> <el-dialog :title="'数据治理总览-已发布服务-市交通委'+ govName" :visible.sync="innerVisible" width="1100px" append-to-body> //第二次弹框! <template> <div style="margin-left: 450px" > <span style="align-content: center;">上海市交通委已发布服务详情</span></div> <el-table :data="tableData" style="width: 100%"> </template> </el-dialog> </div></template><script> export default { name: 'datagovernIssu', data() { } } },methods: { clickHandle() { this.catalogTotalChart.on('click','series.bar', (params) => { this.innerVisible = true;//将内层 Dialog 的该属性设置为 true,它就会插入至 body 元素上,从而保证内外层 Dialog 和遮罩层级关系的正确。 }); } }在第二次弹框加一个append-to-body就好了!(如图演示) --------------------------------拓展--------------------------------Element组件之【Dialog 对话框】Attributes
今天怂怂就为大家分享一篇在写毕设的时候遇到一个问题以及如何解决问题的,具有很好的参考价值,希望对大家有所帮助。一起跟随怂怂过来看看吧!数据库使用数值存储权限(role)这个字段 。由于项目需求,页面需显示具体权限角色名称;比如 role="0" 需显示成 “系统管理员 ”。这是我的源码:<c:if test="${bean.role==0}"> <div class="unit"> <label>权限:</label> <input type="text" name="role" size="30" value="系统管理员" readonly /> </div> </c:if> 复制代码可是发现一个问题,当你在进行update操作的时候,value="系统管理员"也被一并传入数据库(如下图),这就不符合数据库字段的设计,肯定是会报异常;如下有两种解决方案:方案一:不推荐使用,比较笨的方法通过对后端update接口处进行值的判断,再重新赋值的方式进行提交,若是角色分配过多,就容易造成代码冗余!User bean = userDao.selectUser(id); if(request.getParameter("role")=="系统管理员" || request.getParameter("role").equals("系统管理员")) { bean.role="0";}方案二:通过input隐藏域传值【推荐使用】【隐藏域】隐藏域是指在页面中对于用户是不可见的,在表单中插入隐藏域的目的在于收集或发送信息,以利于被处理表单的程序所使用。浏览者单击发送按钮发送表单的时候,隐藏域的信息也被一起发送到服务器。代码实现:<c:if test="${bean.role==0}"> <div class="unit"> <label>权限:</label> <input type="text" size="30" value="系统管理员" readonly /> <input name="role" value="0" type="hidden"/> //通过隐藏域把value="0"传到后台 </div></c:if>解释一下:第一个input框只是用作效果显示而不会被提交到后台中,起着关键作用的就是name属性;name 属性用于对提交到服务器后的表单数据进行标识,或者在客户端通过 JavaScript 引用表单数据,也就是你后端通过 request.getParameter(" 参数"),其中的参数也就是你 name 的属性值(图上:name="role")。注意:一旦使用隐藏域绑定role的value值,在修改role的同时,表单提交到后端,role值还是以隐藏域绑定的value值提交,所以,像权限(role)修改操作,就不言而喻了!切记!! --------------------------------拓展---------------------------------1、<input> 标签的 name 属性定义和用法name 属性规定 input 元素的名称。name 属性用于对提交到服务器后的表单数据进行标识,或者在客户端通过 JavaScript 引用表单数据。注释:只有设置了 name 属性的表单元素才能在提交表单时传递它们的值。语法<input name="value">2、HTML DOM Hidden 对象Hidden 对象代表一个 HTML 表单中的某个隐藏输入域。这种类型的输入元素实际上是隐藏的。这个不可见的表单元素的 value 属性保存了一个要提交给 Web 服务器的任意字符串。如果想要提交并非用户直接输入的数据的话,就是用这种类型的元素。在 HTML 表单中 <input type="hidden"> 标签每出现一次,一个 Hidden 对象就会被创建。您可通过遍历表单的 elements[] 数组来访问某个隐藏输入域,或者通过使用document.getElementById()。Hidden 对象的属性
今天怂怂就为大家分享一篇通过css禁用状态,样式设置以及不可点击样式事件的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随怂怂过来看看吧一:为input框添加禁用状态1、readonly表示此域的值不可修改,仅可与 type="text" 配合使用,可复制,可选择,可以接收焦点,后台能接收到传值.代码演示:<input type="text" name="firstname" value="" readonly="readonly" />2、disabled表示禁用input元素,不可编辑,不可复制,不可选择,不能接收焦点,,后台不能接收到传值.代码演示:<input type="text" name="firstname" value="" disabled="disabled" /> 二:为禁用状态添加状态鼠标不可点击主要是两种表现:1.鼠标不可点击时的显示状态:cursor: not-allowed样式演示:<style> input[readonly] //readonly:后台能接收此input框传值 { background:#dddddd; //为带有readonly的input框添加背景颜色 cursor: not-allowed // 表示一个红色的圈加一个斜杠 } </style>2.鼠标原有的事件不能实现:pointer-events:none样式演示:<style> input[disabled] //disadled:后台不可接收此input传值 { background:#dddddd; //为带有disabled的input框添加背景颜色 pointer-events:none;//鼠标点击不可修改 } </style>--------------------------------拓展---------------------------------cursor 定义和用法cursor 属性规定要显示的光标的类型(形状)。该属性定义了鼠标指针放在一个元素边界范围内时所用的光标形状(不过 CSS2.1 没有定义由哪个边界确定这个范围)。默认值:auto继承性:yes版本:CSS2JavaScript 语法:object.style.cursor="crosshair"可能的值值描述url需使用的自定义光标的 URL。注释:请在此列表的末端始终定义一种普通的光标,以防没有由 URL 定义的可用光标。default默认光标(通常是一个箭头)auto默认。浏览器设置的光标。crosshair光标呈现为十字线。pointer光标呈现为指示链接的指针(一只手)move此光标指示某对象可被移动。e-resize此光标指示矩形框的边缘可被向右(东)移动。ne-resize此光标指示矩形框的边缘可被向上及向右移动(北/东)。nw-resize此光标指示矩形框的边缘可被向上及向左移动(北/西)。n-resize此光标指示矩形框的边缘可被向上(北)移动。se-resize此光标指示矩形框的边缘可被向下及向右移动(南/东)。sw-resize此光标指示矩形框的边缘可被向下及向左移动(南/西)。s-resize此光标指示矩形框的边缘可被向下移动(南)。w-resize此光标指示矩形框的边缘可被向左移动(西)。text此光标指示文本。wait此光标指示程序正忙(通常是一只表或沙漏)。help此光标指示可用的帮助(通常是一个问号或一个气球)。 ps:cursor用法教程来自www.w3school.com.cn/cssref/pr_c…
验证码: 【科普】主要是为了减少恶意的行为发生! 因为现在营销里面有个网络暴力营销行为,就是自动在网上找一些能注册的论坛或者留言板什么的,自动注册,然后发一些广告,这样造成很多垃圾信息,网站为了减少这种现象发生,就出现了验证码,因为这个码是随机生成的,所以软件不可能自动识别,不能识别也就不能发送信息!只有真正的人类才能操作,所以可以有效的防止某些恶意的行为,在开发过程中验证码可能也是一个经常要有的功能。【代码实现】1.建立action,并且导入相应的包CheckImgAction.javapackage manage.action;import java.awt.Color;import java.awt.Font;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.image.BufferedImage;import java.util.Random;import javax.imageio.ImageIO;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import org.apache.struts2.ServletActionContext;import com.opensymphony.xwork2.ActionSupport;public class CheckImgAction extends ActionSupport{ private static final long serialVersionUID = 1L; public String checkImg() throws Exception{ int width = 120; int height = 30; // 步骤一 绘制一张内存中图片 BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 步骤二 图片绘制背景颜色 ---通过绘图对象 Graphics graphics = (Graphics) bufferedImage.getGraphics();// 得到画图对象 --- 画笔 // 绘制任何图形之前 都必须指定一个颜色 graphics.setColor(getRandColor(200, 250)); graphics.fillRect(0, 0, width, height); // 步骤三 绘制边框 graphics.setColor(Color.WHITE); graphics.drawRect(0, 0, width - 1, height - 1); // 步骤四 四个随机数字 Graphics2D graphics2d = (Graphics2D) graphics; // 设置输出字体 graphics2d.setFont(new Font("宋体", Font.BOLD, 18)); String words ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; //String words = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; Random random = new Random();// 生成随机数 // 定义StringBuffer StringBuffer sb = new StringBuffer(); // 定义x坐标 int x = 10; for (int i = 0; i < 4; i++) { // 随机颜色 graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random .nextInt(110), 20 + random.nextInt(110))); // 旋转 -30 --- 30度 int jiaodu = random.nextInt(60) - 30; // 换算弧度 double theta = jiaodu * Math.PI / 180; // 生成一个随机数字 int index = random.nextInt(words.length()); // 生成随机数 0 到 length - 1 // 获得字母数字 char c = words.charAt(index); sb.append(c); // 将c 输出到图片 graphics2d.rotate(theta, x, 20); graphics2d.drawString(String.valueOf(c), x, 20); graphics2d.rotate(-theta, x, 20); x += 30; } // 将生成的字母存入到session中 ServletActionContext.getRequest().getSession().setAttribute("checkImg", sb.toString()); // 步骤五 绘制干扰线 graphics.setColor(getRandColor(160, 200)); int x1; int x2; int y1; int y2; for (int i = 0; i < 30; i++) { x1 = random.nextInt(width); x2 = random.nextInt(12); y1 = random.nextInt(height); y2 = random.nextInt(12); graphics.drawLine(x1, y1, x1 + x2, x2 + y2); } // 将上面图片输出到浏览器 ImageIO graphics.dispose();// 释放资源 ImageIO.write(bufferedImage, "jpg", ServletActionContext.getResponse().getOutputStream()); return null; } /** * 取其某一范围的color * * @param fc * int 范围参数1 * @param bc * int 范围参数2 * @return Color */ private Color getRandColor(int fc, int bc) { // 取其随机颜色 Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); }}2.配置验证码Action------->struts-checkImg.xml<package name="manage_checkImg" namespace="/" extends="struts-default"> <action name="checkImg" method="checkImg" class="manage.action.CheckImgAction"/></package>3.在structs中配置验证码的------->Action struts.xml<include file="strutsconfig/struts-checkImg.xml"></include>4.在Spring中配置验证码的action------->spring-actions.xml<bean id="checkImgAction" name="checkImgAction" class="manage.action.CheckImgAction" scope="prototype"> </bean>5.在需要用到验证码的jsp中写上相应的代码,显示验证码,并且能够在点击图片的时候切换另一张图片,所以需要用到onclick事件,<div class="elements"><li class="user-name"style="list-style: none; border: 1px solid #dedede; border-radius: 4px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);"><input type="text" id="codenum" name="codenum" class="form-control " value="" placeholder="账号" style="margin: 0; padding: 0; border: 0px; box-shadow: none; height: 40px; font-size: 16px" /></li><li class="password" style="list-style: none; border: 1px solid #dedede; border-radius: 4px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);"><input type="password" id="password" name="password"class="form-control" value="" placeholder="密码" style="margin: 0; padding: 0; border: 0px; box-shadow: none; height: 40px; font-size: 16px" /></li><div class="code-wrapper"><li class="code"style="list-style: none; border: 1px solid #dedede; border-radius: 4px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);"><input type="text" id="checkImg" name="checkImg" class="form-control " value="" placeholder="验证码" style="margin: 0; padding: 0; border: 0px; box-shadow: none; height: 40px; font-size: 16px;" /></li><div class="verification-code"><img style="vertical-align: middle;" id="checkimg" class="checkImg" alt="" src='checkImg.html' onclick="change()" title="点击更换验证码" /></div></div><div class="center"><input type="submit" name="sure" value="登 录" id="sure" type="button" class="btn btn-purple btn-login" style="width: 280px;" /></div></div>6.点击onclick事件时会触发change事件,所以写这样一个方法change()。<script language="javascript" type="text/javascript"> function change() { var img1 = document.getElementById("checkimg");//${pageContext.request.contextPath}/ img1.src = "checkImg.html?" + new Date().getTime(); //加时间戳防止缓存 }</script>7.利用js添加表单验证: $(function() { $("#sure").on("click", function(e) { var codenum=$("#codenum").val(); var password=$("#password").val(); var checkImg=$("#checkImg").val(); if(codenum==""){ alert("请输入用户名"); $("#codenum").focus(); return false; }else if(pass==""){ alert("请输入密码"); $("#password").focus(); return false; }else if(checkImg==""){ alert("请输入验证码"); $("#checkImg").focus(); return false; } });8.在action中添加接收验证码的方法------->LoginAction.javaprivate String checkImg;//接收验证码 public String getCheckImg() { return checkImg; } public void setCheckImg(String checkImg) { this.checkImg = checkImg; }9.接着就是验证session中存的验证码和登入输入的验证码是否一致。------->LoginAction.javapublic String login() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); String codenum = request.getParameter("codenum"); String password = request.getParameter("password"); //从Session中获得验证码的随机值 String checkImg1 =(String)ServletActionContext.getRequest().getSession().getAttribute("checkImg"); User user = userDao.selectUserbByusernameByPassword(codenum, password); if(checkImg.equalsIgnoreCase(checkImg1)){ if (user != null) { //验证登入成功 return "success"; } else { request.setAttribute("errorMessage", "不存在此用户,请重新输入帐号密码!!"); return "fail"; } }else{ request.setAttribute("errorMessage", "验证码输入错误,请重新输入!!"); return "fail"; } }
闲话不多说,直接放代码!喜欢的小伙伴不妨点个赞加个关注哟,您的支持就是给怂怂最大的鼓励呢!<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><script type="text/javascript"> var detail_div = 1; function add_div() { var e = document.getElementById("details"); var div = document.createElement("div"); div.className = "form-group"; div.id = "details" + detail_div; div.innerHTML = e.innerHTML; document.getElementById("form").appendChild(div); detail_div++; } function del_div() { if(detail_div >1){ var id = "details" + (detail_div - 1).toString(); var e = document.getElementById(id); document.getElementById("form").removeChild(e); detail_div--; } } function number(){ var number = document.getElementById("number"); var value = number.value; //如果文本值为空,设置为1 if (value=="") { number.value = 1; } //如果文本值为非纯数字,设置为1 //isNaN()是否为非法数字 if (isNaN(value)) { number.value = 1; } //如果文本值小于1,设置为1 if (parseInt(value)<=1) { number.value = 1; } }</script><body> <form id="form" role="form" method="post" class="formBuilder"> <div class="form-inline"> <label for="details" >演示</label> <button type="button" id="add-btn" onclick="add_div()">+</button>&nbsp;&nbsp;&nbsp; <button type="button" id="del-btn" onclick="del_div()">-</button> </div> <div id="details"> <div class="form-inline"> <label for="receivable" >选项</label> Label:<input id="receivable" onBlur="number()" /> value:<input id="receivable1" onBlur="number()" /> </div> </div> </form></body></html>代码演示效果,如下:
1、这个主要是在 cmd 下输入 java -version来查看,如果没有标明是多少位的,默认一般是32位的。我的是64位,请看,具体是有显示;2、看你在oracle官网下载的jdk文件原名:刚下载的JDK文件名后面标注了x64代表是64位的JDK,若没有标注,则都是32位的JDK(必须保证是官网下载的文件原名哦);当然此只是官网下载的文件,不包括手动更改的文件名。比如:jdk-8u102-windows-i586是什么32位还是64位?没有标注x64,就说明是32位的。64位后面都会带x64标识;不带就是32位的;
第一种情况:Java环境没有配置好这里就不做仔细说明了,详情可见百度Path配置:CLASSPATH配置:检查完后检测是否安装正确;WINDOWS+R键,输入cmd,进入命令行界面1、输入java -version命令,可以出现如下图的提示,你可以看你安装的JDK版本。2、输入javac命令3、输入java命令第二种情况:javaw.exe路径缺失因为Eclipse需要javaw.exe来启动,程序会先查找path目录,如果没有找到,这会在eclipse的安装目录下查找,再找不到就会报如上的错误。所以可以肯定的就是路径出问题了。如图:eclipse启动时提示A java runtime Environment(JRE) or java Development找到eclipse的安装目录,找出eclipse.ini文件并打开:打开eclipse.ini文件,因为有的eclipse配置文件里面没有-vm配置,需要手动增加,如下:将-vm添加到openFile的下一行;然后-vm下添加你jre下bin的路径,-vm 下面的路径盘符前面不要动,一般打开自己安装好的jre路径直接复制全路径粘贴就好了,这是我安装jre的全路径 C:\java\jre\bin;如图所示操作;切记,修改之后,一定要记得保存!!!java环境变量配置正确修改文件记得保存之后就完美解决eclipse打不开的问题了
今天,有很多小伙伴被何为内网外网迷糊?究竟什么是内网?什么是外网?他们又有和区别?还有什么是内网IP和外网IP?本地连接和宽带连接有什么区别?怂怂今天就来给大家科普一下吧:问题一、何为内外网及定义诠释:内网就是局域网(Local Area Network,LAN)是指在某一区域内由多台计算机互联成的计算机组。外网就是广域网。广域网(英语:Wide Area Network,缩写为 WAN),又称外网、公网。是连接不同地区局域网或城域网计算机通信的远程网。简单的说,自己的单位或者家庭、小区内部有局域网;单位、家庭之外有覆盖范围极大的网络,比如internet,这个大网络延伸到了我们的单位、家庭(通过光纤、网线、电话线等)。我们把自己的局域网连接到internet上,那么我们的访问范围就从局域网扩展到了整个internet。这时候,就说局域网是内网,internet是外网。同理,如果你们单位的局域网很庞大,而你的办公室里面的几台电脑组成的小局域网又连接到单位的整个大局域网,那么也可以说单位的大局域网是外网,办公室内的小局域网是内网。同时,如果单位的大局域网连接了Internet,那么相对于Internet,也可以说单位的大局域网是内网。内网可能是一个独立的局域网,通过其中的网关(网关就是连接两个网络的节点,说白了,就是有双重身份的电脑,既有局域网的IP地址,又有Internet的IP地址,两个IP地址分别捆绑在不同的网卡上)的代理访问外部网络,比如网吧都是这样实现的,其特征是:网吧内的电脑的ip都是局域网专用ip,比如192.168.xxx.xxx或者10.xxx.xxx.xxx,而这种ip在internet上面是不会出现的。(注:所谓代理,就是你提要求,他来办事,类似于代购火车票。局域网的电脑想和外面联络,就把对方地址告诉服务器,也就是网关,网关以自己的身份和对方联络,同时把对方发回来的消息转送给局域网内的电脑。因此,对方看不见局域网内电脑的IP,只会以为是网关那台电脑在与自己交流。网吧内的所有QQ都显示同样的IP,现在你能理解为什么了吗?)内网也可能是外网的一个部分,比如校园网,或者相对于单位局域网的办公室内部局域网。其特征是:内网电脑的ip就是整个外网ip范围的一部分,内网的电脑通过网关(路由器)连接到外网,网关不需要进行代理服务,直接路由就行了。(注:所谓路由,就是路径选择。路由器连接多个网络,因此一定是各个网络的网关,其作用类似于邮局。你想联系局域网外的电脑,就把邮包发送给路由器,路由器会帮你投递到邮包上标明的地址。这样,收到邮包的人可以知道是谁把邮包发过来的,但是,他无法知道发邮包的人是不是帮别人代理发邮包的。或者说,收到数据的电脑可以知道是哪台电脑在与它联络,但它无法知道与它联络的这台电脑是否是某个局域网的代理服务器。)adsl比较特殊,它有两种工作方式。第一种,adsl的modem打开代理功能,这时候,modem实际上就可以看作一台电脑,它是internet(外网)的一个节点,同时,它与你的电脑连接成为局域网,也就是内网,内网网关就是modem。第二种,通过电脑进行拨号上网,这种情况下,modem就是电脑的一个外部设备,而你的电脑通过电话线直接连接在internet上,不存在其它网络,因此也就无所谓内网外网。问题二:什么是广域网(WAN、公网、外网),什么是局域网(LAN、私网、内网)?广域网(WAN),就是我们通常所说的Internet,它是一个遍及全世界的网络。局域网(LAN),相对于广域网(WAN)而言,主要是指在小范围内的计算机互联网络。这个“小范围”可以是一个家庭,一所学校,一家公司,或者是一个政府部门。BT中常常提到的公网、外网,即广域网(WAN);BT中常常提到私网、内网,即局域网(LAN)。广域网上的每一台电脑(或其他网络设备)都有一个或多个广域网IP地址(或者说公网、外网IP地址),广域网IP地址一般要到ISP处交费之后才能申请到,广域网IP地址不能重复;局域网(LAN)上的每一台电脑(或其他网络设备)都有一个或多个局域网IP地址(或者说私网、内网IP地址),局域网IP地址是局域网内部分配的,不同局域网的IP地址可以重复,不会相互影响。广域网(WAN、公网、外网)与局域网(LAN、私网、内网)电脑交换数据要通过路由器或网关的NAT(网络地址转换)进行。一般说来,局域网(LAN、私网、内网)内电脑发起的对外连接请求,路由器或网关都不会加以阻拦,但来自广域网对局域网内电脑电脑连接的请求,路由器或网关在绝大多数情况下都会进行拦截。通常情况下,网关或路由器对内部向外发出的信息不会进行拦截,但对来自外部想进入内部网络的信息则会进行识别、筛选,认为是安全的、有效的,才会转发给内网电脑。正是这种情况的存在,才导致了很多内网用户没有“远程”,速度也不尽如人意。内外网IP问题三:什么是内网Ip、外网IP?内网ip地址也就是局域网,内网的计算机以NAT(网络地址转换)协议,通过一个公共的网关访问Internet。内网的计算机可向Internet上的其他计算机发送连接请求,但Internet上其他的计算机无法向内网的计算机发送连接请求。外网IP地址指的是:打开ADSL路由功能的用户你的外网IP就应该是ADSL设备的IP,网吧里的外网IP是指整个网吧的主IP,校园网的外网IP就是整个校园网的那个主IP,小区网的外网IP与校园网同理,长宽的用户就要试下了,可以上论坛,看看你的IP是多少,那么那个IP就是你要绑定的。以下IP是内网IP的类型:10.0.0.0~10.255.255.255172.16.0.0~172.31.255.255192.168.0.0~192.168.255.255一般的人都不能拥有外网IP,因为一个人用外网IP简直是太浪费了。所以我们都是通过内网去上网的。外网Ip一般都是用于公司企业,学校等机构的。那外网IP都有哪些呢?其实除了内网IP之外的IP就是外网IP了。问题四:本地连接和宽带连接有什么区别?1、含义不同本地连接(local connection)是指电脑中不同网络创建的链接,当创建家庭或小型办公网络时,运行 Windows 的计算机将连接到局域网 (LAN)。安装 Windows 时,检测网络适配器,自动创建本地连接。本地连接是唯一自动创建并激活的连接类型。以拨号上网速率上限56KBps为界,低于56KBps称为“窄带”,以上称为“宽带”。宽带连接对家庭用户而言是指传输速率超过1M,可以满足语音、图像等大量信息传递的上网连接方式,包括光纤,xDSL(ADSl,HDSL),ISDN等。2、IP不同本地连接的IP可以是自己设置,也可以是内部网络设置自动分配的。本地连接的ip是以路由器作为网关,在这个路由器局域网内为区分局域网内的不同电脑而设定的ip,这个IP只是个局域网IP。宽带连接IP是网络IP地址,是ISP服务器分配的外网IP。连接时临时分配宽带连接IP,每台上网的机器都有一个外网IP。访问网站网站收到的是宽带IP。那个IP是宽带IP,是不固定的。只有连接成功才能获得一个IP。3、使用限制不同本地连接插上网线就可以使用,宽带连接必须拨号才能使用。宽带上网的话首先本地连接(也就是电脑要有网卡,本地连接要连接好)再拨号,拨号成功才能上网。扩展资料:外网IP地址查询法:①最直接的查询外网IP地址的方法,就是在百度搜索引擎中输入关键词:IP地址点击百度一下按钮(保持联网),即可自动显示电脑外网IP的地址。②很多软件也有这样的查询功能,比如遨游浏览器,打开之后,右下方就有查询内网和公网的菜单,点击一下,即可显示两个IP地址,很是方便。小伙伴们,你们看明白了嘛?如若有啥解释的不到位或者有瑕疵的地方,还请多多提出,一起进步!
使用struts2中的文件上传与下载功能,需要先导入两个jar文件,一个是commons-fileupload.jar,另一个是commons-io.jar,如下:struts2 实现excel上传:<jsp页面><form method="post" id="uploadForm"><input type="file" id="file" name="file"/><input type="button" onclick="ajax('${pageContext.request.contextPath}/upload/excel.html')" value="开始上传"></form><js引用>function ajax(url){ var file=document.getElementById('file').files[0]; if(file==undefined){ alert('请选择文件'); return } var data = new FormData(); data.append("file", file); $.ajax({ type: 'post', url: url, data: data, cache: false, processData: false, contentType: false, success: function (data) { alert(data.msg); }, error: function () { alert("上传失败"); }, });}<工具类util>public class ExcelUtils { public static List<Map<String,Object>> excelToMap(Map<String,Object> map, File file){ String fileType =file.getName().substring(file.getName().lastIndexOf(".") + 1); Workbook workbook = null; try { if (fileType.equals("xls")) { workbook = new HSSFWorkbook(new FileInputStream(file)); } else { workbook = new XSSFWorkbook(new FileInputStream(file)); } } catch (IOException e) { e.printStackTrace(); } //开始读取文件 Sheet sheet=workbook.getSheetAt(0); //读取列头 Row titleRow=sheet.getRow(0); Map<String,Integer> keyMap=new HashMap<>(); for(int i=0;i<=titleRow.getLastCellNum();i++){ String cellText=transString(titleRow.getCell(i)); if(map.containsKey(cellText)){ String key=map.get(cellText).toString(); keyMap.put(key,i); } } List<Map<String,Object>> result=new ArrayList<>(); for(int i=sheet.getFirstRowNum()+1;i<sheet.getPhysicalNumberOfRows();i++){ Map<String,Object> temp=new HashMap<>(); for(Iterator<String> iterator=keyMap.keySet().iterator();iterator.hasNext();){ String key=iterator.next().toString(); temp.put(key,transString(sheet.getRow(i).getCell(keyMap.get(key)))); } result.add(temp); } return result; } public static <T> List<T> excelToBean(Class clazz,Map<String,Object> map, File file){ List<Map<String,Object>> list=excelToMap(map,file ); Field[] fields=clazz.getDeclaredFields(); List<T> result=new ArrayList<>(); for(Map<String,Object> temp:list) { try { Object obj=clazz.newInstance(); for (Field field : fields) { String fieldName = field.getName(); if (temp.containsKey(fieldName)){ field.setAccessible(true); String dataType=field.getType().getTypeName(); Object value=temp.get(fieldName); if(value!=null){ if(dataType.equals("java.lang.String")){ field.set(obj,value.toString()); }else if(dataType.equals("java.lang.int")){ field.set(obj,Integer.valueOf(value.toString())); } } } } result.add((T)obj); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } return result; } public static String transString(Cell cell){ String value; try { value=cell.toString(); if(value.indexOf(".")>-1){ DecimalFormat df = new DecimalFormat("#");//转换成整型 value=df.format(cell.getNumericCellValue()); } }catch (Exception e){ value="空"; } return value; }}<UploadAction文件>public class UploadAction extends ActionSupport { private static final long serialVersionUID = 1L; private UserDao userDao; private TeacherDao teacherDao; private StudentDao studentDao; private File file; /** *省略实体类的get set方法 */ // 执行上传功能 public void upload() { int countIng=0; int endCount=0; int startCount=0; // excel列名与数据model的对应关系映射 // key为excel列名value为实体名称(例如学生Excel表中的列名学生对于User实体中的username) // 填写对应关系,如果对应不上,不会填充对应对象 try { Map<String, Object> map = new HashMap<String, Object>(); map.put("姓名", "username"); map.put("学号", "codenum"); map.put("班级编号", "banjinum"); map.put("角色", "role"); map.put("是否禁用", "userlock"); // 返回excel中的数据,以列表形式返回,并封装内部转换,自动将对象转换填充,需要使用泛型填充 如User // 方法的第一个参数是泛型的类型 如User的类是User.class List<User> list = ExcelUtils.excelToBean(User.class, map, getFile()); // 返回的是一个列表,可以将列表中的数据保存到数据库等 startCount=list.size(); List<User> date = userDao.getAllUser(); //下面这种写法,实例化就是不支持,具体不知道是什么原因 /*List<String> hasUser = date.stream().map(x->x.getCodenum()).collect(Collectors.toList());*/ List<String> hasUser=new ArrayList<String>(); for(User user:date){ hasUser.add(user.getCodenum()); } for (User user : list) { if (hasUser.contains(user.getCodenum())) { // 学号已存在 countIng++; } else { userDao.insertUser(user); // 联动添加到对应的techer表与student表中 if (user.getRole().equals("3")) { Teacher teacher = new Teacher(); teacher.setName(user.getUsername()); teacher.setClassid(user.getCodenum()); teacherDao.insertTea(teacher); } else if (user.getRole().equals("2") || user.getRole().equals("4")) { Student stu = new Student(); stu.setClassnum(user.getCodenum()); stu.setRole(user.getRole()); stu.setName(user.getUsername()); studentDao.insertStu(stu); } } } } catch (Exception e) { Util.ajaxResponse(500, "导入失败"); } if(countIng>0){ endCount=startCount-countIng; Util.ajaxResponse(200, "导入失败!数据共有"+countIng+"条重复,成功录入"+endCount+"条!"); } else{ Util.ajaxResponse(200, "导入成功!录入"+startCount+"条"); } }}<struts-upload.xml配置文件><struts> <package name="manage_upload" namespace="/upload" extends="struts-default"> <action name="excel" method="upload" class="manage.action.UploadAction" /> </package></struts>
1、如果第一次用Navicat,则需自己创建一个本地数据库,自己创建一个数据库【演示一下mysql的创建方式,其他的数据库也是同样此操作】2、连接名也就是数据库名,自己随便起,用户名跟密码是你在安装Navicat的时候配置过的,必须跟安装的用户名跟密码一样,即可3、填写完正确的用户名跟密码,点击下方的测试连接,如果显示连接成功,则表示创建数据库成功!4、如果如下显示,字面意思,填写的用户名或者密码跟你安装Navicat配置的用户名跟密码不一致导致的问题。如果密码忘了,那只能把Navicat卸载了重新安装一遍;一般默认安装的话,密码是为空,用户名为root,不妨试试看碰碰运气。5、创建完了本地数据库,选择本地数据库单击右键选择运行sql文件6、选择本地数据库文件(带.sql的后缀),直接打开,即可;7、如图所示,我的就成功运行配置文件。小伙伴们,你们看懂了嘛?每天都要进步一点点哦
一、前言Springboot默认是不支持JSP的,默认使用thymeleaf模板引擎。所以这里介绍一下springboot结合Thymeleaf,实现模板实例以及途中遇到的问题。二、配置与使用1.引入jar。 在pom中加入thymeleaf对应的starter 依赖。<!--模板引擎Thymeleaf--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>2.在配置文件(application-dev.yml)中配置Thymeleaf模板参数。#spring配置 spring: thymeleaf: cache: false mode: LEGACYHTML5 prefix: classpath:/templates/ suffix: .html重要参数解说:cache: 是否缓存,开发模式下设置为false,避免改了模板还要重启服务器,线上设置为true,可以提高性能。一般改为false。mode:配置视图模板类型,如果使用html5需要配置成html5。prefix:指定模板所在的目录。suffix: 模板后缀。3.编写freemarker模板文件:<!DOCTYPE HTML><html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"><head> <meta content="text/html;charset=UTF-8"/></head><body><h6>Thymeleaf 模板引擎</h6><table border="1" bgcolor="#f0ffff"> <thead> <tr> <th>序号</th> <th>日志内容</th> <th>ip号</th> <th>创建时间</th> </tr> </thead><!--th:each表示循环遍历,和Vue一样--><tbody th:each="logList : ${list}"><tr> <td th:text="${logList .id}"></td> <td th:text="${logList .content}"></td> <td th:text="${logList .ip}"></td> <td th:text="${logList .createTime}"></td></tr></tbody></table></body></html>附上:Thymeleaf语法:看官方文档是很好的一种学习方式,这里我找了一个pdf的Thymeleaf文档。Thymeleaf官方文档pdf版下载链接:www.thymeleaf.org/doc/tutoria…看一下Thymeleaf的使用语法:4、接下来编写一个Controller,并放一些数据进去。package com.system.xiaoma.controller; import com.system.xiaoma.entity.LogInfo; import com.system.xiaoma.service.ILogInfoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; /** * 日志模板页面 */ @Controller @RequestMapping("/page") public class ThymeleafController { @Autowired private ILogInfoService logInfoService; @RequestMapping("/logList.html") public String getArticles(Model model) { //获取日志列表 List<LogInfo> list = logInfoService.list(); //存入数据 model.addAttribute("list", list); return "logList"; } }注意:1、其中,你所return返回的是相对路径,比如你的模板路径于resources/templates/logList.html上;由于你在application-dev.yml已配置了根路径,即你只需要返回相对路径即可。2、注意:只能使用注解是@Controller;这里若是直接使用@RestController,会自动将返回结果转为字符串。如下给大家演示一下,如果使用@RestController注解,该页面会如何展示?展示效果:果真是将return 返回的字符串给打印出来了。而使用@Controller正确示范结果页,如下,表示成功取出数据并渲染展示。三.注意事项:注意可能途中会遇到此类问题:500错Error resolving template [log/logList], template might not exist or might not be accessible by any of the configured Template Resolvers其实很好理解,就是说你访问的静态模板页面路径不对,或者静态页面有语法错误,模板解析器无法访问;解决方案:第一步:检查 application-dev.yml文件对于页面模板的配置对不对,在resources下面创建templates文件夹,页面放于里面。第二步:在controller里;直接返回“/logList”即可。好啦,以上就是Springboot配置Thymeleaf实现静态页面访问 的全部内容啦,咱们下期再见。
大家好,我是bug菌~~Hi ,小伙伴们,我们又见面啦!先给大家说声抱歉,距离上一篇发文已经时隔快两个月了,至于为什么不及时更文了呢?有小伙伴可能就会猜,要么是太忙要么就是转移到其他的博客上了...都不是啦,主要是自己的工作出了一点问题,导致我搁置了更文这件事,至于问题详细具体,此处就不便透露啦。好啦,话不多说,咱们就开始今天的活儿吧。今天,我主要是想简单介绍一下乐观锁的实现及在什么场景下该使用乐观锁。那么,今天的第一个问题,什么是乐观锁呢?一、概念:乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。在Java中java.util.concurrent.atomic包下面的原子变量类{AtomicInteger}就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。比如:看下边这张图,辅助你理解。乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。二、实现方式1、版本号机制一般是在数据表中加上一个版本号version字段,表示数据被修改的次数,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行 +1 操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题。乐观锁实现过程:取出记录时:获取当前version更新时:带上这个version执行更新时:set version = newVersion where version = oldVersion如果version不相等,更新失败;如下具体介绍使用如何使用mybatis-plus 实现乐观锁1、数据库表加字段:int version2、实体该字段加@Version 注解3、注册乐观锁插件/** * MybatisPlusConfig配置 * * @Author luoYong * @Date 2021-08-10 21:41 */@Configuration@MapperScan("com.system.xiaoma.dao")public class MybatisPlusConfig { /** * 注册乐观锁插件 */ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); }}4、test-case测试用户数据更新成功,version也+1以上就是通过mybatis-plus 组件实现乐观锁机制;2、CAS算法即compare and swap(比较和互换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。CAS算法涉及到3个操作数。1、需要读写的内存值V2、进行比较的值A3、拟写入的新值B当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。但是这样的CAS机制会带来一个比较常见的问题。那就是ABA问题。举个例子,你看到桌子上有100块钱,然后你去干其他事了,回来之后看到桌子上依然是100块钱,你就认为这100块没人动过,其实在你走的那段时间,别人已经拿走了100块,后来又还回来了。这就是ABA问题。三、如何选择在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。响应效率:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被应用到生产环境中了,尤其是并发量比较大的业务场景。往期热文推荐:在Ubuntu上使用IDEA搞开发是种什么体验?没想到竟是...最后有惊喜!520夜我花了288大洋就得到了小师妹青睐,原因竟是...一定要看到最后!你一定没用过的代码生成工具,好不好用你们说了算如何实现Springboot项目保存本地系统日志文件,超详细,你值得拥有!使用MultipartFile实现图片指定路径上传下载并使用postman测试教程 | 附完整源码,强烈建议收藏!一篇文带你零基础玩转mysql触发器 | 超级干货,建议收藏...OK,今天的文章先写到这。如果问题还请批评指正。❤如果文章对您有所帮助,就请在文章末尾的左下角把大拇指点亮吧!(#^.^#);❤如果喜欢bug菌分享的文章,就请给bug菌点个关注吧!(๑′ᴗ‵๑)づ╭❤~;❤对文章有任何问题欢迎小伙伴们下方留言或者入群探讨【群号:708072830】;❤鉴于个人经验有限,所有观点及技术研点,如有异议,请直接回复参与讨论(请勿发表攻击言论,谢谢);❤版权声明:本文为博主原创文章,转载请附上原文出处链接和本文声明,版权所有,盗版必究!(*^▽^*).
2022年05月