关于springboot:spring-boot-文件上传和下载

学习了老师写的文件上传和下载,这里学习一下。 文件上传controller层从controller层开始,承受前台传的数据。 1.前台调用后盾接口: /** * 上传文件 * @param file 文件 */ upload(file: File): Observable<HttpEvent<Attachment>> { const formData: FormData = new FormData(); formData.append('file', file); return this.httpClient.post<Attachment>(`${this.url}/upload`, formData, {reportProgress: true, observe: 'events'}); }2.controller层接收数据:/** * 上传文件 * * @param multipartFile 附件 * @return 上传附件后果 */ @PostMapping("upload") @JsonView(UploadJsonView.class) public Attachment upload(@RequestParam("file") MultipartFile multipartFile) throws Exception { return this.attachmentService.upload(multipartFile); }能够看到,后盾承受文件是以 MultipartFile 类型来接管. MultipartFile其实是一个接口,其中定义提供了上传文件的各方面信息 MultipartFile 办法阐明: 返回值类型办法阐明byte[] getBytes()将文件内容转化成一个byte[] 返回String getContentType()返回文件的内容类型InputstreamgetInputStream() 返回InputStream读取文件的内容String getOriginalFilename()返回文件的名称Long getSize()返回文件大小 以字节为单位BooleanisEmpty() 判断是否为空,或者上传的文件是否有内容Void transferTo(File dest)用来把 MultipartFile 转换换成 File有了下面提供的函数,咱们就能够很轻松的操作它。 ...

October 12, 2022 · 2 min · jiezi

关于springboot:从SpringBoot启动阅读源码设计

服务启动堪称Spring源码设计的答案;一、背景阐明初学SpringBoot框架时,第一次启动服务,直呼什么鬼?只须要简略的几步配置,几个外围的注解,就能够疾速实现工程的搭建和运行; 尽管从Spring框架迁徙到SpringBoot框架,在初期会有很多的不适应,然而更好用的框架会疾速失去认可,从而成为支流的技术选型; 对于大多数的框架或者组件来说,如果应用起来越是简便,那么其外部的封装策略就越是简单; 比方在Spring框架更新到SpringBoot版本时,其用法的简便与外部封装的复杂性曾经造成强烈的比照;再到SpringCloud微服务框架时,其封装逻辑简单到离谱; 对于服务端的开发来说,绕不开对Spring框架的深度学习,如果单纯站在源码浏览的角度,倡议先熟读SpringBoot启动流程,而后再适当扩大其余源码块; 二、SpringBoot工程首先聊一聊浏览源码的基本思路,从一个极简的案例开始,围绕案例中的外围API作为切入点,通过对源码逻辑的断点调试,从而领会其设计的原理; 浏览SpringBoot的源码,能够从服务启动办法作为切入点,而后一直的剖析启动过程波及到的外围API和设计原理,再基于具体的启动日志去剖析形象的加载逻辑; 在看具体的源码之前,还须要说下剖析思路,Spring我的项目中,要留神每个API所属工程与层级,而后再去剖析API之间关系,外围的结构、属性、办法等; 在SpringBoot的启动类中,有两个外围的切入点,一个是类的构造方法,实现一列的初始化动作;一个是启动办法,实现利用上下文的创立和装载; 构造方法: 启动办法: 须要阐明的是,因为SpringBoot服务启动过程波及源码过多,所以下面的源码中只是列举局部的外围切入点,而后围绕这些要害流程开展,剖析一些常见的源码设计; 另外阐明一点,以下源码的外围版本:JDK-1.8,spring-5.2.4,spring-boot-2.2.5,在不同的版本下源码会存在差别; 三、利用上下文服务启动时,依据利用类型判断创立的上下文,此处启动的是基于servlet的web利用,所以也依赖相应的web服务器,默认为Tomcat; 启动办法的外围在于对利用上下文的创立、筹备、刷新,利用上下文是一个非常形象的形容,能够了解为利用运行的整体环境,其中波及到资源加载,配置文件拆卸,运行服务的治理等,后续的源码剖析都围绕该API开展; ApplicationContext:利用上下文外围接口,在该接口中所有的办法都是只读模式,即只能通过Get办法进行拜访; ConfigurableApplicationContext:上下文配置扩大接口,提供了利用上下文的配置能力,生命周期的保护,以及在敞开之后的相干资源开释; AbstractApplicationContext:上下文接口形象实现,外围的API,对利用上下文中的公共能力做了实现; ConfigurableWebApplicationContext:Web利用上下文配置扩大接口,提供了Web利用的上下文配置能力; WebServerApplicationContext:Web服务上下文,创立并治理Web利用的服务器,在该流程中嵌入的是Tomcat服务; 依据利用上下文几个外围的API设计,领会Spring源码的设计思路,从顶级的接口开始,一直向下扩大并且新增办法,了解形象实现类的逻辑,以及服务运行时所依赖的具体API; 四、资源加载什么是资源,能够是各种类型的文件和配置,字节输出流的转换,也能够是URL资源定位,Spring框架在运行的过程中,须要依赖Resource接口实现对底层资源的拜访; Resource:资源形容的顶级接口,提供了一系列的办法,继承InputStreamSource接口,反对将资源转换为流的模式操作; AbstractResource:资源拜访的形象实现类,这里的设计原理与AbstractApplicationContext相似,提供资源拜访办法的根底实现; ResourceLoader:资源加载的封装接口,利用下文须要依赖该接口实现资源的获取与拜访; 针对不同利用场景需要,Resource接口的实现类有如下几个:FileSystemResource文件系统资源,ClassPathResource类门路下资源,InputStreamResource输出流资源等; 五、应用环境对于Property和Environment源码设计体系,参考上述的源码模块,在思路上是类似的,此处不多形容; 应用程序的属性和环境波及到的参数形容十分多,比拟间接的伎俩是通过System类中的办法输入,至于信息如何加载,在StandardEnvironment类中提供了办法,能够断点查看; 六、Bean对象基于Spring框架的应用程序中,由Spring容器负责创立,拆卸,设置属性,进而治理整个生命周期的对象,称为Bean对象;Bean的生命周期非常复杂,过程大抵如下:实例化,属性加载,初始化前后治理,销毁; BeanFactory:工厂类,Spring框架的外围能力,Bean容器的顶级接口,提供了一系列Bean对象的拜访办法,是IOC思维和依赖注入的根底撑持; ConfigurableBeanFactory:Bean容器可配置化接口,该扩大接口只是为了容许框架外部的即插即用和拜访bean工厂的配置办法; AbstractBeanFactory:Bean治理的形象实现类,能够查看其外部doGetBean办法,提供Bean实例对象的获取逻辑,如果无奈获取则执行创立逻辑; 七、Tomcat服务首次启动SpringBoot工程时,最大的疑难就是可见Tomcat启动日志,然而没有显式的做服务器拆卸,间接启动JAR包即可,这在流程上简化了一大步; WebServer:Web应用服务器接口,比方罕用的Tomcat,Jetty,Netty等,依据利用类型抉择,只提供了启动、进行、获取端口三个办法,通过WebServerApplicationContext与利用上下文相关联; TomcatWebServer:SpringBoot框架治理内置Tomcat服务的外围类,对Tomcat生命周期的治理提供了一层包装; Tomcat:Apache组件中轻量级Tomcat启动器,提供了Tomcat根底配置,比方默认的Port和HostName,以及生命周期治理的办法,TomcatWebServer类中调用的就是该API中的具体方法; 八、事件模型事件驱动模型是简单流程中的罕用解耦伎俩,即通过事件发送和监听两个拆解动作,实现流程的分步执行,这在SpringBoot启动流程和上下文装载中更是施展的酣畅淋漓; ApplicationEvent:利用事件根底抽象类,继承自JDK中EventObject类,具体事件会继承该类,外部申明了事件源和产生工夫两个外围属性; ApplicationEventMulticaster:利用事件播送的顶级接口,能够将指定的利用事件播送给适宜的监听器; SimpleApplicationEventMulticaster:利用事件播送接口的简略实现,能够断点该类的multicastEvent办法,查看播送时利用事件和其相应的监听器; ApplicationListener:利用事件监听器接口,继承自JDK中EventListener接口,Spring中扩大了多种具体的事件监听器,以实现各种不同的场景需要,比方最常见的ConfigFileApplicationListener配置文件监听器; 九、配置加载SpringBoot工程中,配置文件的管理策略非常复杂,有外部程序执行加载配置,也有内部集成的组件配置,当然最外围的就是工程的自定义配置; ConfigFileApplicationListener.Loader:配置文件监听器的外部类,实现对工程中的配置源加载,其外围逻辑在Loader.load办法中实现,具体逻辑由相干的实现类实现; PropertySourceLoader:配置加载的策略接口,在Spring工程中反对多种类型的文件配置,比方yml、yaml、properties、xml,须要通过文件的扩展名抉择相应的加载实现类; YamlPropertySourceLoader:加载.yml或者.yaml类型的文件,SpringBoot工程中罕用的配置文件类型,最终转换成Name和Value的属性源汇合,即通过PropertySource抽象类来形容; 十、数据库集成Spring框架的弱小之处还在于可能和其余组件进行简略疾速的集成,比方罕用的数据库、缓存、音讯队列等各种类型的组件,剖析外部的集成逻辑,会发现很多原理上的相似性,尤其在SpringBoot框架中,约定大于配置; DataSourceAutoConfiguration:SpringBoot工程中数据库的自动化配置类,在配置中Hikari是默认抉择的连接池,也是号称速度最快的; DataSourceProperties:数据源配置相干的根底类,在DataSourceConfiguration配置类中,会基于参数去创立数据源对象; HikariDataSource:Hikari连接池组件中的数据源API,形容数据源的具体信息,例如配置、连接池、状态等,具体的数据库连贯逻辑是在该组件外部实现的; 基于SpringBoot集成数据库的原理,能够扩展性的看看:Redis组件的RedisAutoConfiguration配置类;Kafka组件的KafkaAutoConfiguration配置类,Elasticsearch组件的RestClientAutoConfiguration配置类,在设计原理上都有殊途同归之妙; 写在最初 从集体教训来看,想要浏览Spring框架的源码设计,须要基于利用流程先构建一个大的轮廓构造,了解设计中的罕用策略和原理,而后再深刻单个模块的细节逻辑,这样容易找到浏览节奏; 本文并没有波及源码中过多的细节逻辑,只是从服务启动作为切入点,整顿与开发关联性较为间接的源码模块,形容集体对于Spring源码浏览的根底思路。 十一、参考源码利用仓库:https://gitee.com/cicadasmile/butte-flyer-parent组件封装:https://gitee.com/cicadasmile/butte-frame-parent

October 10, 2022 · 1 min · jiezi

关于springboot:spring-boot整合swagger

依赖 <!--swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--swagger-ui.html模式 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!--doc.html模式 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>swagger-bootstrap-ui</artifactId> <version>1.9.2</version> </dependency>配置端口号server.port=1313#启动会报错(起因:Springfox应用的门路匹配基于AntPathMatcher,而Spring Boot 2.6.X应用的是PathPatternMatcher。)#org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerExceptionspring.mvc.pathmatch.matching-strategy=ant_path_matcher启动类增加注解( @EnableSwagger2 )@EnableSwagger2@SpringBootApplicationpublic class JavaApplication { public static void main(String[] args) { SpringApplication.run(JavaApplication.class, args); }}注解介绍/** * Api注解:定义接口名称 * ApiOperation注解:定义方法名称 * ApiImplicitParam注解:定义param参数的各个属性 * * ApiModel注解:定义对象名称 * ApiModelProperty注解:定义参数名称 */@Api(tags = "测试接口")@RestControllerpublic class TestController { @ApiOperation(value = "测试01") @GetMapping("/test01") public String Test01(){ return "Test01"; }}拜访地址:http://localhost:1313/swagger...或者http://localhost:1313/doc.html ...

October 8, 2022 · 1 min · jiezi

关于springboot:jpa-自动更新遇到的问题

JPA自动更新示例代码: 1 DivisionalWorksType oldEntity this.divisionalWorksTypeRepository.findById(id);2 List<ScoringItemTemplate> oldList = oldEntity.getScoringItemTemplates();3 oldList.remove(0);调用了 Repository 的 findById 办法。 并对其属性的数组进行了删除。 起初测试的时候,总发现该删除操作保留到了数据库中。 而并没有执行 Repository 的 save 函数。 最初发现是因为JPA的自动更新 Hibernate文档中为Hibernate对象定义了四种状态,别离是: 刹时态(new, or transient)长久态(managed, or persistent)游状态(detached)和移除态(remove)因为当实体对象属于托管状态下时,往这个对象外面的某个属性set新的值,这个新的值会被自动更新到数据表中去。(JPA自带的个性) 所以这就是咱们没有执行save函数,数据库也会更新的起因。 解决: // 应用new ArrayList 创立, 若间接应用该数据,会因为jpa自动更新而批改实体数据List<ScoringItemTemplate> oldList = new ArrayList<>(oldEntity.getScoringItemTemplates());应用new ArrayList, 复制一份数据就能够了。

October 5, 2022 · 1 min · jiezi

关于springboot:如何查询已经执行过的流程信息

上篇文章和小伙伴们分享的流程操作次要是正在执行的流程,咱们有一个十分常见的场景是查问执行过的流程信息,在上篇文章中,小伙伴们曾经晓得,对于正在执行的流程,会在 ACT_RU_EXECUTION 表中保留一条对应的记录,不过流程执行完结之后,ACT_RU_EXECUTION 表中的记录会被删除掉,此时要是想查问曾经执行过的流程信息,去哪里查问呢?明天咱们就来看看 HistoryService 的应用。 本文应用的流程图仍然是上篇文章中应用过的,如下: 1. 查问历史流程查问历史流程的形式很简答,如下: @SpringBootTestpublic class HiTest { @Autowired HistoryService historyService; private static final Logger logger = LoggerFactory.getLogger(HiTest.class); @Test void test01() { List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().list(); for (HistoricProcessInstance hi : list) { logger.info("==={},{},{},{},{},{}",hi.getId(),hi.getName(),hi.getStartActivityId(),hi.getStartTime(),hi.getEndActivityId(),hi.getEndTime()); } }}因为咱们这是 Spring Boot 我的项目,所以基本上不须要什么额定的配置,间接注入 HistoryService 实例即可。 test01 办法中的代码就是查问进去目前所有的流程实例,包含正在执行的和曾经执行结束的都能够查问到。查问之后,控制台打印后果如下: ==> Preparing: SELECT RES.* , DEF.KEY_ as PROC_DEF_KEY_, DEF.NAME_ as PROC_DEF_NAME_, DEF.VERSION_ as PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_ from ACT_HI_PROCINST RES left outer join ACT_RE_PROCDEF DEF on RES.PROC_DEF_ID_ = DEF.ID_ order by RES.ID_ asc==> Parameters: <== Total: 1Flushing dbSqlSessionflush summary: 0 insert, 0 update, 0 delete.now executing flush...--- HistoricProcessInstanceQueryImpl finished --------------------------------------------------------===a3786614-38eb-11ed-afc8-acde48001122,null,startEvent1,Tue Sep 20 21:53:42 CST 2022,null,null首先大家看到,这里查问的 SQL,查问的表是 ACT_HI_PROCINST,简略截个图大家看下: ...

September 28, 2022 · 2 min · jiezi

关于springboot:记一次使用spring-javaconfig踩到的坑

前言为了简化开发,我部门常常会封装一些通用的类库给业务研发应用,因为业务方的根包门路很常常和咱们部门我的项目的根包是不一样的,因而咱们会让业务方在应用咱们封装的包时,扫描一下咱们的根包,形如下 @ComponentScan(basePackages = {"com.aaa","com.bbb"})不过这样就导致了业务方想应用咱们的类库,就必须晓得咱们的根包。这其实是一种间接的耦合。前面咱们就全面应用springboot的主动拆卸,让业务方无需晓得咱们的根包,也能够应用咱们的类库。然而在咱们封装的过程中,也遇到一些坑。本文就来复盘一次咱们应用spring javaconfig踩到的坑。本文次要是demo示例演示 demo示例假如咱们封装了一个类库DemoService。示例如下 public class DemoService { private DemoProperties demoProperties; private String version; public DemoService(DemoProperties demoProperties) { this.demoProperties = demoProperties; } public String print(){ return "version:" + version + ">>>>>>>>>>" + demoProperties; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; }}DemoProperties 类如下 @ConfigurationProperties(prefix = "demo.hello")public class DemoProperties { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "DemoProperties{" + "name='" + name + '\'' + '}'; }}有个针对DemoService的扩大后置处理器 ...

September 28, 2022 · 2 min · jiezi

关于springboot:Spring-Boot微服务个人见解

以前开发一个我的项目,要花费不少工夫在搭建我的项目,配置文件上,到当初Spring Boot开箱即用,须要技术栈导入pom就能够了,技术变更带来效率提醒是微小的。有时候我会纳闷,这所有如何得来的,Spring Boot怎么摈弃war部署,摈弃繁琐xml配置。浏览本文章须要肯定的Spring框架常识储备,最初能理解Spring如何进行Bean初始化的,至多晓得BeanDefinition之类的知识点,能力更好阅读文章。上面代码基于Spring Boot 2.7.2 、 Spring Cloud 2021.0.3。 先从我的项目启动放入入口,每一个Spring Boot 我的项目都须要main入口都要调用SpringApplication.run开始 public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); } public SpringApplication(Class<?>... primarySources) { this(null, primarySources); } public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //web 我的项目类型 this.webApplicationType = WebApplicationType.deduceFromClasspath(); this.bootstrapRegistryInitializers = new ArrayList<>( getSpringFactoriesInstances(BootstrapRegistryInitializer.class)); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }webApplicationType是一个枚举类,形容以后我的项目web类型NONE: 以后我的项目不是一个web我的项目SERVLET: 基于servlet api的传统web我的项目REACTIVE: Spring webFlux 响应式web框架deduceFromClasspath : 依据我的项目jar判断以后我的项目属于下面哪个一个类型,前面创立Spring 上下文对象须要用到getSpringFactoriesInstances:从给定接口从文件META-INF/spring.factories 应用类名去加载全类名,并且返回接口所有实现类, 配置文件格式如下 ...

September 27, 2022 · 6 min · jiezi

关于springboot:如何解决mybatisplus添加数据时id自增问题

https://blog.csdn.net/gb42152...

September 27, 2022 · 1 min · jiezi

关于springboot:SpringBoot集成onlyoffice实现word文档编辑保存

阐明onlyoffice为一款开源的office在线编辑组件,提供word/excel/ppt编辑保留操作 以下操作均基于centos8零碎,officeonly镜像版本7.1.2.23镜像下载地址:https://yunpan.360.cn/surl_y8... (提取码:1f92)已破解20连贯限度已导入罕用中文字体,批改了字号已勾销上传文件大小的限度,批改为500M暗藏所有操作按钮,暗藏onlyoffice图标和名称,只保根底操作栏目仅用于word文件和excel文件编辑/保留/查看Linux装置yum设置进入yum的repos目录 cd /etc/yum.repos.d/cd /etc/yum.repos.d/批改所有的CentOS文件内容sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*更新yum源为阿里镜像wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-vault-8.5.2111.repoyum clean allyum makecacheyum装置测试是否能够yum装置yum install wget –y装置依赖包sudo yum install -y yum-utils device-mapper-persistent-data lvm2 docker装置设置镜像源sudo yum-config-manager --add-repohttps://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo装置docker-ce社区版sudo yum install docker-ce --allowerasing创立用户组建设 Docker 用户组sudo groupadd docker增加以后用户到 docker 组sudo usermod -aG docker $USER启动dockersystemctl start docker.service服务开机启动systemctl enable docker.service装置docker图形化治理页面docker volume create portainer_datadocker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer拜访http://localhost:9000/进行治理端初始化设置 onlyoffice部署上传镜像文件到服务器载入镜像docker load < onlyoffice.tar查看镜像docker images启动容器sudo docker run --name=onlyoffice -i -t -d -p 8088:80 --restart=always 镜像idWindows装置装置VMWare虚拟机,装置centos8零碎,参照上述步骤网络配置成NAT模式,虚拟机进入centos零碎后配置固定ipNAT网卡设置端口转发,转发9000和8088端口,主机端口和虚构端口统一增加字体找到对应字体,如果是windows下的字体进入C:\Windows\Fonts 搜寻装置High-Logic FontCreator 运行Keygen.exe 点击generate获取激活码工具下载:https://yunpan.360.cn/surl_y8... (提取码:7059)关上字体 font=>properties批改 font family 为custom 中对应的中文 ,导出字体上传批改后的字体到liunx文件下 eg:/home/fonts/查看OnlyOffice容器iddocker ps进入OnlyOffice容器docker exec -it 容器id /bin/bash删除字体缓存cd /var/www/onlyoffice/documentserver/fontsrm -rf *复制字体到 /var/www/onlyoffice/documentserver/core-fonts 下docker cp /home/fonts onlyoffice:/var/www/onlyoffice/documentserver/core-fonts进入onlyoffice容器执行刷新/usr/bin/documentserver-generate-allfonts.sh浏览器革除缓存从新刷新对接业务零碎onlyfiice部署结束后会有一个服务地址,例如 http://localhost:8088/ ...

September 25, 2022 · 5 min · jiezi

关于springboot:SpringBoot学习记录

SpringBoot概述SpringBoot概念SpringBoot提供了一种疾速应用Spring的形式,基于约定优于配置的思维,能够让开发人员不用在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,肯定水平上缩短了我的项目周期。2014 年 4 月,Spring Boot 1.0.0 公布。Spring的顶级我的项目之一(https://spring.io)。 Spring毛病配置繁琐尽管Spring的组件代码是轻量级的,但它的配置却是重量级的。一开始Spring用XML配置,而且是很多XML配置。Spring 2.5引入了基于注解的组件扫描,这打消了大量针对应用程序本身组件的显式XML配置。 Spring 3.0引入了基于Java的配置,这是一种类型平安的可重构配置形式,能够代替XML。 所有这些配置都代表了开发时的损耗。因为在思考Spring个性配置和解决业务问题之间须要进行思维切换,所以编写配置挤占了编写利用程序逻辑的工夫。和所有框架一样,Spring实用,但它要求的回报也不少。 依赖繁琐我的项目的依赖治理也是一件耗时耗力的事件。在环境搭建时,须要剖析要导入哪些库的坐标,而且还须要剖析导入与之有依赖关系的其余库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会重大妨碍我的项目的开发进度。 SpringBoot性能主动配置SpringBoot的主动配置是一个运行时(更精确地说,是应用程序启动时)的过程,思考了泛滥因素,才决定Spring配置应该用哪个,不该用哪个。该过程是SpringBoot主动实现的。 起步依赖起步依赖实质上是一个Maven我的项目对象模型(Project Object Model,POM),定义了对其余库的传递依赖 ,这些货色加在一起即反对某项性能。 简略的说:起步依赖就是将具备某种性能的坐标打包到一起,并提供一些默认的性能。 辅助性能提供了一些大型项目中常见的非功能性个性,如嵌入式服务器、平安、指标,衰弱检测、内部配置等。 小结SpringBoot提供了一种疾速开发Spring我的项目的形式,而不是对Spring性能上的加强。 Spring的毛病: 配置繁琐依赖繁琐SpringBoot性能 主动配置起步依赖辅助性能SpringBoot疾速入门第一个SpringBoot程序需要搭建SpringBoot工程,定义HelloController.hello()办法,返回”Hello SpringBoot!”。 思路创立Maven我的项目导入SpringBoot起步依赖定义Controller编写疏导类启动测试实现创立一个一般的Java Maven工程,最终的我的项目目录构造如图所示: 编写pom.xml配置文件,增加SpringBoot我的项目所需依赖 <!--SpringBoot的web我的项目起步配置依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>创立HelloController类,进行简略的业务代码书写 package xyz.rtx3090.springboot.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author BernardoLi * @version 1.0.0 * @ClassName HelloController * @createTime 2021年09月11日 08:22:00 * @Description 第一个springboot程序 */@RestController//相当于@[email protected]public class HelloController { @RequestMapping(value = "/hello") public String hello() { return "Hello World!"; }}创立Application启动类,用于启动SpringBoot我的项目 ...

September 24, 2022 · 5 min · jiezi

关于springboot:实体类BigDecimal精度控制

import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import java.io.IOException;import java.math.BigDecimal;public class BigDecimalSerializer extends JsonSerializer<BigDecimal> { public BigDecimalSerializer() { } public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value != null) { BigDecimal number = value.setScale(2, 4); gen.writeNumber(number); } else { gen.writeNumber(value); } }}class A{ @JsonSerialize( using = BigDecimalSerializer.class ) @ApiModelProperty(value = "价格下限") private BigDecimal maxPrice;}

September 22, 2022 · 1 min · jiezi

关于springboot:聊聊如何利用管道模式来进行业务编排下篇

前言上篇文章咱们介绍利用管道模式来进行业务编排的2种实现形式。本文又来介绍其余实现形式 实现形式形式一:利用springboot主动拆卸1、新建管道实体@Data@AllArgsConstructor@NoArgsConstructorpublic class PipelineDefinition { public static final String PREFIX = "lybgeek_pipeline_"; private String comsumePipelineName; private List<String> pipelineClassNames;}@Data@AllArgsConstructor@NoArgsConstructor@ConfigurationProperties(prefix = PipelineDefinitionProperties.PREFIX)public class PipelineDefinitionProperties { public final static String PREFIX = "lybgeek.pipeline"; private List<PipelineDefinition> chain;}2、编写主动拆卸类@Configuration@EnableConfigurationProperties(PipelineDefinitionProperties.class)public class PipelineAutoConfiguration implements BeanFactoryAware,InitializingBean, SmartInitializingSingleton { @Autowired private PipelineDefinitionProperties pipelineDefinitionProperties; private DefaultListableBeanFactory defaultListableBeanFactory; @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { defaultListableBeanFactory = (DefaultListableBeanFactory)beanFactory; } private void registerPipeline(DefaultListableBeanFactory defaultListableBeanFactory, PipelineDefinition pipelineDefinition) { LinkedBlockingDeque linkedBlockingDeque = buildPipelineQuque(pipelineDefinition); GenericBeanDefinition beanDefinition = (GenericBeanDefinition) BeanDefinitionBuilder.genericBeanDefinition(ChannelPipeline.class).getBeanDefinition(); beanDefinition.getPropertyValues().addPropertyValue("channelHandlers",linkedBlockingDeque); defaultListableBeanFactory.registerBeanDefinition(PipelineDefinition.PREFIX + pipelineDefinition.getComsumePipelineName() ,beanDefinition); } @SneakyThrows private LinkedBlockingDeque buildPipelineQuque(PipelineDefinition pipelineDefinition) { List<String> pipelineClassNames = pipelineDefinition.getPipelineClassNames(); if(CollectionUtil.isEmpty(pipelineClassNames)){ throw new PipelineException("pipelineClassNames must config"); } LinkedBlockingDeque linkedBlockingDeque = new LinkedBlockingDeque(); for (String pipelineClassName : pipelineClassNames) { Class<?> pipelineClassClass = Class.forName(pipelineClassName); if(!AbstactChannelHandler.class.isAssignableFrom(pipelineClassClass)){ throw new PipelineException("pipelineClassNames must be 【com.github.lybgeek.pipeline.handler.AbstactChannelHandler】 subclass"); } Object pipeline = pipelineClassClass.getDeclaredConstructor().newInstance(); linkedBlockingDeque.addLast(pipeline); } return linkedBlockingDeque; } @Override public void afterPropertiesSet() throws Exception { if(CollectionUtil.isNotEmpty(pipelineDefinitionProperties.getChain())){ for (PipelineDefinition pipelineDefinition : pipelineDefinitionProperties.getChain()) { registerPipeline(defaultListableBeanFactory, pipelineDefinition); } } } @Override public void afterSingletonsInstantiated() { Map<String, ChannelPipeline> pipelineBeanMap = defaultListableBeanFactory.getBeansOfType(ChannelPipeline.class); pipelineBeanMap.forEach((key,bean)->{ bean.setHandlerContext(ChannelHandlerContext.getCurrentContext()); }); }}3、编写spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.github.lybgeek.pipeline.spring.autoconfigure.PipelineAutoConfiguration\业务我的项目如何应用该形式实现业务编排示例: ...

September 20, 2022 · 4 min · jiezi

关于springboot:SpringBoot中JPA的基本使用

SpringBoot中JPA的根本应用1.Jpa是什么? JPA,顾名思义,就是Java持久性API的意思。JDK 5.0正文或XML形容了对象和关系表之间的映射关系,并在运行时将实体对象保留到数据库中。 2.劣势2.1标准化 JPA是JCP公布的Java EE规范之一,所以任何宣称合乎JPA规范的框架都遵循雷同的架构,提供雷同的拜访API,这保障了基于JPA开发的企业应用只需稍加批改就能够在不同的JPA框架下运行。2.2反对容器级个性JPA框架反对容器级的事务,比方大数据集、事务、并发等。,使得JPA超过了简略持久性框架的限度,在企业应用中施展更大的作用。 2.3简略不便 JPA的次要指标之一是提供一个更简略的编程模型:在JPA框架下创立实体就像创立Java类一样简略,没有任何束缚和限度,只须要应用javax.persistence.Entity进行正文。JPA的框架和接口也非常简单,没有太多不同的规定和设计模式,开发者很容易把握。JPA是基于非侵入性原理设计的,因而能够很容易地与其余框架或容器集成。 2.4查问能力 JPA的查询语言是面向对象的,而不是面向数据库的。它用面向对象的天然语法结构查问语句,能够看作是Hibernate HQL的等价物。JPA定义了惟一的JPQL (Java持久性查询语言)。JPQL是EJB QL的扩大,它是一种实体查询语言。操作对象是实体,而不是关系数据库的表,能够反对批量更新和批改、JOIN、GROUP BY、HAVING等高级查问性能。,通常只有SQL能够提供。它甚至能够反对子查问。 2.4高级性能 JPA能够反对高级的面向对象个性,比方继承、多态和类之间的简单关系。这种反对使开发人员可能最大限度地应用面向对象的模型来设计企业应用程序,而不用解决这些个性在关系数据库自身中的持久性。Spring Boot Jpa把咱们从DAO层的操作中解放出来,基本上所有的CRUD都能够通过它来实现。 3.代码实现。 增加依赖项和配置 pom.xmlspringframework.boot弹簧-启动-启动器-数据-jpa关系型数据库MySQL-连接器-java应用程序.属性 spring . data source . URL = JDBC:MySQL://localhost:3306/test?server time zone = UTC & use unicode = true & character encoding = utf-8 & use SSL = truespring.datasource .用户名=rootspring . data source . password = rootspring . data source . driver-class-name = com . MySQL . CJ . JDBC . driverspring . JPA . properties . hibernate . hbm 2 DDL . auto = createspring . JPA . properties . hibernate . dialect = org . hibernate . dialect . MySQL 5 innodb dialect#sql\u8F93\u51FAspring.jpa.show-sql=true# format \ u4e 00 \ u 4 E0 bsql \ u8FDB \ u884C \ u8f 93 \ u51FAspring . JPA . properties . hibernate . format _ SQL = true一.根本考察基本上有两种查问:一是默认状况下曾经实现了Spring数据,一种是依据查询方法主动解析成SQL。预生成办法默认状况下,Spring Boot Jpa事后生成了一些凝乳的根本办法,如增加、删除、更改等。 ...

September 19, 2022 · 2 min · jiezi

关于springboot:springboot-shiro-jwt-redis-实现登录授权

我的项目github地址:https://github.com/liboshuai0...我的项目gitee地址:https://gitee.com/liboshuai01...背景公司用的我的项目是基于shiro + cookie/session的,然而当初微服务架构的背景下都是采纳token机制进行认证和受权的。于是决定先本人搭建一个spring+shiro+jwt的我的项目,用来不便替换公司的技术栈。 Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,能够记录会话信息。而 Token 是令牌,拜访资源接口(API)时所须要的资源凭证。Token 使服务端无状态化,不会存储会话信息。 Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个申请都有签名还能避免监听以及重放攻打,而 Session 就必须依赖链路层来保障通信平安了。如果你须要实现有状态的会话,依然能够减少 Session 来在服务器端保留一些状态。 所谓 Session 认证只是简略的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是平安的。而 Token ,如果指的是 OAuth Token 或相似的机制的话,提供的是 认证 和 受权 ,认证是针对用户,受权是针对 App 。其目标是让某 App 有权力拜访某用户的信息。这里的 Token 是惟一的。不能够转移到其它 App上,也不能够转到其它用户上。Session 只提供一种简略的认证,即只有有此 SessionID ,即认为有此 User 的全副权力。是须要严格窃密的,这个数据应该只保留在站方,不应该共享给其它网站或者第三方 App。所以简略来说:如果你的用户数据可能须要和第三方共享,或者容许第三方调用 API 接口,用 Token 。如果永远只是本人的网站,本人的 App,用什么就无所谓了。 疾速开始搭建一个springboot我的项目demo我的项目pom.xml配置文件 父工程pom.xml文件 <?xml version="1.0" encoding="UTF-8"?><project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.liboshuai</groupId> <artifactId>mall-tiny</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>mall-tiny-01</module> <module>mall-tiny-00-api</module> </modules> <packaging>pom</packaging> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <mybatis-plus-boot-starter-version>3.4.0</mybatis-plus-boot-starter-version> <druid-spring-boot-starte-version>1.2.11</druid-spring-boot-starte-version> <mysql-connector-java-version>8.0.15</mysql-connector-java-version> <lombok-version>1.18.10</lombok-version> <log4j-version>1.2.17</log4j-version> <springfox-swagger2-version>2.7.0</springfox-swagger2-version> <springfox-swagger-ui-version>2.7.0</springfox-swagger-ui-version> <jackson-databind-version>2.13.3</jackson-databind-version> <xxl-job-core-version>2.4.0-SNAPSHOT</xxl-job-core-version> <hutool-all-version>4.5.7</hutool-all-version> <jjwt-version>0.9.0</jjwt-version> <mybatis-plus-generator-version>3.5.1</mybatis-plus-generator-version> <velocity-engine-core-version>2.3</velocity-engine-core-version> <commons-io-version>2.4</commons-io-version> <shiro-version>1.4.0</shiro-version> <jwt-version>3.2.0</jwt-version> <fastjson.version>1.2.58</fastjson.version> <knife4j-swagger-version>2.0.4</knife4j-swagger-version> </properties></project>子工程pom.xml文件 ...

September 19, 2022 · 18 min · jiezi

关于springboot:springboot配置文件脱敏

本文转载至:https://www.51cto.com/article...常常会遇到这样一种状况:我的项目的配置文件中总有一些敏感信息,比方数据源的url、用户名、明码....这些信息一旦被裸露那么整个数据库都将会被透露,那么如何将这些配置暗藏呢? 明天介绍一种计划,让你在无感知的状况下实现配置文件的加密、解密。利用一款开源插件:jasypt-spring-boot。我的项目地址如下: https://github.com/ulisesbocchio/jasypt-spring-boot应用办法很简略,整合Spring Boot 只须要增加一个starter。 增加依赖 <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.3</version></dependency> 配置秘钥在配置文件中增加一个加密的秘钥(任意),如下: jasypt: encryptor: password: Y6M9fAJQdU7jNp5MW当然将秘钥间接放在配置文件中也是不平安的,咱们能够在我的项目启动的时候配置秘钥,命令如下: java -jar xxx.jar -Djasypt.encryptor.password=Y6M9fAJQdU7jNp5MW生成加密后的数据这一步骤是将配置明文进行加密,代码如下: @SpringBootTest@RunWith(SpringRunner.class)public class SpringbootJasyptApplicationTests { /** * 注入加密办法 */ @Autowired private StringEncryptor encryptor; /** * 手动生成密文,此处演示了url,user,password */ @Test public void encrypt() { String url = encryptor.encrypt("jdbc\\:mysql\\://127.0.0.1\\:3306/test?useUnicode\\=true&characterEncoding\\=UTF-8&zeroDateTimeBehavior\\=convertToNull&useSSL\\=false&allowMultiQueries\\=true&serverTimezone=Asia/Shanghai"); String name = encryptor.encrypt("root"); String password = encryptor.encrypt("123456"); System.out.println("database url: " + url); System.out.println("database name: " + name); System.out.println("database password: " + password); Assert.assertTrue(url.length() > 0); Assert.assertTrue(name.length() > 0); Assert.assertTrue(password.length() > 0); }}上述代码对数据源的url、user、password进行了明文加密,输入的后果如下: ...

September 19, 2022 · 1 min · jiezi

关于springboot:62dockercompose-结合dockerfile-部署springboot应用

dockerfile内容: # 根底镜像FROM openjdk:8-jre# authorMAINTAINER lw# 挂载目录VOLUME /home/ybss# 创立目录RUN mkdir -p /home/ybss# 指定门路WORKDIR /home/ybssADD ./jar/lib /home/ybss/libADD ./jar/*.yml /home/ybss/# 复制jar文件到门路COPY ./jar/run.jar /home/ybss/run.jar# 启动认证服务ENTRYPOINT ["java","-jar","-Dserver.port=9200","-Dspring.profiles.active=dev","run.jar"]docker-compose.yml 内容: version : '3.8'services: ybss-auth: container_name: ybss-auth build: context: ./ybss/auth dockerfile: dockerfile ports: - "19200:9200" volumes: - /home/ybss/ybss/auth/jar:/home/ybssnetworks: ybss_default: driver: bridgevolumes挂载目录/home/ybss/ybss/auth/jar,有更新时,只须要把更新jar和文件替换即可。 部署脚本shell:#!/bin/shauth(){ chmod 777 -R ./ybss/auth/* docker-compose up -d ybss-auth}stop(){ docker-compose stop}case "$1" in"auth") auth;;"s") stop;;

September 7, 2022 · 1 min · jiezi

关于springboot:61腾讯云负载均衡配置httpsnginx转发到springboot项目

腾讯云控制台配置登录腾讯云控制台 拜访负载平衡》实例治理: 点击【ID/名称】 切换页签【监听管理器】>【HTTP/HTTPS监听器】 配置门路: 1. 配置协定 2. 配置转发门路 nginx配置在nginx配置文件中 http{节点下:这样转发申请时,header不会失落 underscores_in_headers on;增加sever节点: server { listen 9007; server_name localhost; location /ya { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Port $server_port; # 这里对应的是springboot我的项目 ,拜访时, http://localhost:9008/ya proxy_pass http://localhost:9008; } }springboot 配置跨域配置:将 corsConfiguration.addAllowedOrigin("*");改为: corsConfiguration.addAllowedOriginPattern("*");具体为: @Configurationpublic class CorsConfig { @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration();// corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedOriginPattern("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.setAllowCredentials(true); source.registerCorsConfiguration("/**", corsConfiguration); return new CorsFilter(source); }}yml配置: ...

September 6, 2022 · 1 min · jiezi

关于springboot:支持多种前后端组合的代码生成项目终于不用写CRUD了

介绍抽取若依RuoYi-Vue的代码生成,目前反对Mybatis、Mybaits-plus的后端代码生成,前端反对element-ui与vue、Ant Design of Vue以及html的代码,能够自由组合导出预览,能够通过导入mysql脚本的形式来创立表进行代码生成,能够导出数据字典,后续会继续更新。性能反对mybatis、mybaits-plus的后端代码生成(仅限于增删改查)反对element-ui与vue的代码生成反对ant-dv与vue的代码生成反对html的代码生成反对mysql脚本创立数据库表进行代码生成反对导出数据字典环境部署JDK >= 1.8 (举荐1.8版本)Mysql >= 5.7.0Maven >= 3.0Node >= 10拉取代码,仓库地址:https://gitee.com/Xiao_bear/x...启动前端与后端配置阐明数据库连贯配置application.yml # 数据源配置spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver druid: # 主库数据源 master: url: 数据库地址 username: 数据库账号 password: 数据库明码代码生成配置文件generator.yml #代码生成gen: # 作者 author: xiaobear # 默认生成包门路 com.xiaobear 需改成本人的模块名称 如 system packageName: com.xiaobear # 主动去除表前缀,默认是false autoRemovePre: false # 表前缀(生成类名不会蕴含表前缀,多个用逗号分隔) tablePrefix: # 代码生成数据库 dataBase: ly-generatordataBase:若为空,则导入数据库脚本,默认抉择以后连贯的数据库也可连贯若依环境的数据库,但须要减少如下两个字段: SET FOREIGN_KEY_CHECKS=0;ALTER TABLE `gen_table` ADD COLUMN `front_end` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '前端框架' AFTER `options`;ALTER TABLE `gen_table` ADD COLUMN `back_end` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '后端框架' AFTER `front_end`;应用阐明脚本代码生成复制数据库脚本 ...

September 4, 2022 · 1 min · jiezi

关于springboot:Springboot中的Order怎么用

在spring-boot 2.6.2下测试,@Order并不会影响bean的装载程序,申明了@Component的类,无论是构造方法、@PostConstruct注解申明的办法,还是实现的InitializingBean接口中的afterPropertiesSet()办法,如果beanClass位于同样的目录层级,这些办法的调用只会受到className的程序影响: @Component@Slf4j@Order(2)public class Bean1 implements InitializingBean { public Bean1() { log.info("construct bean1"); } @Override public void afterPropertiesSet() throws Exception { log.info("initialled bean1"); } @PostConstruct public void post() { log.info("post bean1"); }}@Component@Slf4j@Order(1)public class Bean2 implements InitializingBean { public Bean2() { log.info("construct bean2"); } @Override public void afterPropertiesSet() throws Exception { log.info("initialled bean2"); } @PostConstruct public void post() { log.info("post bean2"); }}/* 后果打印程序:construct bean1post bean1initialled bean1construct bean2post bean2initialled bean2*/察看@Order的注解阐明,第一句写着: @Order defines the sort order for an annotated component. 提到这个注解只是对component排序,那么哪里会收到这个排序数值的影响呢? ...

September 2, 2022 · 2 min · jiezi

关于springboot:SpringBootUniapp实战开发全新仿抖音短视频App

download:SpringBoot+Uniapp实战开发全新仿抖音短视频AppSpring Cloud OpenFeign近程调用性能优化分享一下OpenFeign几个方面优化的小技巧,次要分为以下几点: 请求通信连接优化超时优化负载平衡数据压缩日志级别优化 一、请求通信连接优化OpenFeign底层通信组件默认使用JDK自带的URLConnection对象进行HTTP请求的,因为没有使用连接池,所以性能不是很好。咱们可能将OpenFeign的通信组件,手动替换成像Apache HttpClient或OKHttp这样的专用通信组件,这些的专用通信组件自带连接池可能更好地对 HTTP 连接对象进行重用与治理,同时也能大大的晋升 HTTP 请求的效率。接下来我以Apache HttpClient为例,演示一下专用通信组件的替换使用。1.1 增加pom依赖<!-- 增加 openfeign 框架依赖 --><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!-- 增加 httpclient 框架依赖 --><dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId></dependency>复制代码1.2 开启Apache HttpClient使用启动Apache HttpClient,在我的项目配置文件application.yml中增加以下配置:spring: cloud: feign: client: httpclient: # 默认Feign使用的是JDK自带的URLConnection进行Http请求的,手动替换成Apache HttpClient专用的通信组件 enabled: true 复制代码考据Apache HttpClient配置是否失效,通过debug调试,在feign.SynchronousMethodHandler#executeAndDecode()方法上打端断点,通过发动一个近程调用请求, 二、Ribbon超时优化OpenFeign和Feign底层都内置了Ribbon负载平衡组件,在导入OpenFeign依赖后无需顺便导入Ribbon依赖。OpenFeign也因此使用了Ribbon的请求连接超时工夫和请求处理超时工夫作为其超时工夫,而Ribbon默认的请求连接超时工夫和请求处理超时工夫都是1s, 2.1 设置OpenFeign调用超时工夫在我的项目配置文件application.yml中增加配置: 近程调用优化feign: client: httpclient: # 默认Feign使用的是JDK自带的URLConnection进行Http请求的,手动替换成Apache HttpClient专用的通信组件 enabled: trueconfig: default: # 设置的全局超时工夫 connectTimeout: 3000 # 请求连接的超时工夫 readTimeout: 5000 # 请求处理的超时工夫复制代码2.2 设置Ribbon超时工夫ribbon: ConnectionTimeout: 3000 # 请求连接的超时工夫 ReadTimeout: 5000 # 请求处理的超时工夫 复制代码通过debug的形式考据以下设置的超时工夫是否失效,很显著配置已经失效,次要看org.springframework.cloud.openfeign.FeignClientFactoryBean#configureUsingProperties()方法, ...

September 1, 2022 · 1 min · jiezi

关于springboot:SpringBoot-华为Kafka集群-Kerberos认证

一、简介环境:1、JDK 1.8 2、SpringBoot3、华为Kafka集群 Kafka集群用户名明码的连贯集群简略,Kerberos认证集群的网上简直没有,这里我把对接的重要点分享一下。 二、集成1、能到手上的材料Kafka集群个别不是咱们装置的,当咱们须要对接到Kafka集群的时候,对方会提供给咱们的货色有: 密钥文件:user.keytabprincipal:xxxxxx加密协议security.protocol: SASL_SSL或SASL_PLAINTEXTjaas.conf(如果没有,咱们也能够依据以上内容构建一个)2、入手2.1、程序配置通过官网、网上的材料,咱们最初的配置文件如下: spring: ########################################################################## ############# kafka 配置 ########################################################################## kafka: # kafka实例的broker地址和端口 bootstrap-servers: 100.xxx.xxx.87:909x,100.xxx.xxx.69:909x,100.xxx.xxx.155:909x # 生产者配置 producer: # 重试次数,则客户端会将发送失败的记录从新发送 retries: 1 # 16K batch-size: 16384 # #32M buffer-memory: 33554432 # 发送确认参数: 0:发送后不论, 1:发送后Partition Leader音讯落盘, all: 所有的正本都ok才返回 acks: 1 # 指定音讯key和音讯体的编解码形式 key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer consumer: # 消费者组 group-id: Consumer-test # 动提交 enable-auto-commit: true # 偏移量的形式: # earliest:当各分区下有已提交的offset时,从提交的offset开始生产;无提交的offset时,从头开始生产 # latest: 当各分区下有已提交的offset时,从提交的offset开始生产;无提交的offset时,从新产生的该分区下的数据生产 auto-offset-reset: latest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer jaas: enabled: true login-module: com.sun.security.auth.module.Krb5LoginModule control-flag: required options: "useKeyTab": true "debug": true "useTicketCache": false "storeKey": true "keyTab": "/etc/user.keytab" "principal": xxxxx properties: # 加密协议,目前反对 SASL_SSL、SASL_PLAINTEXT 协定 "security.protocol": SASL_PLAINTEXT # 域名 "kerberos.domain.name": topinfo.com # 服务名 "sasl.kerberos.service.name": kafka关注: jaas 属性的配置,"keyTab" 的门路配置。 ...

August 31, 2022 · 2 min · jiezi

关于springboot:SpringBoot-在线协同办公小程序开发-全栈式项目实战完结内附资料文档

download:SpringBoot 在线协同办公小程序开发 全栈式我的项目实战【完结内附材料文档】SpringBoot-全局异样处理背景在 Web 开发中, 咱们常常会需要处理各种异样, 这是一件辣手的事件, 需要考虑以下几个问题 : 什么时候需要捕捉 ( try-catch ) 异样, 什么时候需要抛出 ( throws ) 异样到下层 ?在 dao 层捕捉还是在 service 捕捉, 还是在 controller 层捕捉 ?抛出异样后要怎么处理. 怎么返回给页面错误信息 ? 全局异样处理推荐做法 不要在业务代码中进行捕捉异样, 即 dao, service, controller 层的所有异样都全副抛出到下层. 这样不会导致业务代码中的一堆 try-catch 导致业务代码错乱.哪一层都不捕捉.返回对立的后果集 ( 错误码 + 谬误描述 ). 通常会将事务配置在 Service 层, 当数据库操作失败时, 让 Service 层抛出运行时异样, 而不进行 try-catch 处理,  Spring 事物管理器就会进行回滚. 为了事务回滚但 Service 层抛出后. 在 Controller 层就需要 try-catch 去捕捉异样, 否则会返回原生错误信息到客户端. 然而, 如果在 Controller 层的每个方法体外面都写一些模板化的 try-catch 的代码, 代码岂但不美观, 也减少了保护的难度, 特地是还需要对 Service 层的不同异样进行不同处理的时候.@ControllerAdvice 注解是 Spring 3.2 中新增的. 用于拦挡全局的 Controller 的异样. 注意:该注解只能拦挡 Controller 不能拦挡 Interceptor 的异样.全局异样处理的步骤 ...

August 28, 2022 · 1 min · jiezi

关于springboot:SpringBoot全局异常处置

SpringBoot-全局异样处理背景在 Web 开发中, 咱们常常会需要处理各种异样, 这是一件辣手的事件, 需要考虑以下几个问题 : 什么时候需要捕捉 ( try-catch ) 异样, 什么时候需要抛出 ( throws ) 异样到下层 ?在 dao 层捕捉还是在 service 捕捉, 还是在 controller 层捕捉 ?抛出异样后要怎么处理. 怎么返回给页面错误信息 ? 全局异样处理推荐做法不要在业务代码中进行捕捉异样, 即 dao, service, controller 层的所有异样都全副抛出到下层. 这样不会导致业务代码中的一堆 try-catch 导致业务代码错乱.哪一层都不捕捉.返回对立的后果集 ( 错误码 + 谬误描述 ).通常会将事务配置在 Service 层, 当数据库操作失败时, 让 Service 层抛出运行时异样, 而不进行 try-catch 处理,  Spring 事物管理器就会进行回滚. 为了事务回滚但 Service 层抛出后. 在 Controller 层就需要 try-catch 去捕捉异样, 否则会返回原生错误信息到客户端. 然而, 如果在 Controller 层的每个方法体外面都写一些模板化的 try-catch 的代码, 代码岂但不美观, 也减少了保护的难度, 特地是还需要对 Service 层的不同异样进行不同处理的时候.@ControllerAdvice 注解是 Spring 3.2 中新增的. 用于拦挡全局的 Controller 的异样. 注意:该注解只能拦挡 Controller 不能拦挡 Interceptor 的异样. ...

August 28, 2022 · 1 min · jiezi

关于springboot:spring-boot使用IoTDB的两种方式

InfluxDB和IotDB介绍与性能比照 Linux MacBook Docker装置IoTDB及应用 形式一: session形式拜访IotDB (举荐应用,自带连接池)maven依赖iotdb-session<dependency> <groupId>org.apache.iotdb</groupId> <artifactId>iotdb-session</artifactId> <version>0.11.2</version></dependency>springboot IotDB配置信息session形式spring: iotdb: username: root password: root ip: 192.168.0.5 port: 6667 maxSize: 100 IotDB-session配置类package com.beyond.data.config;import org.apache.iotdb.session.pool.SessionPool;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import java.util.List;@Component@Configurationpublic class IotDBSessionConfig { private static final Logger log = LoggerFactory.getLogger(IotDBSessionConfig.class); @Value("${spring.iotdb.username:root}") private String username; @Value("${spring.iotdb.password:root}") private String password; @Value("${spring.iotdb.ip:127.0.0.1}") private String ip; @Value("${spring.iotdb.port:6667}") private int port; @Value("${spring.iotdb.maxSize:10}") private int maxSize; private static SessionPool sessionPool; private SessionPool getSessionPool() { if (sessionPool == null) { sessionPool = new SessionPool(ip, port, username, password, maxSize); } return sessionPool; } public void insertRecord(String deviceId, long time, List<String> measurements, List<String> values) { getSessionPool(); try { log.info("iotdb数据入库:device_id:[{}], measurements:[{}], values:[{}]", deviceId, measurements, values); sessionPool.insertRecord(deviceId, time, measurements, values); } catch (Exception e) { log.error("IotDBSession insertRecord失败: deviceId={}, time={}, measurements={}, values={}, error={}", deviceId, time, measurements, values, e.getMessage()); } }}调用session形式@Autowiredprivate IotDBSessionConfig iotDBSessionConfig;......StringBuffer tableName = new StringBuffer();tableName.append("root").append(".").append("test").append("deviceid");long currentTime = System.currentTimeMillis();List<String> iotMeasurements = new ArrayList<>();iotMeasurements.add("aaa");iotMeasurements.add("bbb");List<String> iotValues = new ArrayList<>();iotValues.add("123");iotValues.add("abide");iotDBSessionConfig.insertRecord(tableName.toString(), currentTime, iotMeasurements, iotValues);形式二: jdbc形式拜访IotDB (本人实现连接池)maven依赖iotdb-jdbc<dependency> <groupId>org.apache.iotdb</groupId> <artifactId>iotdb-jdbc</artifactId> <version>0.11.2</version></dependency> <!-- alibaba的druid数据库连接池 --><dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId></dependency>spring boot IotDB配置信息jdbcspring: iotdb: username: root password: root driver-name: org.apache.iotdb.jdbc.IoTDBDriver url: jdbc:iotdb://192.168.0.5:6667/ initial-size: 5 min-idle: 10 max-active: 50 max-wait: 60000 remove-abandoned: true remove-abandoned-timeout: 30 time-between-eviction-runs-millis: 60000 min-evictable-idle-time-millis: 300000 test-while-idle: false test-on-borrow: false test-on-return: false IotDB-jdbc配置类package com.beyond.data.config;import com.alibaba.druid.pool.DruidDataSource;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import java.sql.*;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@Component@Configurationpublic class IotDBConfig { private static final Logger log = LoggerFactory.getLogger(IotDBConfig.class); @Value("${spring.iotdb.username}") private String username; @Value("${spring.iotdb.password}") private String password; @Value("${spring.iotdb.driver-name}") private String driverName; @Value("${spring.iotdb.url}") private String url; @Value("${spring.iotdb.initial-size:20}") private int initialSize; @Value("${spring.iotdb.min-idle:10}") private int minIdle; @Value("${spring.iotdb.max-active:500}") private int maxActive; @Value("${spring.iotdb.max-wait:60000}") private int maxWait; @Value("${spring.iotdb.remove-abandoned:true}") private boolean removeAbandoned; @Value("${spring.iotdb.remove-abandoned-timeout:30}") private int removeAbandonedTimeout; @Value("${spring.iotdb.time-between-eviction-runs-millis:60000}") private int timeBetweenEvictionRunsMillis; @Value("${spring.iotdb.min-evictable-idle-time-millis:300000}") private int minEvictableIdleTimeMillis; @Value("${spring.iotdb.test-while-idle:false}") private boolean testWhileIdle; @Value("${spring.iotdb.test-on-borrow:false}") private boolean testOnBorrow; @Value("${spring.iotdb.test-on-return:false}") private boolean testOnReturn; private static DruidDataSource iotDbDataSource; //应用阿里的druid连接池 private Connection getConnection() { if (iotDbDataSource == null) { iotDbDataSource = new DruidDataSource(); //设置连贯参数 iotDbDataSource.setUrl(url); iotDbDataSource.setDriverClassName(driverName); iotDbDataSource.setUsername(username); iotDbDataSource.setPassword(password); //配置初始化大小、最小、最大 iotDbDataSource.setInitialSize(initialSize); iotDbDataSource.setMinIdle(minIdle); iotDbDataSource.setMaxActive(maxActive); //配置获取连贯期待超时的工夫 iotDbDataSource.setMaxWait(maxWait); //连贯透露监测 iotDbDataSource.setRemoveAbandoned(removeAbandoned); iotDbDataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout); //配置距离多久才进行一次检测,检测须要敞开的闲暇连贯,单位是毫秒 iotDbDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); iotDbDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); //避免过期 iotDbDataSource.setTestWhileIdle(testWhileIdle); iotDbDataSource.setTestOnBorrow(testOnBorrow); iotDbDataSource.setTestOnReturn(testOnReturn); } Connection connection = null; try { connection = iotDbDataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); log.error("iotDB getConnection失败: error={}", e.getMessage()); } return connection; } public void insert(String sql) { log.info("iotDB insert sql={}", sql); Connection connection = getConnection(); Statement statement = null; try { if(connection!=null){ statement = connection.createStatement(); long systemTime = System.currentTimeMillis(); statement.execute(sql); log.info("执行IotDb的sql----[{}],工夫:[{}]ms", sql, System.currentTimeMillis()-systemTime); } } catch (SQLException e) { log.error("iotDB insert失败: error={}", e.getMessage()); } finally { close(statement, connection); } } public List<Map<String, Object>> query(String sql) { Connection connection = getConnection(); Statement statement = null; List<Map<String, Object>> resultList = null; ResultSet resultSet = null; try { if(connection!=null){ statement = connection.createStatement(); long systemTime = System.currentTimeMillis(); resultSet = statement.executeQuery(sql); log.info("查问IotDb的sql----[{}],工夫:[{}]ms", sql,System.currentTimeMillis()-systemTime); resultList = outputResult(resultSet); } } catch (SQLException e) { e.printStackTrace(); log.error("iotDB query失败: error={}", e.getMessage()); } finally { try { if (resultSet != null) { resultSet.close(); } } catch (SQLException e) { log.error("iotDB resultSet敞开异样: error={}", e.getMessage()); } close(statement, connection); } return resultList; } private List<Map<String, Object>> outputResult(ResultSet resultSet) throws SQLException { List<Map<String, Object>> resultList = new ArrayList<>(); if (resultSet != null) { ResultSetMetaData metaData = resultSet.getMetaData(); int columnCount = metaData.getColumnCount(); while (resultSet.next()) { Map resultMap = new HashMap<>(); for (int i = 1; i <= columnCount; i++) { String colunmName = metaData.getColumnLabel(i); if (colunmName.indexOf('.')>=0) { colunmName = colunmName.substring(colunmName.lastIndexOf('.') + 1); } if (colunmName.indexOf(')')>=0){//过滤 函数()括号 colunmName = colunmName.substring(0, colunmName.lastIndexOf(')')); } if (colunmName.equals("Time")){//时序库自带的工夫格局转换 Long timeStamp = Long.parseLong(resultSet.getString(i)); if(timeStamp > 0) { Date d = new Date(timeStamp); SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); resultMap.put("Time", sf.format(d)); } } else { resultMap.put(colunmName, resultSet.getString(i)); } } resultList.add(resultMap); } } return resultList; } private void close(Statement statement, Connection connection) { try { if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (Exception e) {// e.printStackTrace(); log.error("iotDB close失败: error={}", e.getMessage()); } }}调用jdbc形式@Autowiredprivate IotDBConfig iotDBConfig;......StringBuffer tableName = new StringBuffer();tableName.append("root").append(".").append("test").append("deviceid");long currentTime = System.currentTimeMillis();List<String> iotMeasurements = new ArrayList<>();iotMeasurements.add("aaa");iotMeasurements.add("bbb");List<String> iotValues = new ArrayList<>();iotValues.add("123");iotValues.add("abde");StringBuffer sql = new StringBuffer();sql.append(" insert into ").append(tableName.toString());sql.append("(timestamp,");sql.append(String.join( ",", iotMeasurements)).append(")");sql.append(" values(").append(currentTime).append(",");sql.append(String.join(",", iotValues)).append(")");iotDBConfig.insert(sql.toString());//查问StringBuffer querySql = new StringBuffer();querySql.append(" select ").append("aaa");querySql.append(" from ").append(tableName.toString());querySql.append(" where ").append("bbb").append(" = '");querySql.append("abde").append("'");querySql.append(" order by time desc limit 1 ");log.info("sql----{}", querySql);List<Map<String, Object>> resultList = iotDBConfig.query(querySql.toString());

August 27, 2022 · 4 min · jiezi

关于springboot:SpringBoot-实战数据报表统计并定时推送用户的手把手教程

本文节选自 《实战演练专题》 【实战系列】数据报表统计并定时推送用户的手把手教程 通过一个小的业务点登程,搭建一个能够实例应用的我的项目工程,将各种知识点串联起来; 实战演练专题中,每一个我的项目都是能够独立运行的,蕴含若干知识点,甚至能够不做批改间接利用于生产我的项目; 明天的实战我的项目次要解决的业务需要为:每日新增用户统计,生成报表,并邮件发送给相干人 本我的项目将蕴含以下知识点: 基于 MySql 的每日新增用户报表统计(如何统计每日新增用户,若日期不间断如何主动补 0?)定时执行报表统计工作MyBatis + MySql 数据操作邮件发送Thymeleaf 引擎实现报表模板渲染<!-- more --> I. 需要拆解须要相对来说属于比拟明确的了,目标就是实现一个主动报表统计的工作,查问出每日的用户新增状况,而后推送给指定的用户 因而咱们将很清晰的晓得,咱们须要干的事件 定时工作 这里重点放在如何来反对这个工作的定时执行,通常来说定时工作会辨别为固定时刻执行 + 距离时长执行两种(留神这种辨别次要是为了不便了解,如每天五点执行的工作,也能够了解为每隔 24h 执行一次) 前者常见于一次性工作,如本文中的每天统计一次,这种就是绝对典型的固定时刻执行的工作; 后者常见于轮询式工作,如常见的利用探活(每隔 30s 发一个 ping 音讯,判断服务是否健在) 定时工作的计划十分多,有趣味的小伙伴能够关注一波“一灰灰 blog”公众号,蹲守一个后续本文将间接采纳 Spring 的定时工作实现需求场景,对这块不相熟的小伙伴能够看一下我之前的分享的博文 180801-Spring 之定时工作根本应用篇 - 一灰灰 Blog180803-Spring 定时工作高级应用篇 - 一灰灰 Blog每日新增用户统计 每日新增用户统计,实现形式挺多的,比方举几个简略的实现思路 基于 redis 的计数器:一天一个 key,当天有新用户时,同步的实现计数器+1基于数据库,新增一个统计表,蕴含如日期 + 新增用户数 + 沉闷用户数 等字段 有新用户注册时,对应日期的新增用户数,沉闷用户数 + 1老用户今日首次应用时,沉闷用户数 + 1下面两个计划都须要借助额定的库表来辅助反对,本文则采纳间接统计用户表,依据注册工夫来聚合统计每日的新增用户数 长处:简略,无额定要求,实用于数据量小的场景(比方用户量小于百万的)毛病:用户量大时,数据库压力大对于如何应用 mysql 进行统计每日新增用户,不相熟的小伙伴,举荐参考博主之前的分享文章 220707-MySql 按时、天、周、月进行数据统计 - 一灰灰 Blog报表生成&推送用户 ...

August 23, 2022 · 5 min · jiezi

关于springboot:使用myabatis-postgreSql-中遇到的问题

问题一:字段名不存在执行如下语句的时候呈现了问题: Test entity = new Test();entity.setName(new RandomString().nextString());entity.setFatherTestId(new RandomString().nextString());Integer insNum = testMapper.insert(entity);这里使用了mapper自带的insert办法向数据库中插入数据。 在用mapper进行插入的时候后,报了这样的谬误: 字段名不存在。 然而去数据表中查看,实际上是有fatherId这个字段的。 就纳闷为什么后盾会找不到这个字段? 并认真看红色的的报错,后盾说fatherid这个字段找不到。这里我发现了端倪,数据库中表定义的是fatherId, i这个字母是大写的。 猜想是因为数据库字段大小写的问题。 之后去谷歌之后找到了答案: 起因:在于PostgreSQL对表名、字段名都是辨别大小写的。在执行SQL时,那么不论你的SQL中字段是大写还是小写,最初都会默认转为小写去执行。 解决: 1. 数据库字段名改为小写。或者用下划线_宰割。 起初改为下划线后运行失常 2. 写sql语句的时候,对表名或者字段名加上双引号 定义SQL的时候,对表名及字段名加上双引号,能够避免大写字段被转为小写去执行。 因为在PGSQL中,默认对SQL语句进行抹去双引号和大写转小写的其中一个,且抹去双引号优先级比拟高,因而当大写字段被加上双引号,只进行了抹去双引号的解决而不会再被转为小写了。 例如这样, 加上双引号。 最初因为我这里应用的是mapper自带的insert函数来进行数据插入,并没有本人写sql语句。所以最初采纳了第一种形式解决。 问题二:char(32)类型字段,查问进去的数据带有空格Integer insNum = testMapper.insert(entity);Assert.isTrue(insNum.equals(1), "insert success");String id = entity.getId();Test getEntity = testMapper.selectById(id);;Assert.isTrue(getEntity.getName().equals(entity.getName()), "name not equal");在后盾单元测试的时候,进行了断言:插入的数据和查问进去的数据雷同。 也就是说,将一条数据插入到数据库中,再将其查问进去,判断这两个数据的属性都雷同。 然而单元测试始终没通过。 在代码中打断点测试中发现,发现从数据库中查问进去的数据都带有空格。如下图。 猜想是因为postgresql在保留的时候会,主动将数据长度用空格补充至32位。 后果用testMapper自带的selectById办法查问出数据的时候,就会带有空格。 如果是这样的话,用mapper查问数据进去之后,还须要用trim()函数将空格一一去除。

August 22, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-and-RabbitMQ-初探

概述明天给大家分享以下三局部内容: Docker 装置 RabbitMQSpring Boot and RabbitMQ demoRabbitMQ 提早队列Docker 装置 RabbitMQ第一步:拉取镜像 docker pull rabbitmq:management第二步:启动 docker run -d \--name rabbitmq \-p 5672:5672 \-p 15672:15672 \-v /data/rabbitmq:/var/lib/rabbitmq \--hostname myRabbit \-e RABBITMQ_DEFAULT_VHOST=my_vhost \-e RABBITMQ_DEFAULT_USER=admin \-e RABBITMQ_DEFAULT_PASS=admin \rabbitmq:management阐明: -d 后盾运行容器; --name 指定容器名; -p 指定服务运行的端口(5672:利用拜访端口;15672:控制台Web端口号); -v 映射目录或文件; --hostname 主机名(RabbitMQ的一个重要注意事项是它依据所谓的 “节点名称” 存储数据,默认为主机名); -e 指定环境变量;(RABBITMQ_DEFAULT_VHOST:默认虚拟机名;RABBITMQ_DEFAULT_USER:默认的用户名;RABBITMQ_DEFAULT_PASS:默认用户名的明码) Spring Boot and RabbitMQ demo依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId></dependency>配置 spring: rabbitmq: host: localhost port: 5672 username: admin password: admin virtual-host: my_vhost配置 ...

August 21, 2022 · 1 min · jiezi

关于springboot:spring-boot使用InfluxDB超简单三步搞定

InfluxDB和IotDB介绍与性能比照 Centos MacBook Docker离线装置InfluxDB超级简略 maven依赖InfluxDB<dependency> <groupId>org.influxdb</groupId> <artifactId>influxdb-java</artifactId> <version>2.14</version></dependency>InfluxDB配置spring: influx: url: http://192.168.1.5:8086 user: admin password: abcd_2021 database: demoInfluxDB配置类package com.beyond.data.config;import org.influxdb.InfluxDB;import org.influxdb.InfluxDBFactory;import org.influxdb.dto.BatchPoints;import org.influxdb.dto.Point;import org.influxdb.dto.Query;import org.influxdb.dto.QueryResult;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.concurrent.TimeUnit;@Component@Configurationpublic class InfluxDBConfig { @Value("${spring.influx.user}") private String userName; @Value("${spring.influx.password}") private String password; @Value("${spring.influx.url}") private String url; //数据库 @Value("${spring.influx.database}") private String database; //保留策略 private String retentionPolicy; private InfluxDB influxDB; public InfluxDBConfig(){} public InfluxDBConfig(String userName, String password, String url, String database) { this.userName = userName; this.password = password; this.url = url; this.database = database; // autogen默认的数据保留策略 this.retentionPolicy = retentionPolicy == null || "".equals(retentionPolicy) ? "autogen" : retentionPolicy; this.influxDB = influxDbBuild(); } /** * 设置数据保留策略 defalut 策略名 /database 数据库名/ 30d 数据保留时限30天/ 1 正本个数为1/ 结尾DEFAULT * 示意 设为默认的策略 */ public void createRetentionPolicy() { String command = String.format("CREATE RETENTION POLICY \"%s\" ON \"%s\" DURATION %s REPLICATION %s DEFAULT", "defalut", database, "30d", 1); this.query(command); } /** * 连贯时序数据库;取得InfluxDB **/ private InfluxDB influxDbBuild() { if (influxDB == null) { influxDB = InfluxDBFactory.connect(url, userName, password); influxDB.setDatabase(database); } return influxDB; } /** * 插入 * @param measurement 表 * @param tags 标签 * @param fields 字段 */ public void insert(String measurement, Map<String, String> tags, Map<String, Object> fields) { influxDbBuild(); Point.Builder builder = Point.measurement(measurement); builder.time(System.currentTimeMillis(), TimeUnit.MILLISECONDS); builder.tag(tags); builder.fields(fields); influxDB.write(database, "", builder.build()); } /** * @desc 插入,带工夫time * @date 2021/3/27 *@param measurement *@param time *@param tags *@param fields * @return void */ public void insert(String measurement, long time, Map<String, String> tags, Map<String, Object> fields) { influxDbBuild(); Point.Builder builder = Point.measurement(measurement); builder.time(time, TimeUnit.MILLISECONDS); builder.tag(tags); builder.fields(fields); influxDB.write(database, "", builder.build()); } /** * @desc influxDB开启UDP性能,默认端口:8089,默认数据库:udp,没提供代码传数据库性能接口 * @date 2021/3/13 *@param measurement *@param time *@param tags *@param fields * @return void */ public void insertUDP(String measurement, long time, Map<String, String> tags, Map<String, Object> fields) { influxDbBuild(); Point.Builder builder = Point.measurement(measurement); builder.time(time, TimeUnit.MILLISECONDS); builder.tag(tags); builder.fields(fields); int udpPort = 8089; influxDB.write(udpPort, builder.build()); } /** * 查问 * @param command 查问语句 * @return */ public QueryResult query(String command) { influxDbBuild(); return influxDB.query(new Query(command, database)); } /** * @desc 查问后果解决 * @date 2021/5/12 *@param queryResult */ public List<Map<String, Object>> queryResultProcess(QueryResult queryResult) { List<Map<String, Object>> mapList = new ArrayList<>(); List<QueryResult.Result> resultList = queryResult.getResults(); //把查问出的后果集转换成对应的实体对象,聚合成list for(QueryResult.Result query : resultList){ List<QueryResult.Series> seriesList = query.getSeries(); if(seriesList != null && seriesList.size() != 0) { for(QueryResult.Series series : seriesList){ List<String> columns = series.getColumns(); String[] keys = columns.toArray(new String[columns.size()]); List<List<Object>> values = series.getValues(); if(values != null && values.size() != 0) { for(List<Object> value : values){ Map<String, Object> map = new HashMap(keys.length); for (int i = 0; i < keys.length; i++) { map.put(keys[i], value.get(i)); } mapList.add(map); } } } } } return mapList; } /** * @desc InfluxDB 查问 count总条数 * @date 2021/4/8 */ public long countResultProcess(QueryResult queryResult) { long count = 0; List<Map<String, Object>> list = queryResultProcess(queryResult); if(list != null && list.size() != 0) { Map<String, Object> map = list.get(0); double num = (Double)map.get("count"); count = new Double(num).longValue(); } return count; } /** * 查问 * @param dbName 创立数据库 * @return */ public void createDB(String dbName) { influxDbBuild(); influxDB.createDatabase(dbName); } /** * 批量写入测点 * * @param batchPoints */ public void batchInsert(BatchPoints batchPoints) { influxDbBuild(); influxDB.write(batchPoints); } /** * 批量写入数据 * * @param database * 数据库 * @param retentionPolicy * 保留策略 * @param consistency * 一致性 * @param records * 要保留的数据(调用BatchPoints.lineProtocol()可失去一条record) */ public void batchInsert(final String database, final String retentionPolicy, final InfluxDB.ConsistencyLevel consistency, final List<String> records) { influxDbBuild(); influxDB.write(database, retentionPolicy, consistency, records); } /** * @desc 批量写入数据 * @date 2021/3/19 *@param consistency *@param records */ public void batchInsert(final InfluxDB.ConsistencyLevel consistency, final List<String> records) { influxDbBuild(); influxDB.write(database, "", consistency, records); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getDatabase() { return database; } public void setDatabase(String database) { this.database = database; } public String getRetentionPolicy() { return retentionPolicy; } public void setRetentionPolicy(String retentionPolicy) { this.retentionPolicy = retentionPolicy; } public InfluxDB getInfluxDB() { return influxDB; } public void setInfluxDB(InfluxDB influxDB) { this.influxDB = influxDB; }}调用@Autowiredprivate InfluxDBConfig influxDBConfig;......influxDBConfig.insert(measurement, tags, fields);

August 19, 2022 · 4 min · jiezi

关于springboot:通过一个具体的例子理解-npm-的-peerDependency

假如咱们有两个 npm module A 和 B,A 是 B 的 plugin. 如果 ABAP 的 package.json 里将 B 定义成其 dependency: { "dependencies": { "B": "1.2.0" }}那么咱们在 host 利用里装置 A 后,层级后果如下: node_modules|_ A |_ node_modules |_ B 假如咱们又装置了两个 module C 和 D,则 node_modules 文件夹变为如下构造: node_modules|_ A| |_ node_modules| |_ B|_C| |_ node_modules| |_ B|_D |_ node_modules |_ B 如果装置的 B 版本都是雷同的,这将起作用,然而,如果不是,咱们就会遇到潜在的问题。 当然咱们还疏忽了这样一个事实,即实际上,咱们将模块 B 复制了三次,这是毫无意义的。 这里的重点是,如果开发人员将 B 申明为 A、C 和 D 的 peer dependency 依赖项,则咱们抉择的包管理器会做两件事之一。它要么只是疏忽这种依赖关系(就像 Yarn 默认状况下所做的那样),让开发人员来自行作出抉择。 ...

August 19, 2022 · 1 min · jiezi

关于springboot:82springboot启动过程总结

springboot启动过程总结: 1、SpringBoot启动过程有两大步:①传入spring利用,而后创立spring利用 ②把spring利用跑起来- 创立 SpringApplication - 保留一些信息。 - 断定以后利用的类型。Servlet or 响应式编程 - bootstrappers:初始启动疏导器(List<Bootstrapper>):去spring.factories文件中找 org.springframework.boot.Bootstrapper - 找 ApplicationContextInitializer;去spring.factories找 ApplicationContextInitializer - 封装到 List<ApplicationContextInitializer<?>> initializers - 找ApplicationListener利用监听器。去spring.factories找 ApplicationListener - 封装到 List<ApplicationListener<?>> listeners简略来说,利用创立的过程,就是把一些要害的主键去spring.factories文件给咱们读取信息读取进去,提前保留到springapplication里- 运行 SpringApplication - StopWatch 进行监听器 - 记录利用的启动工夫 - 创立疏导上下文(Context环境)createBootstrapContext() - 获取到所有之前保留的 bootstrappers 挨个执行他们的 intitialize() 来实现对疏导启动器上下文环境设置 - 让以后利用进入headless模式。java.awt.headless (headless是自力更生的意思,不依赖他人) - 获取所有RunListener(运行监听器)【为了不便所有Listener进行事件感知】 - getSpringFactoriesInstances 去spring.factories找 SpringApplicationRunListener. - 遍历所有的 SpringApplicationRunListener调用 starting 办法; - 相当于告诉所有感兴趣零碎正在启动过程的人,我的项目正在 starting。(即通知我这个我的项目正在启动) - 保留命令行参数;ApplicationArguments - 筹备环境 ,调用 prepareEnvironment(); - 返回或者创立根底环境信息对象。StandardServletEnvironment - 配置环境信息对象。 - 读取所有的配置源的配置属性值。 - 绑定环境信息 - 遍历每个监听器,调用 listener.environmentPrepared();告诉所有的监听器以后环境筹备实现 - 创立IOC容器(调用createApplicationContext()) - 依据我的项目类型(Servlet)创立容器, - 以后会创立 AnnotationConfigServletWebServerApplicationContext - 筹备ApplicationContext IOC容器的根本信息 调用prepareContext() - 保留环境信息 - IOC容器的后置解决流程。 - 利用初始化器:applyInitializers; - 遍历所有的 ApplicationContextInitializer。调用 initialize.。来对ioc容器进行初始化扩大性能 - 遍历所有的 listener 调用 contextPrepared。实际上是EventPublishRunListenr:告诉所有的监听器contextPrepared容器的上下文环境曾经筹备好了 - 所有的监听器 调用contextLoaded。告诉所有的监听器 contextLoaded;容器的上下文环境曾经加载好了 - 刷新IOC容器。refreshContext - 创立容器中的所有组件 - 容器刷新实现后要做的工作(调用 afterRefresh()) - 遍历所有监听器 调用 listeners.started(context); 告诉所有的监听器started 我的项目曾经启动了(告诉监听器又一个事干好了)所以在这里就能够拜访8080了 - 调用所有runners; (调用 callRunners()) - 获取容器中的ApplicationRunner - 获取容器中的CommandLineRunner - 合并所有runner并且依照@Order进行排序 - 遍历所有的runner。调用 run办法 - 如果从`筹备环境 ,调用 prepareEnvironment()到这里` 有异样, - 调用Listener 的 failed - 没有任何异样就 调用所有监听器的 running 办法 listeners.running(context); 告诉所有的监听器 running我的项目曾经进入running状态了 - running如果有问题。持续告诉 failed 。调用所有 Listener 的failed;告诉所有的监听器failed 以后我的项目运行失败只有看到这个getSpringFactoriesInstances()办法,都是去spring.factories文件找货色springboot启动过程波及到的组件有:ApplicationContextInitializer, ApplicationListener, SpringApplicationRunListener, ApplicationRunner,CommandLineRunner咱们就能够通过自定义组件,来监控springboot整个启动过程,而后在某个时刻做一些事件

August 11, 2022 · 1 min · jiezi

关于springboot:81SpringBoot启动流程把spring应用跑起来

刷新IOC容器 refreshContext private void refreshContext(ConfigurableApplicationContext context) { if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } //spring的外围源码 refresh((ApplicationContext) context); }==============进入refresh((ApplicationContext) context);@Deprecatedprotected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext); refresh((ConfigurableApplicationContext) applicationContext); }protected void refresh(ConfigurableApplicationContext applicationContext) { applicationContext.refresh();}@Overridepublic final void refresh() throws BeansException, IllegalStateException { try { super.refresh(); } catch (RuntimeException ex) { WebServer webServer = this.webServer; if (webServer != null) { webServer.stop(); } throw ex; }}//spring ioc容器的经典的初始化过程public void refresh() throws BeansException, IllegalStateException { Object var1 = this.startupShutdownMonitor; synchronized(this.startupShutdownMonitor) { StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); this.prepareRefresh(); ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); this.prepareBeanFactory(beanFactory); try { this.postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); this.invokeBeanFactoryPostProcessors(beanFactory); this.registerBeanPostProcessors(beanFactory); beanPostProcess.end(); this.initMessageSource(); this.initApplicationEventMulticaster(); this.onRefresh(); this.registerListeners(); //实例化容器中所有的组件 this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var10) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10); } this.destroyBeans(); this.cancelRefresh(var10); throw var10; } finally { this.resetCommonCaches(); contextRefresh.end(); } } }callRunners(context, applicationArguments) ...

August 11, 2022 · 2 min · jiezi

关于springboot:81-SpringBoot启动流程把spring应用跑起来

创立IOC容器 createApplicationContext() protected ConfigurableApplicationContext createApplicationContext() { return this.applicationContextFactory.create(this.webApplicationType); }=============依据以后的利用类型(Servlet)创立容器//以后会创立AnnotationConfigServletWebServerApplicationContextApplicationContextFactory DEFAULT = (webApplicationType) -> { try { switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); } } catch (Exception ex) { throw new IllegalStateException("Unable create a default ApplicationContext instance, " + "you may need a custom ApplicationContextFactory", ex); } };筹备ApplicationContext IOC容器的根本信息 prepareContext() private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { //保留后面的根底环境 context.setEnvironment(environment); //IOC容器的后置解决流程。 postProcessApplicationContext(context); //利用初始化器:applyInitializers applyInitializers(context); //遍历所有的 listener 调用 contextPrepared //ioc容器的环境准备就绪了(contextPrepared办法的源码在上面) listeners.contextPrepared(context); //疏导上下文的环境敞开了 bootstrapContext.close(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans //拿到bean工厂 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //注册单实例 beanFactory.registerSingleton("springApplicationArguments", applicationArguments); //打印banner if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.lazyInitialization) { context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); //listeners:监听器 listeners.contextLoaded(context); }================== postProcessApplicationContext(context);protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) {//注册组件 context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { //读取配置文件的资源 if (context instanceof GenericApplicationContext) { ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { //资源加载器 ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); } } //筹备类型转换器 if (this.addConversionService) { context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } }===================applyInitializers(context)protected void applyInitializers(ConfigurableApplicationContext context) {//遍历所有的 ApplicationContextInitializer。调用 initialize。来对ioc容器进行初始化扩大性能 for (ApplicationContextInitializer initializer : getInitializers()) { //之前从spring.factories找进去的七个Initializer Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(), ApplicationContextInitializer.class); Assert.isInstanceOf(requiredType, context, "Unable to call initializer."); initializer.initialize(context); } }@Override public void initialize(ConfigurableApplicationContext context) { ConfigurableEnvironment environment = context.getEnvironment(); List<Class<?>> initializerClasses = getInitializerClasses(environment); //如果initializerClasses不为空,就把他们实例化。 if (!initializerClasses.isEmpty()) { applyInitializerClasses(context, initializerClasses); } }===========contextPrepared()void contextPrepared(ConfigurableApplicationContext context) { doWithListeners("spring.boot.application.context-prepared", (listener) -> listener.contextPrepared(context));}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) { doWithListeners(stepName, listenerAction, null);}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) { StartupStep step = this.applicationStartup.start(stepName); //拿到所有的listeners //实际上是EventPublishRunListenr:告诉所有的监听器contextPrepared容器的上下文环境曾经筹备好了 this.listeners.forEach(listenerAction); if (stepAction != null) { stepAction.accept(step); } step.end();}================contextLoaded()void contextLoaded(ConfigurableApplicationContext context) { doWithListeners("spring.boot.application.context-loaded", (listener) -> listener.contextLoaded(context));}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) { doWithListeners(stepName, listenerAction, null);}private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction, Consumer<StartupStep> stepAction) { StartupStep step = this.applicationStartup.start(stepName); //所有的监听器去调用contextLoaded(context) //告诉所有的监听器contextLoaded;容器的上下文环境曾经加载好了 this.listeners.forEach(listenerAction); if (stepAction != null) { stepAction.accept(step); } step.end();}(listener) -> listener.contextLoaded(context)

August 11, 2022 · 2 min · jiezi

关于springboot:81SpringBoot启动流程把spring应用跑起来

筹备环境 : private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) { // Create and configure the environment //如果以后有上下文环境信息,就获取并返回;没有就创立 //比方保留底层运行的环境变量 ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置环境(拿到环境信息,命令行参数) configureEnvironment(environment, applicationArguments.getSourceArgs()); //绑定环境信息 ConfigurationPropertySources.attach(environment);//监听器调用 listener.environmentPrepared();告诉所有的监听器以后环境筹备实现 listeners.environmentPrepared(bootstrapContext, environment);//将环境信息设置到类的最初(这个不必管) DefaultPropertiesPropertySource.moveToEnd(environment); //配置额定的Profiles,激活哪些环境(不必管) configureAdditionalProfiles(environment); //绑定工作(都是找到一些货色,把一些属性绑定到某个值外面) bindToSpringApplication(environment); //又是筹备一些环境信息 if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } //筹备完了后,把环境信息再次绑定 ConfigurationPropertySources.attach(environment); return environment; }=================进入getOrCreateEnvironment()private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: //如果是原生servlet编程 return new StandardServletEnvironment(); case REACTIVE: //如果是响应式编程 return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }====================进入configureEnvironment()protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { //给环境增加一些类型转换器 if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } //加载(读取)内部配置源 //即读取所有的配置源的配置属性值 configurePropertySources(environment, args); //激活Profiles环境 configureProfiles(environment, args); }==========进入configurePropertySources()protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { //获取到所有配置源 MutablePropertySources sources = environment.getPropertySources(); DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast); //命令行信息 if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }==============进入environmentPrepared()void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { doWithListeners("spring.boot.application.environment-prepared", (listener) -> listener.environmentPrepared(bootstrapContext, environment)); }所有配置源 ...

August 11, 2022 · 1 min · jiezi

关于springboot:81-SpringBoot启动流程把spring应用跑起来

start办法里的源码 public void start() throws IllegalStateException { this.start(""); } public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { throw new IllegalStateException("Can't start StopWatch: it's already running"); } else { //给stopWatch对象保留一些信息 this.currentTaskName = taskName;//当前任务的名字 this.startTimeNanos = System.nanoTime();//以后的工夫 }createBootstrapContext创立疏导上下文 private DefaultBootstrapContext createBootstrapContext() { //创立默认的疏导上下文对象 //当前就会给外面保留很多信息 DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();//遍历所有之前的获取到的bootstrappers,调用每一个bootstrappers的intitialize() 来实现对疏导启动器上下文环境设置 this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext)); return bootstrapContext;//null }=============================================================public interface Bootstrapper { /** * Initialize the given {@link BootstrapRegistry} with any required registrations. * @param registry the registry to initialize *///传入疏导注册工厂//这个工厂外面能够给他退出一些以后上下文的一些疏导环境信息 void intitialize(BootstrapRegistry registry);}让以后利用进入headless模式 ...

August 11, 2022 · 1 min · jiezi

关于springboot:81SpringBoot启动流程把spring应用跑起来

把spring利用跑起来开始进入run办法: public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }step into run() public ConfigurableApplicationContext run(String... args) { //进行监听器,监控整个利用的启动和进行的 StopWatch stopWatch = new StopWatch(); //start办法里的源码能够看上面代码展现 stopWatch.start();//记录利用的启动工夫 //创立疏导上下文(xxxContext都是疏导环境) DefaultBootstrapContext bootstrapContext = createBootstrapContext();//createBootstrapContext办法的源码在上面 ConfigurableApplicationContext context = null; //让以后利用进入headless模式。源码在上面 //(headless是自力更生的意思,不依赖他人) configureHeadlessProperty();//获取所有RunListener(运行监听器)保留到SpringApplicationRunListeners//【为了不便所有Listener进行事件感知】所谓的监听器就是监听到以后我的项目的状态 SpringApplicationRunListeners listeners = getRunListeners(args); //starting()源码剖析在上面 listeners.starting(bootstrapContext, this.mainApplicationClass); try { //筹备ApplicationArguments利用参数,利用参数是从命令行里传过来的。即保留命令行参数 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); //筹备运行时环境 ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);//这行代码完结后,所有的环境信息曾经准备就绪 //配置一些要疏忽的bean信息 configureIgnoreBeanInfo(environment); //打印banner(banner是啥,能够看上面) Banner printedBanner = printBanner(environment); //创立ioc容器(容器创立实现后,所有的bean都会进去) context = createApplicationContext();//源码在上面 //把Startup信息保存起来 context.setApplicationStartup(this.applicationStartup); //筹备ioc容器环境的信息(源码在上面) prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner); //刷新ioc容器(办法的源码在上面) refreshContext(context);//===============到这里为止,ioc容器就创立实现了================== //容器刷新实现后要做的工作 afterRefresh(context, applicationArguments); //监听到容器全副启动实现 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } //遍历所有监听器 调用 listeners.started(context); //告诉所有的监听器started 我的项目曾经启动了(告诉监听器又一个事干好了)所以在这里就能够拜访8080了 listeners.started(context); //调用所有runners callRunners(context, applicationArguments); } catch (Throwable ex) { //如果有异样,还会捕捉异样(捕捉源码在上面) handleRunFailure(context, ex, listeners); throw new IllegalStateException(ex); } try { //没有任何异样就调用所有监听器的 running 办法 listeners.running(context); //告诉所有的监听器 running我的项目曾经进入running状态了 listeners.running(context); } catch (Throwable ex) { //如果有异样,持续捕捉异样 handleRunFailure(context, ex, null); throw new IllegalStateException(ex); } return context; }banner: ...

August 11, 2022 · 1 min · jiezi

关于springboot:82SpringBoot启动流程把spring应用跑起来接81

=================进入getOrCreateEnvironment()private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: //如果是原生servlet编程 return new StandardServletEnvironment(); case REACTIVE: //如果是响应式编程 return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }====================进入configureEnvironment()protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { //给环境增加一些类型转换器 if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } //加载(读取)内部配置源 //即读取所有的配置源的配置属性值 configurePropertySources(environment, args); //激活Profiles环境 configureProfiles(environment, args); }==========进入configurePropertySources()protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { //获取到所有配置源 MutablePropertySources sources = environment.getPropertySources(); DefaultPropertiesPropertySource.ifNotEmpty(this.defaultProperties, sources::addLast); //命令行信息 if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }==============进入environmentPrepared()void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { doWithListeners("spring.boot.application.environment-prepared", (listener) -> listener.environmentPrepared(bootstrapContext, environment)); }所有配置源 ...

August 10, 2022 · 2 min · jiezi

关于springboot:www

ww为什么公布不了文章

August 10, 2022 · 1 min · jiezi

关于springboot:微服务架构的外部-API-集成模式

明天咱们来聊聊API集成,通过前两天的理解,咱们理解到微服务是多服务,松耦合的服务集,既然波及到了多服务,调用内部的API的必不可少的。因为客户的多样性,设计应用程序的内部 API 变得更具备挑战性。这些客户端通常具备不同的数据要求。 1、间接沟通这种形式以客户端间接调用服务的形式设计 API。因为以下毛病,微服务架构目前很少采纳这种办法。客户端必须通过细粒度的服务 API 收回多个申请来检索他们须要的数据,这不仅效率低下,而且可能导致比拟差的用户体验。因为客户端对每个服务及其 API 的理解导致不足封装,因而很难更改架构和 API。客户可能感觉应用服务的 IPC 机制既不不便也不实用。 咱们在研发的时候,不能把放弃向后兼容性留给后端服务的开发人员。须要开发独自的公共 API,而不是间接向第三方公开服务。这项艰巨的工作就是由API 网关的架构组件实现的。 2、API 网关模式间接拜访服务有许多毛病。API 网关是一种更好服务拜访模式。基本上,API 网关也是一种服务,它充当内部应用程序的入口点。该组件负责申请路由、API 组合和其余横切关注点,例如身份验证、监控和速率限度。API 网关相似于 OOPS(设计模式) 中的外观设计模式。API 网关封装应用程序的外部架构并向其客户端提供 API。API 网关还负责申请路由、API 组合和协定转换。来自内部客户端的 API 申请通过 API 网关,API网关将一些申请路由到相应的服务。API 网关还能够通过调用多个服务并聚合后果来解决其余申请。服务器还能够在客户端敌对协定(例如 HTTP 和 WebSockets)与服务应用的客户端不敌对协定之间进行转换。 3、申请路由申请路由是 API 网关最重要的性能之一。API 网关通过将申请路由到适当的服务来实现 API 操作。API 网关在收到申请时查问路由映射以确定将申请路由到哪个服务。例如,路由映射可能会将 HTTP 办法和门路映射到服务的 HTTP URL。NGINX 等 Web 服务器提供反向代理作为此性能的一部分。 4、API 组成API 网关通常不仅仅做反向代理。他们还能够应用 API 组合执行 API 操作。API 网关为客户端提供了一个粗粒度的 API,使他们可能通过一个申请来检索他们须要的数据。聚合器/组合模式有两个子模式。 链式模式:基本上,这种类型的组合模式遵循链式构造。在这种模式下,客户端与服务通信,所有服务链接在一起,一个服务的输入成为下一个服务的输出。分支模式:分支模式是聚合器和链模式的扩大版本。客户端能够间接与服务通信,在这种设计模式下,服务能够同时与多个服务通信。5、协定转换API 网关也能够转换协定。只管应用程序服务在外部应用各种协定,包含 REST 和 gRPC,但它可能会向内部客户端提供 REST API。一些 API 操作在须要时在 RESTful 内部 API 和外部基于 gRPC 的 API 之间进行转换。国外一些技术网站,把这项性能间接称为:Protocol Translation,间接翻译就是:协定翻译,我依据能力间接叫做:协定转换 ...

August 9, 2022 · 1 min · jiezi

关于springboot:记一次springboot项目结合arthas排查ClassNotFoundException问题

前言前阵子业务部门的我的项目呈现了一个很奇怪的问题,有个class明明存在,本地idea运行也没问题,而后一公布线上就呈现ClassNotFoundException问题,而且线上这个class的确是存在的。本文就通过一个demo示例来复现这么一个状况 demo示例注: 本文的我的项目框架为springboot2。本文仅演示ClassNotFoundException相干内容,并不模仿业务流 业务服务A package com.example.helloloader.service;import org.springframework.stereotype.Service;@Servicepublic class HelloService { public String hello(){ return "hello loader"; }}组件B @Componentpublic class HelloServiceLoaderUtils implements ApplicationContextAware { private ApplicationContext applicationContext; public String invoke(String className){ try { ClassLoader classLoader = ClassLoader.getSystemClassLoader(); Class clz = classLoader.loadClass(className); Object bean = applicationContext.getBean(clz); Method method = clz.getMethod("hello"); return (String) method.invoke(bean); } catch (Exception e) { e.printStackTrace(); } return null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}服务A调用组件B ...

August 9, 2022 · 1 min · jiezi

关于springboot:记录遇到的mongDb问题-以及restTemplate请求重试

最近应用mongDb的时候,呈现了一些问题。 mongDb 实体id自增在保留实体的时候报了这个错 Cannot aotogenerate id of type java.lang.Long for entity即 不能主动生成id 之后就带着报错去谷歌了一下。 失去了如下后果: Mongo ObjectIds 与 jave Long 类型不匹配。 先来介绍一下MongoDB 的对象 Id(ObjectId) ObjectIdObjectId 是一个12字节 BSON 类型数据,有以下格局: 前4个字节示意工夫戳接下来的3个字节是机器标识码紧接的两个字节由过程id组成(PID)最初三个字节是随机数。 例如 62ee651f cabe7f 59dc f10372 从 MongoDB 3.4 开始(最早公布于 2016 年 12 月),ObjectId 的设计被批改了,两头 5 字节的值由原先的“机器标识码+过程号”改为单纯随机值。 a 4-byte value representing the seconds since the Unix epoch,a 5-byte random value, anda 3-byte counter, starting with a random value.MongoDB中存储的文档必须有一个"_id"键。这个键的值能够是任何类型的,默认是个ObjectId对象。 在一个汇合外面,每个文档都有惟一的"_id"值,来确保汇合外面每个文档都能被惟一标识。 MongoDB采纳ObjectId,而不是其余比拟惯例的做法(比方主动减少的主键)的次要起因,因为在多个 服务器上同步主动减少主键值既费劲还费时。 ...

August 8, 2022 · 2 min · jiezi

关于springboot:Spring-Boot结合Swegger

增加依赖 <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.8.0</version> </dependency>配置SweggerConfig/** * @Api: 用于类,标识这个类是swagger的资源 * @ApiIgnore: 用于类,疏忽该 Controller,指不对以后类做扫描 * @ApiOperation: 用于办法,形容 Controller类中的 method接口 * @ApiParam: 用于参数,单个参数形容,与 @ApiImplicitParam不同的是,他是写在参数左侧的。如( @ApiParam(name="username",value="用户名")Stringusername) * @ApiModel: 用于类,示意对类进行阐明,用于参数用实体类接管 * @ApiProperty:用于办法,字段,示意对model属性的阐明或者数据操作更改 * @ApiImplicitParam: 用于办法,示意独自的申请参数 * @ApiImplicitParams: 用于办法,蕴含多个 @ApiImplicitParam * @ApiResponse: 用于办法,形容单个出参信息 * @ApiResponses: 用于办法,蕴含多个@ApiResponse * @ApiError: 用于办法,接口谬误所返回的信息 * * * Failed to start bean ‘documentationPluginsBootstrapper‘;解决办法 * 在application.properties里配置里增加: * spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER * 起因: 这是因为Springfox应用的门路匹配是基于AntPathMatcher的,而Spring Boot 2.6.X应用的是PathPatternMatcher。 */@Configuration // 表明是配置类@EnableSwagger2 //开启swagger性能public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) // DocumentationType.SWAGGER_2 固定的,代表swagger2// .groupName("分布式工作零碎") // 如果配置多个文档的时候,那么须要配置groupName来分组标识 .apiInfo(apiInfo()) // 用于生成API信息 .select() // select()函数返回一个ApiSelectorBuilder实例,用来管制接口被swagger做成文档 .apis(RequestHandlerSelectors.basePackage("com.jsd.controller")) // 用于指定扫描哪个包下的接口 .paths(PathSelectors.any())// 抉择所有的API,如果你想只为局部API生成文档,能够配置这里 .build(); } /** * 用于定义API主界面的信息,比方能够申明所有的API的总题目、形容、版本 * @return */ private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("测试") // 能够用来自定义API的主题目 .description("XX我的项目SwaggerAPI治理") // 能够用来形容整体的API .termsOfServiceUrl("") // 用于定义服务的域名 .version("1.0") // 能够用来定义版本。 .build(); // }}Testcontroller@Api(tags = "测试治理")@RestControllerpublic class TestController { @ApiOperation("测试方法") @GetMapping("/test") public String Test01(){ return "Test01"; }}

August 8, 2022 · 1 min · jiezi

关于springboot:SpringApplication创建初始化流程

有两大步:①创立spring利用 ②把spring利用跑起来一、创立SpringApplication@SpringBootApplicationpublic class Boot09HelloTestApplication { public static void main(String[] args) { SpringApplication.run(Boot09HelloTestApplication.class, args); }}step into应用new class把主程序类传进来,而后调用run办法 public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class[]{primarySource}, args);}step into run办法第一步,创立spring利用。第二步把spring利用跑起来 public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return (new SpringApplication(primarySources)).run(args);}step into public SpringApplication(Class... primarySources) {//调用本类有参结构器,传入两个参数 this((ResourceLoader)null, primarySources); }step into this办法初始化了好多属性 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { //第一个属性,资源加载器resourceLoader this.resourceLoader = resourceLoader; //断言你有主配置类,如果没有,则报错 Assert.notNull(primarySources, "PrimarySources must not be null"); //把主配置类信息保存起来, this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); //决定以后web利用的类型。如何决定的看上面的源码 this.webApplicationType = WebApplicationType.deduceFromClasspath(); //bootstrappers:初始启动疏导器(我的项目一启动就要干什么)。如何获取的看上面的源码 this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class)); //设置初始化器。只有看到getSpringFactoriesInstances这个办法,都是去META-INF/spring.factories文件中有没有配置某个类类,它的值是什么 //如果找到了ApplicationContextInitializer,就会放到List<ApplicationContextInitializer<?>> initializers里 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //设置监听器。去spring.factories文件中有没有配置ApplicationListener类,它的值是什么 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); //找主程序。决定哪个是主程序。如何决定的看上面的源码剖析 this.mainApplicationClass = deduceMainApplicationClass(); }如何决定以后web利用的类型??Servlet or 响应式编程? ...

August 8, 2022 · 2 min · jiezi

关于springboot:微服务架构的数据设计模式

最近参加公司我的项目研发,在其中发现对于数据的治理存在一些小问题,依据以往教训,在这里记录下微服务数据设计模式。微服务架构中的服务是松耦合的,能够独立开发、部署和扩大。每个微服务都须要不同类型的数据和存储形式,也因为这样每个微服务都有本人的数据库。一、每个服务的数据库每个微服务都有本人的数据库,能够自由选择如何治理数据。1.1 每个服务都有一个数据库的益处 松耦合,各自服务能够更加专一本人的业余畛域自由选择数据库类型,如 MySQL 等 RDBMS、Cassandra 等宽列数据库、MongoDB 等文档数据库、Redis 等键值存储和 Neo4J 等图形数据库。 是否须要为每个服务应用不同的数据库服务器?这不是一个硬性要求。让咱们看看咱们能做些什么。1.2 如果您应用的是 RDMS,那么就包含以下个性: 专用表—每个服务领有一组表,只能由该服务拜访。专用数据库架构 —每个服务都有一个公有的数据库架构。专用数据库服务器 —每个服务都有本人的数据库服务器。 1.3 每个服务都有一个数据库的挑战须要连贯多个数据库的查问 —以下数据模式能够克服这一挑战。 事件溯源API 组成命令查问职责拆散 (CQRS) 跨多个数据库事务 —为了解决这个问题,咱们能够应用Saga 模式。 二、事件溯源通过事件溯源,业务实体的状态由一系列状态变动的事件跟踪。每当业务实体的状态发生变化时,都会将新事件增加到事件列表中。因为保留事件是一个繁多的操作,它实质上是原子的。通过重放事件,应用程序重建实体的以后状态。应用程序将事件保留在事件存储中,事件存储是事件数据库。能够应用其 API 从存储中增加和检索事件。事件存储也充当音讯代理。服务能够通过其 API 订阅事件。当服务在事件存储中保留一个事件时,它会发送给所有感兴趣的订阅者。当实体有大量事件时,应用程序能够定期保留实体以后状态的快照以优化加载。应用程序查找最近的快照以及自该快照以来产生的事件以重建以后状态。这缩小了要重播的事件的数量。 2.1 事件溯源的益处 应用它解决了事件驱动架构的要害挑战之一,并使得在状态变动时牢靠地公布事件。防止了对象关系阻抗不匹配问题,长久化事件而不是域对象。对实体提供 100% 牢靠的审计日志。容许执行确定实体在任何工夫点的状态的工夫查问。基于事件溯源的业务逻辑波及替换事件的涣散耦合实体。使从单体应用程序迁徙到微服务架构变得容易得多。 2.2 事件溯源的毛病 有肯定学习老本,目前还是一种不太成熟的技术。查问事件存储很艰难,须要一个典型的查问来重建实体状态。可能会导致低效和简单的查问。因而,应用程序必须应用命令查问职责拆散 (CQRS) 来实现查问。反过来,这意味着应用程序必须解决最终统一的数据。 三、API 组成您能够应用 API 组合模式实现从多个服务中检索数据的查问操作。在这个模式中,通过调用领有数据的服务而后组合后果来实现查问操作。 3.1 API 组合的益处在微服务架构中查问数据的一种便捷形式。3.2 API组合的毛病有时,查问会导致大型数据集的低效内存连贯。 四、命令查问职责拆散 (CQRS)RDBMS 通常用作记录事务零碎和文本搜寻数据库,例如用于文本搜寻查问的 Elasticsearch 或 Solr。一些应用程序通过同时写入两者来放弃数据库同步。其他人定期将数据从 RDBMS 复制到文本搜索引擎。基于此架构构建的应用程序利用了多个数据库的劣势、RDBMS 的事务属性以及文本数据库的查问能力。CQRS 概括了这种架构。微服务架构在实现查问时面临三个常见挑战。 应用 API 组合模式检索扩散在多个服务中的数据,从而导致老本昂扬且效率低下的内存连贯。数据以不能无效反对领有数据的服务所需查问的格局或数据库中存储。拆散关注点意味着领有数据的服务不应该负责实现查问操作。 这三个问题都能够通过应用 CQRS 模式来解决。 CQRS 的次要指标是拆散或拆散关注点。因而,持久数据模型分为两局部:命令端和查问端。创立、更新和删除操作由命令端模块和数据模型实现。查问由查问端模块和数据模型实现。通过订阅命令行公布的事件,查问端放弃其数据模型与命令端同步 ...

August 7, 2022 · 1 min · jiezi

关于springboot:一文读懂微服务架构的分解设计

如果您在设计大型并发应用程序或者筹备拆解之前的老零碎时,我想你第一思考的是微服务架构形式。后面咱们理解到微服务架构将应用程序构建为一系列涣散耦合的服务,是为了通过实现继续交付和灵便部署来减速软件开发。出于很起因,合成很重要 有利于分工和常识共享。应用它,具备非凡常识的多集体(或团队)能够在一个应用程序上高效地单干。它形容了多个元素如何交互。 在微服务下,有两种类型的我的项目 待从新开发我的项目—国外译名:Brownfield projects,是指在现有或遗留零碎的背景下开发和部署新的软件系统。因而,将单体利用程序转换为微服务是属于这种类型我的项目。新建我的项目——是指从头开始为一个全新的零碎,而无需应用任何遗留代码。当您从头开始时,没有任何限度或依赖性。 一、按业务能力模式合成为了创立微服务架构,一种策略是基于业务能力进行合成。作为一家企业,我的项目是为了发明价值。例如,在电子商务业务中,订单治理、库存治理、领取、运输等都波及。 这种模式有以下益处 业务能力比较稳定,架构依赖的业务逻辑比较稳定。开发团队是跨职能的、自主的,并且围绕交付业务价值而非技术个性进行组织。服务是涣散耦合和内聚的。 二、按子域模式合成畛域驱动设计 (DDD) 办法是一种构建简单软件应用程序的办法,它基于面向对象畛域模型的开发。DDD 为每个子域定义了独自地区模型。每个子域都属于一个域。辨认子畛域与辨认业务能力的过程比拟类似,即剖析业务和辨认业余畛域。最有可能的是,大多数是业务相熟的子域。畛域模型的范畴在 DDD 中称为有界上下文。有界上下文包含实现模型的代码组件。 子域能够分类如下 外围—业务的最大差异化因素和应用程序最有价值的局部,在一些公司常常有外围零碎我的项目,有外围报价子系统,外围定价子系统等反对—不是差异化因素,而是与业务提供的内容相干。通常在外部或外包施行。通用—不特定于业务,最好应用现成的软件施行。 这种模式有以下益处 子域职能比较稳定,架构绝对也比较稳定。开发团队(通常会设计到组建虚构团队)是跨职能的、自主的,并且专一于交付业务价值而不是技术个性。服务是涣散耦合和内聚的。 三、将单体应用程序合成为微服务时的挑战在合成单体应用程序时,可能会呈现挑战。 网络提早—在分布式系统中,网络提早是一个继续关注的问题。您可能会发现对服务的特定合成会导致两个服务之间的大量往返。数据一致性—每个服务都有本人的数据库,因而保护跨服务的数据一致性会十分艰难。神类(捂脸哭一下)—神类是控制系统中太多其余对象的对象,它超过了逻辑,成为了无所不能的类。因为其规模和复杂性,它是一个集中零碎智能的类,并应用来自其余类的信息。 四、扼杀者模式将遗留的单体应用程序迁徙到微服务架构时,会应用 Strangler 模式。通过用新服务替换特定性能,能够应用这种模式逐渐转换单体应用程序。新服务一旦筹备好,旧组件就被扼杀,新服务投入使用,而旧组件服役。 单体利用最终会放大性能,而微服务将接管整体性能。

August 6, 2022 · 1 min · jiezi

关于springboot:SpringBootUniapp实战开发全新仿抖音短视频App某课内置资料

download:SpringBoot+Uniapp实战开发全新仿抖音短视频AppJava线程池Executor详解咱们最常使用的Executors实现创建线程池使用线程次要是用上述类图中提供的类。在上边的类图中,蕴含了一个Executor框架,它是一个根据一组执行策略的调用调度执行和管制异步工作的框架,目标是提供一种将工作提交与工作如何运行别来到的机制。它蕴含了三个executor接口: Executor:运行新工作的简略接口ExecutorService:扩大了Executor,增加了用来治理执行器生命周期和工作生命周期的方法ScheduleExcutorService:扩大了ExecutorService,反对Future和定期执行工作 线程池的好处 升高资源消耗-重用存在的线程,缩小对象创建、沦亡的开销,性能好提高响应速度 -可无效管制最大并发线程数,提高系统资源应用率,同时可能避免过多资源竞争,避免阻塞。当工作到达时,工作可不必等待线程创建就能立即执行提高线程的可管理性-提供定时执行、定期执行、单线程、并发数管制等功能。 new Thread的弊病 每次new Thread 新建对象,性能差线程不足对立治理,可能无限度的新建线程,相互竞争,可能占用过多的系统资源导致死机或者OOM(out of memory 内存溢出),这种问题的原因不是因为单纯的new一个Thread,而是可能因为程序的bug或者设计上的缺点导致不断new Thread造成的。缺少更多功能,如更多执行、定期执行、线程中断。 线程池核心类-ThreadPoolExecutor参数说明:ThreadPoolExecutor一共有七个参数,这七个参数配合起来,形成了线程池弱小的功能。 corePoolSize:核心线程数量maximumPoolSize:线程最大线程数workQueue:阻塞队列,存储等待执行的工作,很重要,会对线程池运行过程产生重大影响 当咱们提交一个新的工作到线程池,线程池会根据以后池中正在运行的线程数量来决定该工作的处理形式。处理形式有三种:1、间接切换(SynchronusQueue)2、无界队列(LinkedBlockingQueue)能够创建的最大线程数为corePoolSize,这时maximumPoolSize就不会起作用了。当线程池中所有的核心线程都是运行状态的时候,新的工作提交就会放入等待队列中。3、有界队列(ArrayBlockingQueue)最大maximumPoolSize,能够升高资源消耗,然而这种形式使得线程池对线程调度变的更艰巨。因为线程池与队列容量都是无限的。所以想让线程池的吞吐率和处理工作达到一个正当的范畴,又想使咱们的线程调度绝对简略,并且还尽可能升高资源的消耗,咱们就需要正当的限度这两个数量调配技巧: [如果想升高资源的消耗包含升高cpu使用率、操作系统资源的消耗、上下文切换的开销等等,可能设置一个较大的队列容量和较小的线程池容量,这样会升高线程池的吞吐量。如果咱们提交的工作常常发生阻塞,咱们可能调整maximumPoolSize。如果咱们的队列容量较小,咱们需要把线程池大小设置的大一些,这样cpu的使用率相对来说会高一些。然而如果线程池的容量设置的过大,提高工作的数量过多的时候,并发量会减少,那么线程之间的调度就是一个需要考虑的问题。这样反而可能会升高处理工作的吞吐量。] keepAliveTime:线程没有工作执行时最多保持多久工夫终止(当线程中的线程数量大于corePoolSize的时候,如果这时没有新的工作提交核心线程外的线程不会立即销毁,而是等待,直到超过keepAliveTime)unit:keepAliveTime的工夫单位threadFactory:线程工厂,用来创建线程,有一个默认的工场来创建线程,这样新创建进去的线程有雷同的优先级,是非守护线程、设置好了名称)rejectHandler:当拒绝处理工作时(阻塞队列满)的策略(AbortPolicy默认策略间接抛出异样、CallerRunsPolicy用调用者所在的线程执行工作、DiscardOldestPolicy抛弃队列中最靠前的工作并执行当前任务、DiscardPolicy间接抛弃当前任务)corePoolSize、maximumPoolSize、workQueue 三者关系:如果运行的线程数小于corePoolSize的时候,间接创建新线程来处理工作。即使线程池中的其余线程是空闲的。如果运行中的线程数大于corePoolSize且小于maximumPoolSize时,那么只有当workQueue满的时候才创建新的线程去处理工作。如果corePoolSize与maximumPoolSize是雷同的,那么创建的线程池大小是固定的。这时有新工作提交,当workQueue未满时,就把请求放入workQueue中。等待空线程从workQueue取出工作。如果workQueue此时也满了,那么就使用另外的拒绝策略参数去执行拒绝策略。

August 5, 2022 · 1 min · jiezi

关于springboot:Lombok的Data类注解导致被JsonProperty注解字段被序列化了两次

问题形容因为接口传参须要序列化对象,且字段名是全大写例如ZPRODUCT须要传输的格局为 { "ZPRODUCT":"afb"}而如果不加JsonProperty注解会为 { "zproduct":"afb"}这不是咱们想要的,因此咱们应用@JsonProperty("ZPRODUCT")来申明该字段序列化为咱们定义的全大写,同时咱们我的项目中应用到了Lombok,应用@Data注解来生成get|set等办法,当咱们序列化之后发现序列化字符串为 { "ZPRODUCT":"afb", "zproduct":"afb"}没错,多了一个属性。很显著的序列化问题,而且必然和Lombok存在关系,所以咱们关上编译后的Class文件发现setZPRODUCT办法上没有被注解@JsonProperty("ZPRODUCT") 解决方案1:lombok更改为1.18.14,没错这个版本能够,我之前应用的为1.18.22比照两个版本生成的class文件能够发现14版本的set办法上有@JsonProperty("ZPRODUCT")注解而22版本上没有2: 去掉@Data注解,本人写get|set办法,并加上注解@JsonProperty("ZPRODUCT")

August 5, 2022 · 1 min · jiezi

关于springboot:微服务系列二微服务架构面临的挑战

微服务零碎绝对于以往的单体零碎更为简单。在构建的时候,研发团队必须要治理和反对很多组件,环境会变得更加简单。上面是我以往构建微服务零碎时整顿的一些次要挑战。 一、限界上下文限界上下文概念起源于畛域驱动设计 (DDD) 圈子。它的呈现促成了优先对象模型的服务办法,定义了服务责任和绑定的数据模型。有边界的上下文廓清、封装并定义了模型的特定责任。每个模型都必须在子域内隐式定义一个上下文,并且每个上下文都定义了边界。换句话说,服务领有其数据并对其完整性和可变性负责。它反对微服务最重要的个性:独立性和解耦。二、动静扩大和缩减不同微服务上的负载可能在不同类型的实例上。除了主动扩大你的微服务应该主动缩减。升高了微服务的老本,能够动态分配负载。三、监控传统的监控形式与微服务差异性很大,微服务有多个服务组成了单个应用程序反对的雷同性能。当应用程序中呈现谬误时,找到根本原因具备很大的挑战性。四、容错容错是不会升高整个零碎的单个服务。当故障产生时,应用程序能够在肯定的满意度下运行。如果没有容错能力,零碎中的单个故障可能会导致齐全解体。断路器能够实现容错,它是一种将申请包装到内部服务,并检测它们何时呈现故障的模式。微服务须要肯定水平上容忍外部和内部故障。五、循环依赖跨不同服务的依赖治理,这个性能十分重要。如果不及时辨认和解决,循环依赖可能会产生问题,当依赖关系造成了环,最终导致:在装置 A 软件包之前,必须要装置 A、B、C、D 软件包六、DevOps 文化微服务非常适合 DevOps。它提供更快的交付服务、跨数据的可见性和具备老本效益的数据。它能够将他们对容器化转换的应用从面向服务的架构 (SOA) 扩大到微服务架构 (MSA)。微服务的其余挑战 随着咱们增加更多微服务,咱们必须确保它们能够一起扩大。更多的粒度意味着更多的组件,减少了零碎的复杂性。传统的日志记录是有效的,因为微服务是无状态的、分布式的、独立的。日志记录必须可能跨多个平台关联事件。当更多的服务互相交互时,失败的可能性也会减少。

August 5, 2022 · 1 min · jiezi

关于springboot:从99打造Sentinel高可用集群限流中间件

接上篇Sentinel集群限流摸索,上次简略提到了集群限流的原理,而后用官网给的 demo 简略批改了一下,能够失常运行失效。 这一次须要更进一步,基于 Sentinel 实现内嵌式集群限流的高可用计划,并且包装成一个中间件 starter 提供给三方应用。 对于高可用,咱们次要须要解决两个问题,这无论是应用内嵌或者独立模式都须要解决的问题,相比而言,内嵌式模式更简略一点。 集群 server 主动选举主动故障转移Sentinel-Dashboard长久化到Apollo集群限流首先,思考到大部分的服务可能都不须要集群限流这个性能,因而实现一个注解用于手动开启集群限流模式,只有开启注解的状况下,才去实例化集群限流的 Bean 和限流数据。 @Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Import({EnableClusterImportSelector.class})@Documentedpublic @interface SentinelCluster {}public class EnableClusterImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{ClusterConfiguration.class.getName()}; }}这样写好之后,当扫描到有咱们的 SentinelCluster 注解的时候,就会去实例化 ClusterConfiguration。 @Slf4jpublic class ClusterConfiguration implements BeanDefinitionRegistryPostProcessor, EnvironmentAware { private Environment environment; @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ClusterManager.class); beanDefinitionBuilder.addConstructorArgValue(this.environment); registry.registerBeanDefinition("clusterManager", beanDefinitionBuilder.getBeanDefinition()); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { } @Override public void setEnvironment(Environment environment) { this.environment = environment; }}在配置中去实例化用于治理集群限流的ClusterManager,这段逻辑和咱们之前文章中应用到的一般无二,注册到ApolloDataSource之后主动监听Apollo的变动达到动静失效的成果。 ...

August 4, 2022 · 4 min · jiezi

关于springboot:SpringBoot如何优雅地进行响应数据封装异常处理

背景越来越多的我的项目开始基于前后端拆散的模式进行开发,这对后端接口的报文格式便有了肯定的要求。通常,咱们会采纳JSON格局作为前后端替换数据格式,从而缩小沟通老本等。 这篇文章,就带大家理解一下基于SpringBoot框架来封装返回报文以及对立异样解决。 报文根本格局个别报文格式通常会蕴含状态码、状态形容(或谬误提示信息)、业务数据等信息。在此基础上,不同的架构师、我的项目搭建者可能会有所调整。但从整体上来说,基本上都是大同小异。 在SpringBoot我的项目中,通常接口返回的报文中至多蕴含三个属性: code:申请接口的返回码,胜利或者异样等返回编码,例如定义申请胜利。message:申请接口的形容,也就是对返回编码的形容。data:申请接口胜利,返回的业务数据。示例报文如下: { "code":200, "message":"SUCCESS", "data":{ "info":"测试胜利" }}在上述报文格式中,不同的设计者是会有一些一致的,特地是code值的定义。如果齐全基于RESTful API设计的话,code字段可能就不须要存在了,而是通过HTTP协定中提供的GET、POST、PUT、DELETE操作等来实现资源的拜访。 但在实践中,不论是出于目前国内大多数程序员的习惯,还是受限于HTTP协定提供的操作方法的局限性,很少齐全遵循RESTful API形式进行设计。通常都是通过自定义Code值的模式来赋予它业务意义或业务谬误编码。 尽管能够不必齐全恪守RESTful API格调来定义Code,在Code值的自定义中,也存在两种模式:遵循HTTP状态码和自主定义。 像下面的示例,用200示意返回胜利,这就是遵循HTTP响应状态码的模式来返回,比方还有其余的400、401、404、500等。当然,还有齐全自主定义的,比方用0示意胜利,1示意失败,而后再跟进通用编码、业务分类编码等进行定义。 在此,笔者暂不评论每种模式的好坏,只列举了惯例的几种模式,大家理解对应的状况,做到成竹在胸,有所抉择即可。 响应参数封装实际创立一个SpringBoot我的项目,并引入Lombok依赖(精简代码),对应的外围依赖如下: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>创立枚举类,用于定义返回的错误码: @Getter@AllArgsConstructorpublic enum ResponseCodeEnums { SUCCESS(200, "success"), FAIL(500, "failed"), HTTP_STATUS_200(200, "ok"), HTTP_STATUS_400(400, "request error"), HTTP_STATUS_401(401, "no authentication"), HTTP_STATUS_403(403, "no authorities"), HTTP_STATUS_500(500, "server error"); private final int code; private final String message;}这里只定义了一些通用的、基于的HTTP响应状态码,业务相干的编码可依据业务需要进行定义。 定义对立返回后果实体类: @Datapublic class ResponseInfo<T> { /** * 状态码 */ protected int code; /** * 响应信息 */ protected String message; /** * 返回数据 */ private T data; public static <T> ResponseInfo<T> success() { return new ResponseInfo<>(); } public static <T> ResponseInfo<T> success(T data) { return new ResponseInfo<>(data); } public static <T> ResponseInfo<T> fail(String message) { return new ResponseInfo<>(ResponseCodeEnums.FAIL.getCode(), message); } public ResponseInfo() { this.code = ResponseCodeEnums.SUCCESS.getCode(); this.message = ResponseCodeEnums.SUCCESS.getMessage(); } public ResponseInfo(ResponseCodeEnums statusEnums) { this.code = statusEnums.getCode(); this.message = statusEnums.getMessage(); } /** * 若没有数据返回,能够人为指定状态码和提示信息 */ public ResponseInfo(int code, String msg) { this.code = code; this.message = msg; } /** * 有数据返回时,状态码为200,默认提示信息为“操作胜利!” */ public ResponseInfo(T data) { this.data = data; this.code = ResponseCodeEnums.SUCCESS.getCode(); this.message = ResponseCodeEnums.SUCCESS.getMessage(); } /** * 有数据返回,状态码为 200,人为指定提示信息 */ public ResponseInfo(T data, String msg) { this.data = data; this.code = ResponseCodeEnums.SUCCESS.getCode(); this.message = msg; }}在ResponseInfo中使用了泛型和公共办法、构造方法的封装,不便在业务中应用。示例中只提供了局部办法的封装,依据本身业务场景和须要可进一步封装。 ...

August 3, 2022 · 2 min · jiezi

关于springboot:Spring-Boot-2x教程01入门篇

Spring Boot介绍以前咱们开发Web利用时多采纳SSH、SSM的组合,常常因为一堆繁冗的配置节约大量工夫令人心生厌倦。相比PHP、NodeJs等疾速搭建我的项目并没有劣势,直到Spring Boot的横空出世。 Spring Boot建设在传统的Spring框架之上,因而,它提供了Spring的所有性能,并且比Spring更易于应用,能够帮忙咱们疾速创立独立运行、生产级的Spring应用程序。 总结一下Spring Boot的特点遵循约定大于配置的理念,为所有 Spring 开发提供更快的入门体验开箱即用,提供各种默认配置简化我的项目配置提供一系列大型项目通用的非性能个性(例如嵌入式服务器、安全性、指标、健康检查和内部化配置)没有代码生成和 XML 配置要求学习Spring Boot的前景瞻望自Spring Boot诞生后,曾经成为Java社区最有影响力的我的项目之一,之后同样热门的Spring Cloud也是依赖Spring Boot来构建微服务利用。以下是百度和Google的关注度趋势图,能够看出Spring Boot受到越来越多的关注。 Spring Boot的关注度趋势图(来自百度) Spring Boot的关注度趋势图(来自Google) 疾速入门本文由博客一文多发平台 OpenWrite 公布!

August 2, 2022 · 1 min · jiezi

关于springboot:聊聊在springboot项目中如何配置多个kafka消费者

前言不晓得大家有没有遇到这样的场景,就是一个我的项目中要生产多个kafka音讯,不同的消费者生产指定kafka音讯。遇到这种场景,咱们能够通过kafka的提供的api进行配置即可。但很多时候咱们会应用spring-kafka来简化开发,可是spring-kafka原生的配置项并没提供多个kafka配置,因而本文就来聊聊如何将spring-kafka进行革新,使之能反对多个kafka配置 注释1、通过 @ConfigurationProperties指定KafkaProperties前缀 @Primary @ConfigurationProperties(prefix = "lybgeek.kafka.one") @Bean public KafkaProperties oneKafkaProperties(){ return new KafkaProperties(); }如果有多个就配置多个,形如 @ConfigurationProperties(prefix = "lybgeek.kafka.two") @Bean public KafkaProperties twoKafkaProperties(){ return new KafkaProperties(); } @ConfigurationProperties(prefix = "lybgeek.kafka.three") @Bean public KafkaProperties threeKafkaProperties(){ return new KafkaProperties(); }2、配置消费者工厂,消费者工厂绑定对应的KafkaProperties @Bean public ConsumerFactory twoConsumerFactory(@Autowired @Qualifier("twoKafkaProperties") KafkaProperties twoKafkaProperties){ return new DefaultKafkaConsumerFactory(twoKafkaProperties.buildConsumerProperties()); }3、配置消费者监听器工厂,并绑定指定消费者工厂以及消费者配置 @Bean(MultiKafkaConstant.KAFKA_LISTENER_CONTAINER_FACTORY_TWO) public KafkaListenerContainerFactory twoKafkaListenerContainerFactory(@Autowired @Qualifier("twoKafkaProperties") KafkaProperties twoKafkaProperties, @Autowired @Qualifier("twoConsumerFactory") ConsumerFactory twoConsumerFactory) { ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory(); factory.setConsumerFactory(twoConsumerFactory); factory.setConcurrency(ObjectUtil.isEmpty(twoKafkaProperties.getListener().getConcurrency()) ? Runtime.getRuntime().availableProcessors() : twoKafkaProperties.getListener().getConcurrency()); factory.getContainerProperties().setAckMode(ObjectUtil.isEmpty(twoKafkaProperties.getListener().getAckMode()) ? ContainerProperties.AckMode.MANUAL:twoKafkaProperties.getListener().getAckMode()); return factory; }残缺的配置示例如下 ...

August 2, 2022 · 3 min · jiezi

关于springboot:Mybatis与SQL连接配置

MySQLMySQL依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>application.properties配置 spring.datasource.url=jdbc:mysql://IP地址/数据库?serverTimeZone=GMT%2B8&Character=utf8spring.datasource.username=账号spring.datasource.password=明码mybatis.mapper-locations=classpath:/mapper/*.xmlOracleOracle依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.2</version> </dependency> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>12.2.0.1</version> </dependency>application.peoperties spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriverspring.datasource.url=jdbc:oracle:thin:@IP地址spring.datasource.username=账号spring.datasource.password=明码

August 1, 2022 · 1 min · jiezi

关于springboot:SpringBoot定时任务-集成quartz实现定时任务单实例和分布式两种方式

最为罕用定时工作框架是Quartz,并且Spring也集成了Quartz的框架,Quartz不仅反对单实例形式还反对分布式形式。本文次要介绍Quartz,根底的Quartz的集成案例本,以及实现基于数据库的分布式工作治理和管制job生命周期。@pdaiSpringBoot定时工作 - 根底quartz实现形式 筹备知识点 什么是QuartzQuartz的体系结构什么是Quartz长久化实现案例 - 单实例形式实现案例 - 分布式形式 后端实现前端实现测试成果示例源码参考文章筹备知识点须要理解罕用的Quartz框架。什么是Quartz起源百度百科, 官网地址:http://www.quartz-scheduler.org/Quartz是OpenSymphony开源组织在Job scheduling畛域又一个开源我的项目,它能够与J2EE与J2SE应用程序相结合也能够独自应用。Quartz能够用来创立简略或为运行十个,百个,甚至是好几万个Jobs这样简单的程序。Jobs能够做成规范的Java组件或 EJBs。 它的特点如下 纯java实现,能够作为独立的应用程序,也能够嵌入在另一个独立式利用程序运行弱小的调度性能,Spring默认的调度框架,灵便可配置;作业长久化,调度环境长久化机制,能够保留并复原调度现场。零碎敞开数据不会失落;灵便的利用形式,能够任意定义触发器的调度时间表,反对工作和调度各种组合,组件式监听器、各种插件、线程池等性能,多种存储形式等;分布式和集群能力,能够被实例化,一个Quartz集群中的每个节点作为一个独立的Quartz应用,通过雷同的数据库表来感知到另一个Quartz利用Quartz的体系结构 Job 示意一个工作,要执行的具体内容。JobDetail 示意一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还蕴含了这个任务调度的计划和策略。Trigger 代表一个调度参数的配置,什么时候去调。Scheduler 代表一个调度容器,一个调度容器中能够注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就能够被 Scheduler 容器调度了。注: 上图来源于https://www.cnblogs.com/jijm1... 什么是Quartz长久化为什么要长久化?当程序忽然被中断时,如断电,内存超出时,很有可能造成工作的失落。 能够将调度信息存储到数据库外面,进行长久化,当程序被中断后,再次启动,依然会保留中断之前的数据,继续执行,而并不是从新开始。 Quartz提供了两种长久化形式Quartz提供两种根本作业存储类型: RAMJobStore在默认状况下Quartz将任务调度的运行信息保留在内存中,这种办法提供了最佳的性能,因为内存中数据拜访最快。不足之处是不足数据的持久性,当程序道路进行或零碎解体时,所有运行的信息都会失落。 JobStoreTX所有的工作信息都会保留到数据库中,能够管制事物,还有就是如果应用服务器敞开或者重启,工作信息都不会失落,并且能够复原因服务器敞开或者重启而导致执行失败的工作。 实现案例 - 单实例形式本例将展现quartz实现单实例形式。引入POM依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId></dependency>定义Job只须要继承QuartzJobBean,并重载executeInternal办法即可定义你本人的Job执行逻辑。 @Slf4jpublic class HelloJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { // get parameters context.getJobDetail().getJobDataMap().forEach( (k, v) -> log.info("param, key:{}, value:{}", k, v) ); // your logics log.info("Hello Job执行工夫: " + new Date()); }}配置JobJobDetail, Trigger, Schedule(这里采纳CronScheduleBuilder) ...

August 1, 2022 · 14 min · jiezi

关于springboot:Spring-Boot-EasyExcel导入导出简直太好用了

背景老我的项目次要采纳的POI框架来进行Excel数据的导入和导出,但常常会呈现OOM的状况,导致整个服务不可用。后续逐渐转移到EasyExcel,几乎不能太好用了。 EasyExcel是阿里巴巴开源插件之一,次要解决了poi框架应用简单,sax解析模式不容易操作,数据量大起来容易OOM,解决了POI并发造成的报错。次要解决形式:通过解压文件的形式加载,一行一行地加载,并且摈弃款式字体等不重要的数据,升高内存的占用。 在之前专门写过一篇文章《EasyExcel太不便易用了,强烈推荐!》,介绍EasyExcel性能的根本应用。明天这篇文章,咱们基于SpringBoot来实现一下EasyExcel的集成,更加不便大家在实践中的间接应用。 SpringBoot我的项目集成依赖集成创立一个根底的SpringBoot我的项目,比方这里采纳SpringBoot 2.7.2版本。 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency></dependencies>EasyExcel在SpringBoot的集成十分不便,只需引入对应的pom依赖即可。在上述dependencies中增加EasyExcel的依赖: <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.11</version></dependency>EasyExcel目前稳固最新版本2.2.11。如果想查看开源我的项目或最新版本,可在GitHub上取得:https://github.com/alibaba/ea...。 为了不便和简化代码编写,这里同时引入了Lombok的依赖,后续代码中也会应用对应的注解。 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version></dependency>上面正式开始业务相干代码的编写。如果你想间接取得残缺源码,对照源码浏览本篇文章,可在公号「程序新视界」内回“1007”取得残缺源码。 实体类实现这里创立一个Member,会员的实体类,并在实体类中填写根底的个人信息。 @Datapublic class Member { /** * EasyExcel应用:导出时疏忽该字段 */ @ExcelIgnore private Integer id; @ExcelProperty("用户名") @ColumnWidth(20) private String username; /** * EasyExcel应用:日期的格式化 */ @ColumnWidth(20) @ExcelProperty("出生日期") @DateTimeFormat("yyyy-MM-dd") private Date birthday; /** * EasyExcel应用:自定义转换器 */ @ColumnWidth(10) @ExcelProperty(value = "性别", converter = GenderConverter.class) private Integer gender;}为了尽量多的演示EasyExcel的相干性能,在上述实体类中应用了其常见的一些注解: @ExcelIgnore:疏忽掉该字段;@ExcelProperty("用户名"):设置该列的名称为”用户名“;@ColumnWidth(20):设置表格列的宽度为20;@DateTimeFormat("yyyy-MM-dd"):依照指定的格局对日期进行格式化;@ExcelProperty(value = "性别", converter = GenderConverter.class):自定义内容转换器,相似枚举的实现,将“男”、“女”转换成“0”、“1”的数值。GenderConverter转换器的代码实现如下: ...

August 1, 2022 · 4 min · jiezi

关于springboot:SpringBoot集成文件-如何集成itextpdf导出PDFitext的变迁

除了解决word, excel等文件外,最为常见的就是PDF的导出了。在java技术栈中,PDF创立和操作最为罕用的itext了,然而应用itext肯定要理解其版本历史和License问题,在早前版本应用的是MPL和LGPL双许可协定,在5.x以上版本中应用的是AGPLv3(这个协定意味着,只有集体用处和开源的我的项目能力应用itext这个库,否则是须要免费的)。本文次要介绍通过SpringBoot集成itextpdf实现PDF导出性能。@pdaiSpringBoot集成文件 - 集成itextpdf之导出PDF 常识筹备 什么是itextitext的历史版本和License问题规范的itextpdf导出的步骤实现案例 Pom依赖导出PDF增加页眉页脚和水印进一步了解 遇到license问题怎么办为何增加页眉页脚和水印是通过PdfPageEvent来实现示例源码参考文章更多内容常识筹备须要理解itext,以及itext历史版本变迁,以及license的问题。什么是itext来源于百度百科:iText是驰名的开放源码的站点sourceforge一个我的项目(由Bruno Lowagie编写),是一个用Java和.NET语言写的库,用来创立和批改PDF文件。通过iText不仅能够生成PDF或rtf的文档,而且能够将XML、Html文件转化为PDF文件。 iText的装置十分不便,下载iText.jar文件后,只须要在零碎的CLASSPATH中退出iText.jar的门路,在程序中就能够应用iText类库了。iText提供除了根本的创立、批改PDF文件外的其余高级的PDF个性,例如基于PKI的签名,40位和128位加密,色彩校对,带标签的PDF,PDF表单(AcroForms),PDF/X,通过ICC配置文件和条形码进行色彩治理。这些个性被一些产品和服务中应用,包含Eclipse BIRT,Jasper Reports,JBoss Seam,Windward Reports和pdftk。 个别状况下,iText应用在有以下一个要求的我的项目中: 内容无奈提前利用:取决于用户的输出或实时的数据库信息。因为内容,页面过多,PDF文档不能手动生成。文档需在无人参加,批处理模式下主动创立。内容被定制或个性化;例如,终端客户的名字须要标记在大量的页面上。itext的历史版本和License问题应用itext肯定要理解其版本历史,和License问题,在早前版本应用的是MPL和LGPL双许可协定,在5.x以上版本中应用的是AGPLv3(这个协定意味着,<mark>只有集体用处和开源的我的项目能力应用itext这个库,否则是须要免费的</mark>)iText 0.x-2.x/iTextSharp 3.x-4.x 更新工夫是2000-2009应用的是MPL和LGPL双许可协定最近的更新是2009年,版本号是iText 2.1.7/iTextSharp 4.1.6.0此时引入包的GAV版本如下:<dependency> <groupId>com.lowagie</groupId> <artifactId>itext</artifactId> <version>2.1.7</version></dependency>iText 5.x和iTextSharp 5.x 更新工夫是2009-2016, 公司化运作,并标准化和进步性能开始应用AGPLv3协定 只有集体用处和开源的我的项目能力应用itext这个库,否则是须要免费的iTextSharp被设计成iText库的.NET版本,并且与iText版本号同步,iText 5.0.0和iTextSharp5.0.0同时公布新性能不在这外面减少,然而官网会修复重要的bug此时引入包的GAV版本如下:<dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.5.13.3</version></dependency>iText 7.x 更新工夫是2016到当初AGPLv3协定齐全重写,重点关注可扩展性和模块化不实用iTextSharp这个名称,都统称为iText,有Java和.Net版本JDK 1.7+此时引入包的GAV版本如下:<dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.2</version> <type>pom</type></dependency>注:iText变动后,GitHub上有团队基于4.x版本(MPL和LGPL双许可协定)fork了一个分支成为OpenPDF,并持续保护该我的项目。 规范的itextpdf导出的步骤itextpdf导出pdf次要蕴含如下几步: @Overridepublic Document generateItextPdfDocument(OutputStream os) throws Exception { // 1. 创立文档 Document document = new Document(PageSize.A4); // 2. 绑定输入流(通过pdfwriter) PdfWriter.getInstance(document, os); // 3. 打开文档 document.open(); // 4. 往文档中增加内容 document.add(xxx); // 5. 敞开文档 document.close(); return document;}document中增加的Element有哪些呢? ...

July 31, 2022 · 4 min · jiezi

关于springboot:SpringBoot集成文件-如何基于POItl和word模板导出庞大的Word文件

<article class=“article fmt article-content”><blockquote>前文咱们介绍了通过Apache POI通过来导出word的例子;那如果是word模板形式,有没有开源库通过模板形式导出word呢?poi-tl是一个基于Apache POI的Word模板引擎,也是一个收费开源的Java类库,你能够十分不便的退出到你的我的项目中,并且领有着让人喜悦的个性。本文次要介绍通过SpringBoot集成poi-tl实现模板形式的Word导出性能。</blockquote><ul><li><p>SpringBoot集成文件 - 集成POI-tl之基于模板的Word导出</p><ul><li><p>常识筹备</p><ul><li>什么是poi-tl</li><li><p>poi-tl的TDO模式</p><ul><li>Template:模板</li><li>Data-model:数据</li><li>Output:输入</li></ul></li></ul></li><li><p>实现案例</p><ul><li>Pom依赖</li><li>导出基于template的word</li><li>导出markdown为word</li></ul></li><li>示例源码</li><li>参考文章</li><li>更多内容</li></ul></li></ul><h2>常识筹备</h2><blockquote>须要了解文件上传和下载的常见场景和技术手段。@pdai</blockquote><h3>什么是poi-tl</h3><blockquote>如下内容来源于,poi-tl官网。</blockquote><p>poi-tl(poi template language)是Word模板引擎,应用Word模板和数据创立很棒的Word文档。</p><p>劣势:</p><p></p><p>它还反对自定义插件,如下是官网代码仓库反对的个性</p><blockquote>poi-tl supports <strong>custom functions (plug-ins)</strong>, functions can be executed anywhere in the Word template, do anything anywhere in the document is the goal of poi-tl.</blockquote><table><thead><tr><th>Feature</th><th>Description</th></tr></thead><tbody><tr><td><span class=“emoji emoji-white_check_mark”></span> Text</td><td>Render the tag as text</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Picture</td><td>Render the tag as a picture</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Table</td><td>Render the tag as a table</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Numbering</td><td>Render the tag as a numbering</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Chart</td><td>Bar chart (3D bar chart), column chart (3D column chart), area chart (3D area chart), line chart (3D line chart), radar chart, pie chart (3D pie Figure) and other chart rendering</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> If Condition</td><td>Hide or display certain document content (including text, paragraphs, pictures, tables, lists, charts, etc.) according to conditions</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Foreach Loop</td><td>Loop through certain document content (including text, paragraphs, pictures, tables, lists, charts, etc.) according to the collection</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Loop table row</td><td>Loop to copy a row of the rendered table</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Loop table column</td><td>Loop copy and render a column of the table</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Loop ordered list</td><td>Support the loop of ordered list, and support multi-level list at the same time</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Highlight code</td><td>Word highlighting of code blocks, supporting 26 languages and hundreds of coloring styles</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Markdown</td><td>Convert Markdown to a word document</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Word attachment</td><td>Insert attachment in Word</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Word Comments</td><td>Complete support comment, create comment, modify comment, etc.</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Word SDT</td><td>Complete support structured document tag</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Textbox</td><td>Tag support in text box</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Picture replacement</td><td>Replace the original picture with another picture</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> bookmarks, anchors, hyperlinks</td><td>Support setting bookmarks, anchors and hyperlinks in documents</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Expression Language</td><td>Fully supports SpringEL expressions and can extend more expressions: OGNL, MVEL…</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Style</td><td>The template is the style, and the code can also set the style</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Template nesting</td><td>The template contains sub-templates, and the sub-templates then contain sub-templates</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> Merge</td><td>Word merge Merge, you can also merge in the specified position</td></tr><tr><td><span class=“emoji emoji-white_check_mark”></span> custom functions (plug-ins)</td><td>Plug-in design, execute function anywhere in the document</td></tr></tbody></table><h3>poi-tl的TDO模式</h3><blockquote>TDO模式:Template + data-model = output</blockquote><p>以官网的例子为例:</p><pre><code class=“java”>XWPFTemplate template = XWPFTemplate.compile(“template.docx”).render( new HashMap<String, Object>(){{ put(“title”, “Hi, poi-tl Word模板引擎”);}}); template.writeAndClose(new FileOutputStream(“output.docx”)); </code></pre><ul><li>compile 编译模板 - Template</li><li>render 渲染数据 - data-model</li><li>write 输入到流 - output</li></ul><h4>Template:模板</h4><p>模板是Docx格局的Word文档,你能够应用Microsoft office、WPS Office、Pages等任何你喜爱的软件制作模板,也能够应用Apache POI代码来生成模板。</p><p>所有的标签都是以<code>{{</code>结尾,以<code>}}</code>结尾,标签能够呈现在任何地位,包含页眉,页脚,表格外部,文本框等,表格布局能够设计出很多优良业余的文档,举荐应用表格布局。</p><p>poi-tl模板遵循“所见即所得”的设计,模板和标签的款式会被齐全保留。</p><h4>Data-model:数据</h4><p>数据相似于哈希或者字典,能够是Map构造(key是标签名称):</p><pre><code class=“java”>Map<String, Object> data = new HashMap<>();data.put(“name”, “Sayi”);data.put(“start_time”, “2019-08-04”);</code></pre><p>能够是对象(属性名是标签名称):</p><pre><code class=“java”>public class Data { private String name; private String startTime; private Author author;}</code></pre><p>数据能够是树结构,每级之间用点来分隔开,比方<code>{ {author.name} }</code>标签对应的数据是author对象的name属性值。</p><p><strong>Word模板不是由简略的文本示意,所以在渲染图片、表格等元素时提供了数据模型,它们都实现了接口RenderData</strong>,比方图片数据模型PictureRenderData蕴含图片门路、宽、高三个属性。</p><h4>Output:输入</h4><p>以流的形式进行输入:</p><pre><code class=“java”>template.write(OutputStream stream);</code></pre><p>能够写到任意输入流中,比方文件流:</p><pre><code class=“java”>template.write(new FileOutputStream(“output.docx”));</code></pre><p>比方网络流:</p><pre><code class=“java”>response.setContentType(“application/octet-stream”);response.setHeader(“Content-disposition”,“attachment;filename=""+“out_template.docx”+”"");// HttpServletResponse responseOutputStream out = response.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(out);template.write(bos);bos.flush();out.flush();PoitlIOUtils.closeQuietlyMulti(template, bos, out); // 最初不要遗记敞开这些流。</code></pre><h2>实现案例</h2><blockquote>这里展现SpringBoot集成poi-tl基于word模板导出Word, 以及导出markdown为word的例子。</blockquote><h3>Pom依赖</h3><p>引入poi的依赖包</p><p>根底的包:</p><pre><code class=“xml”><dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.12.0</version></dependency></code></pre><p>插件的包如下,比方highlight,markdown包</p><pre><code class=“xml”><dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl-plugin-highlight</artifactId> <version>1.0.0</version></dependency><dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl-plugin-markdown</artifactId> <version>1.0.3</version></dependency></code></pre><h3>导出基于template的word</h3><p>controller中的办法</p><pre><code class=“java”>@ApiOperation(“Download Word”)@GetMapping("/word/download")public void download(HttpServletResponse response) { try { XWPFTemplate document = userService.generateWordXWPFTemplate(); response.reset(); response.setContentType(“application/octet-stream”); response.setHeader(“Content-disposition”, “attachment;filename=user_word_” + System.currentTimeMillis() + “.docx”); OutputStream os = response.getOutputStream(); document.write(os); os.close(); } catch (Exception e) { e.printStackTrace(); }}</code></pre><p>Service中的理论办法</p><pre><code class=“java”>@Overridepublic XWPFTemplate generateWordXWPFTemplate() throws IOException { Map<String, Object> content = new HashMap<>(); content.put(“title”, “Java 全栈常识体系”); content.put(“author”, “pdai”); content.put(“site”, new HyperlinkTextRenderData(“https://pdai.tech”, “https://pdai.tech”)); content.put(“poiText”, “Apache POI 是创立和保护操作各种合乎Office Open XML(OOXML)规范和微软的OLE 2复合文档格局(OLE2)的Java API。用它能够应用Java读取和创立,批改MS Excel文件.而且,还能够应用Java读取和创立MS Word和MSPowerPoint文件。更多请参考官网文档”); content.put(“poiText2”, “生成xls和xlsx有什么区别?POI对Excel中的对象的封装对应关系?”); content.put(“poiList”, Numberings.create(“excel03只能关上xls格局,无奈间接关上xlsx格局”, “xls只有65536行、256列; xlsx能够有1048576行、16384列”, “xls占用空间大, xlsx占用空间小,运算速度也会快一点”)); RowRenderData headRow = Rows.of(“ID”, “Name”, “Email”, “TEL”, “Description”).textColor(“FFFFFF”) .bgColor(“4472C4”).center().create(); TableRenderData table = Tables.create(headRow); getUserList() .forEach(a -> table.addRow(Rows.create(a.getId() + “”, a.getUserName(), a.getEmail(), a.getPhoneNumber() + “”, a.getDescription()))); content.put(“poiTable”, table); Resource resource = new ClassPathResource(“pdai-guli.png”); content.put(“poiImage”, Pictures.ofStream(new FileInputStream(resource.getFile())).create()); return XWPFTemplate.compile(new ClassPathResource(“poi-tl-template.docx”).getFile()).render(content);}private List<User> getUserList() { List<User> userList = new ArrayList<>(); for (int i = 0; i < 5; i++) { userList.add(User.builder() .id(Long.parseLong(i + “”)).userName(“pdai” + i).email(“pdai@pdai.tech” + i).phoneNumber(121231231231L) .description(“hello world” + i) .build()); } return userList;}</code></pre><p>筹备模板</p><p></p><p>导出word</p><p></p><h3>导出markdown为word</h3><p>controller中的办法</p><pre><code class=“java”>@ApiOperation(“Download Word based on markdown”)@GetMapping("/word/downloadMD")public void downloadMD(HttpServletResponse response) { try { XWPFTemplate document = userService.generateWordXWPFTemplateMD(); response.reset(); response.setContentType(“application/octet-stream”); response.setHeader(“Content-disposition”, “attachment;filename=user_word_” + System.currentTimeMillis() + “.docx”); OutputStream os = response.getOutputStream(); document.write(os); os.close(); } catch (Exception e) { e.printStackTrace(); }}</code></pre><p>Service中实现的办法</p><pre><code class=“java”>@Overridepublic XWPFTemplate generateWordXWPFTemplateMD() throws IOException { MarkdownRenderData code = new MarkdownRenderData(); Resource resource = new ClassPathResource(“test.md”); code.setMarkdown(new String(Files.readAllBytes(resource.getFile().toPath()))); code.setStyle(MarkdownStyle.newStyle()); Map<String, Object> data = new HashMap<>(); data.put(“md”, code); Configure config = Configure.builder().bind(“md”, new MarkdownRenderPolicy()).build(); return XWPFTemplate.compile(new ClassPathResource(“markdown_template.docx”).getFile(), config).render(data);}</code></pre><p>筹备模板</p><p></p><p>导出word</p><p></p><h2>示例源码</h2><p>https://github.com/realpdai/t…</p><h2>参考文章</h2><p>http://deepoove.com/poi-tl/</p><h2>更多内容</h2><p>辞别碎片化学习,无套路一站式体系化学习后端开发: Java 全栈常识体系(https://pdai.tech)</p></article> ...

July 31, 2022 · 3 min · jiezi

关于springboot:学妹想学SpringBoot连夜整理了一份SpringBoot高阶笔记涵盖六大核心专题

Spring Boot从2015年开始在国内走红,Spring Boot让配置、代码编写、部署和监控都更简略了,因而日益受到开发者的青眼。越来越多的企业抉择将Spring Boot作为零碎开发的首选框架。 本篇将会率领大家学习Spring Boot的各项性能个性及其最佳实际、实现原理展开讨论,涵盖了外围容器、Web服务、内置缓存、数据拜访、并发编程、监控和扩大等一系列外围主题,这些外围主题也广泛应用于Spring家族中的其余开发框架。 这份PDF共计分为了七大部分,每个局部都有对应的具体章节! 一、Spring Boot概述本章作为全书的开篇,将简要介绍Spring Boot的基本概念和性能体系,并给出实战案例。 二、外围容器本局部介绍在应用Spring容器时应该关注的最佳实际,并探讨Spring内核最重要的两个性能个性,即依赖注入和面向切面。 三、Web服务本局部探讨针对Web利用程序开发所提供的最佳实际,包含应用SpringHATEOAS开发自解释Web API,应用Spring GraphQL开发查问式Web API,针对传统Spring MVC的异步编程模型,以及新型的基于响应式流的WebFlux组件。同时,咱们还将探讨如何应用目前十分风行的、Spring 5默认内置的RSocket协定来进步网络通信的性能。 四、内置缓存本局部关注Spring Boot框架的一项高性能性能组件,即缓存。咱们将探讨Spring Boot中内置缓存的应用办法以及它的底层实现原理,并联合SpringSecurity框架探讨缓存机制在用户认证流程中的作用。 五、数据拜访本局部关注高效拜访关系型数据的相干实际。咱们将零碎探讨基于JDBC以及ORM框架实现数据拜访的常见开发陷阱及其解决办法,同时,将进一步基于缓存机制剖析如何应用它来优化数据拜访性能。 六、并发编程这部分探讨Spring框架所提供的一组并发编程组件,包含工作执行器、任务调度器以及@Async注解,剖析这些组件与JDK中并发编程组件之间的整合过程,并给出源码级的原理剖析。 七、监控和扩大本局部内容的关注点在于如何找到Spring Boot应用程序中的性能问题并进行无效的监控和度量,通过引入Actuator组件并整合自定义的度量指标来实现这一指标。同时,将探讨Spring Boot的整个生态系统,包含SpringBoot Starter、Spring Boot与微服务、Spring Boot与云原生以及SpringBoot测试计划。 最初的最初须要支付这套SpringBoot学习PDF的同学麻烦帮忙点赞+转发文章之后【点击此处】即可收费获取!

July 29, 2022 · 1 min · jiezi

关于springboot:SpringBoot-使用拦截器

SpringBoot 应用拦截器拦截器有什么用通过重写 preHand() 办法在申请发送到控制器之前执行操作。通过重写 postHand() 办法在响应产生到客户端之前执行操作。通过从新 afterCompletion() 办法在实现申请和响应之后执行操作。怎么应用拦截器定义拦截器实现 HandlerInterceptor 接口并重写须要的办法,并通过 @Component 注解将拦截器注入到 Spring 容器中。 @Componentpublic class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { System.out.println("preHandle"); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView){ System.out.println("postHandle"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("afterCompletion"); } }注册拦截器实现 WebMvcConfigurer 接口,并通过 addInterceptors() 办法注册拦截器,同时能够通过 addPathPatterns()、excludePathPatterns() 办法指定拦截器拦挡的范畴。 ...

July 29, 2022 · 1 min · jiezi

关于springboot:2022就业季|Spring认证教你如何使用-Spring-构建-REST-服务五

书接上文⬆⬆⬆在 REST API 中构建链接到目前为止,您曾经应用根本链接构建了一个可进化的 API。为了倒退您的 API 并更好地为您的客户服务,您须要承受超媒体作为应用程序状态引擎的概念。这意味着什么?在本节中,您将具体探讨它。业务逻辑不可避免地会建设波及流程的规定。此类零碎的危险在于咱们常常将此类服务器端逻辑带入客户端并建设强耦合。REST 就是要突破这种连贯并最小化这种耦合。为了展现如何在不触发客户端中断更改的状况下应答状态变动,设想一下增加一个履行订单的零碎。第一步,定义一条Order记录:链接/src/main/java/payroll/Order.javapackage payroll;import java.util.Objects;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name = "CUSTOMER_ORDER")class Order { private @Id @GeneratedValue Long id; private String description; private Status status; Order() {} Order(String description, Status status) { this.description = description; this.status = status; } public Long getId() { return this.id; } public String getDescription() { return this.description; } public Status getStatus() { return this.status; } public void setId(Long id) { this.id = id; } public void setDescription(String description) { this.description = description; } public void setStatus(Status status) { this.status = status; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Order)) return false; Order order = (Order) o; return Objects.equals(this.id, order.id) && Objects.equals(this.description, order.description) && this.status == order.status; } @Override public int hashCode() { return Objects.hash(this.id, this.description, this.status); } @Override public String toString() { return "Order{" + "id=" + this.id + ", description='" + this.description + '\'' + ", status=" + this.status + '}'; }}复制该类须要 JPA@Table正文将表的名称更改为,CUSTOMER_ORDER因为ORDER它不是表的无效名称。它包含一个description字段以及一个status字段。从客户提交订单到实现或勾销订单时,订单必须经验一系列状态转换。这能够捕捉为 Java enum:链接/src/main/java/payroll/Status.javapackage payroll;enum Status { IN_PROGRESS, // COMPLETED, // CANCELLED}复制这enum捕捉了一个Order能够占据的各种状态。对于本教程,让咱们放弃简略。要反对与数据库中的订单交互,必须定义相应的 Spring Data 存储库:Spring Data JPA 的JpaRepository根本接口interface OrderRepository extends JpaRepository<Order, Long> {}复制有了这个,您当初能够定义一个根本的OrderController:链接/src/main/java/payroll/OrderController.java@RestControllerclass OrderController { private final OrderRepository orderRepository; private final OrderModelAssembler assembler; OrderController(OrderRepository orderRepository, OrderModelAssembler assembler) { this.orderRepository = orderRepository; this.assembler = assembler; } @GetMapping("/orders") CollectionModel<EntityModel<Order>> all() { List<EntityModel<Order>> orders = orderRepository.findAll().stream() // .map(assembler::toModel) // .collect(Collectors.toList()); return CollectionModel.of(orders, // linkTo(methodOn(OrderController.class).all()).withSelfRel()); } @GetMapping("/orders/{id}") EntityModel<Order> one(@PathVariable Long id) { Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); return assembler.toModel(order); } @PostMapping("/orders") ResponseEntity<EntityModel<Order>> newOrder(@RequestBody Order order) { order.setStatus(Status.IN_PROGRESS); Order newOrder = orderRepository.save(order); return ResponseEntity // .created(linkTo(methodOn(OrderController.class).one(newOrder.getId())).toUri()) // .body(assembler.toModel(newOrder)); }}复制它蕴含与您迄今为止构建的控制器雷同的 REST 控制器设置。它同时注入OrderRepositorya 和 a (not yet built) OrderModelAssembler。前两个 Spring MVC 路由解决聚合根以及单个我的项目Order资源申请。第三条 Spring MVC 路由通过在IN_PROGRESS状态中启动它们来解决创立新订单。所有控制器办法都返回 Spring HATEOAS 的RepresentationModel子类之一以正确出现超媒体(或围绕此类类型的包装器)。在构建 之前OrderModelAssembler,让咱们探讨须要产生的事件。您正在对 、 和 之间的状态流Status.IN_PROGRESS进行Status.COMPLETED建模Status.CANCELLED。向客户端提供此类数据时,一件很天然的事件是让客户端依据此无效负载决定它能够做什么。但那是谬误的。当您在此流程中引入新状态时会产生什么?UI 上各种按钮的搁置可能是谬误的。如果您更改了每个州的名称,可能是在编码国内反对并显示每个州的区域设置特定文本时会怎么?这很可能会毁坏所有客户。输出HATEOAS或超媒体作为应用程序状态引擎。与其让客户端解析无效负载,不如为它们提供链接以收回无效操作的信号。将基于状态的操作与数据负载拆散。换句话说,当CANCEL和COMPLETE是无效操作时,将它们动静增加到链接列表中。客户端只须要在链接存在时向用户显示相应的按钮。这使客户端不用晓得此类操作何时无效,从而升高了服务器及其客户端在状态转换逻辑上不同步的危险。曾经承受了 Spring HATEOASRepresentationModelAssembler组件的概念,将这样的逻辑放入其中OrderModelAssembler将是捕捉此业务规定的完满地位:链接/src/main/java/payroll/OrderModelAssembler.javapackage payroll;import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.;import org.springframework.hateoas.EntityModel;import org.springframework.hateoas.server.RepresentationModelAssembler;import org.springframework.stereotype.Component;@Componentclass OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> { @Override public EntityModel<Order> toModel(Order order) { // Unconditional links to single-item resource and aggregate root EntityModel<Order> orderModel = EntityModel.of(order, linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel(), linkTo(methodOn(OrderController.class).all()).withRel("orders")); // Conditional links based on state of the order if (order.getStatus() == Status.IN_PROGRESS) { orderModel.add(linkTo(methodOn(OrderController.class).cancel(order.getId())).withRel("cancel")); orderModel.add(linkTo(methodOn(OrderController.class).complete(order.getId())).withRel("complete")); } return orderModel; }}复制此资源组装器始终蕴含指向单项资源的本身链接以及返回聚合根的链接。但它也包含两个条件链接OrderController.cancel(id)以及OrderController.complete(id)(尚未定义)。这些链接仅在订单状态为 时显示Status.IN_PROGRESS。如果客户能够采纳 HAL 和读取链接的能力,而不是简略地读取一般的旧 JSON 数据,他们能够替换对订单零碎畛域常识的需要。这天然缩小了客户端和服务器之间的耦合。它关上了调整订单履行流程的大门,而不会在流程中毁坏客户。要实现订单履行,请将以下内容增加到OrderController操作中cancel:在 OrderController 中创立“勾销”操作@DeleteMapping("/orders/{id}/cancel")ResponseEntity<?> cancel(@PathVariable Long id) { Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) { order.setStatus(Status.CANCELLED); return ResponseEntity.ok(assembler.toModel(orderRepository.save(order))); } return ResponseEntity // .status(HttpStatus.METHOD_NOT_ALLOWED) // .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) // .body(Problem.create() // .withTitle("Method not allowed") // .withDetail("You can't cancel an order that is in the " + order.getStatus() + " status"));}复制Order它在容许勾销之前查看状态。如果它不是一个无效的状态,它会返回一个RFC-7807 Problem,一个反对超媒体的谬误容器。如果转换的确无效,则将 转换Order为CANCELLED。并将其增加到OrderController订单实现中:在 OrderController 中创立“残缺”操作@PutMapping("/orders/{id}/complete")ResponseEntity<?> complete(@PathVariable Long id) { Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) { order.setStatus(Status.COMPLETED); return ResponseEntity.ok(assembler.toModel(orderRepository.save(order))); } return ResponseEntity // .status(HttpStatus.METHOD_NOT_ALLOWED) // .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) // .body(Problem.create() // .withTitle("Method not allowed") // .withDetail("You can't complete an order that is in the " + order.getStatus() + " status"));}复制这实现了相似的逻辑以避免Order状态实现,除非处于正确的状态。让咱们更新LoadDatabase以预加载一些Orders 以及Employee它之前加载的 s。更新数据库预加载器package payroll;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.CommandLineRunner;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationclass LoadDatabase { private static final Logger log = LoggerFactory.getLogger(LoadDatabase.class); @Bean CommandLineRunner initDatabase(EmployeeRepository employeeRepository, OrderRepository orderRepository) { return args -> { employeeRepository.save(new Employee("Bilbo", "Baggins", "burglar")); employeeRepository.save(new Employee("Frodo", "Baggins", "thief")); employeeRepository.findAll().forEach(employee -> log.info("Preloaded " + employee)); orderRepository.save(new Order("MacBook Pro", Status.COMPLETED)); orderRepository.save(new Order("iPhone", Status.IN_PROGRESS)); orderRepository.findAll().forEach(order -> { log.info("Preloaded " + order); }); }; }}复制当初你能够测试了!要应用新生成的订单服务,只需执行一些操作:$ curl -v http://localhost:8080/orders{ “_嵌入”:{ “订单”: [ { “身份证”:3, “形容”:“MacBook Pro”, “状态”:“已实现”, “_链接”:{ “本人”: { "href": "http://localhost:8080/orders/3" }, “订单”: { "href": "http://localhost:8080/orders" } } }, { “身份证”:4, “形容”:“iPhone”, “状态”:“IN_PROGRESS”, “_链接”:{ “本人”: { "href": "http://localhost:8080/orders/4" }, “订单”: { "href": "http://localhost:8080/orders" }, “勾销”: { "href": "http://localhost:8080/orders/4/cancel" }, “齐全的”: { "href": "http://localhost:8080/orders/4/complete" } } } ] }, “_链接”:{ “本人”: { "href": "http://localhost:8080/orders" } }}此 HAL 文档会依据其以后状态立刻显示每个订单的不同链接。第一个订单,即COMPLETED只有导航链接。未显示状态转换链接。第二个订单,即 IN_PROGRESS还具备勾销链接和残缺链接。尝试勾销订单:$ curl -v -X 删除 http://localhost:8080/orders/...; 删除 /orders/4/cancel HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:/>< HTTP/1.1 200< 内容类型:application/hal+json;charset=UTF-8< 传输编码:分块< 日期:2018 年 8 月 27 日星期一 15:02:10 GMT<{ “身份证”:4, “形容”:“iPhone”, “状态”:“勾销”, “_链接”:{ “本人”: { "href": "http://localhost:8080/orders/4" }, “订单”: { "href": "http://localhost:8080/orders" } }}此响应显示一个HTTP 200状态代码,表明它是胜利的。响应 HAL 文档显示该订单处于新状态 ( CANCELLED)。扭转状态的链接隐没了。如果再次尝试雷同的操作……$ curl -v -X 删除 http://localhost:8080/orders/4/cancel* TCP_NODELAY 设置* 连贯到 localhost (::1) 端口 8080 (#0)> 删除 /orders/4/cancel HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:*/*>< HTTP/1.1 405< 内容类型:应用程序/问题+json< 传输编码:分块< 日期:2018 年 8 月 27 日星期一 15:03:24 GMT<{ "title": "办法不容许", "detail": "您不能取消处于 CANCELED 状态的订单"}…您会看到HTTP 405 Method Not Allowed响应。DELETE已成为有效操作。Problem响应对象分明地表明您不能“勾销”曾经处于“CANCELLED”状态的订单。此外,尝试实现雷同的订单也会失败:$ curl -v -X PUT localhost:8080/orders/4/complete TCP_NODELAY 设置 连贯到 localhost (::1) 端口 8080 (#0)> PUT /orders/4/实现 HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:/*>< HTTP/1.1 405< 内容类型:应用程序/问题+json< 传输编码:分块< 日期:2018 年 8 月 27 日星期一 15:05:40 GMT<{ "title": "办法不容许", "detail": "您无奈实现处于 CANCELED 状态的订单"}有了这所有,您的订单履行服务就可能有条件地显示可用的操作。它还能够避免有效操作。通过利用超媒体和链接协定,客户端能够构建得更坚硬,并且不太可能仅仅因为数据的变动而解体。Spring HATEOAS 能够轻松构建您须要为客户提供服务的超媒体。概括在本教程中,您应用了各种策略来构建 REST API。事实证明,REST 不仅仅是丑陋的 URI 和返回 JSON 而不是 XML。相同,以下策略有助于升高您的服务毁坏您可能管制或可能无法控制的现有客户的可能性:不要删除旧字段。相同,反对他们。应用基于 rel 的链接,这样客户端就不用放心 URI 进行硬编码。尽可能长时间地保留旧链接。即便您必须更改 URI,也要保留 rels,以便旧客户端能够应用新性能。当各种状态驱动操作可用时,应用链接而不是无效负载数据来批示客户端。RepresentationModelAssembler为每种资源类型构建实现并在所有控制器中应用这些组件仿佛须要一些致力。然而这种额定的服务器端设置(感激 Spring HATEOAS 使之变得容易)能够确保您管制的客户端(更重要的是,您不管制的客户端)能够随着您的 API 随着倒退而轻松升级。咱们对于如何应用 Spring 构建 RESTful 服务员的教程到此结束。本教程的每个局部都在单个 github 存储库中作为独自的子项目进行治理:nonrest — 没有自媒体的简略 Spring MVC 应用程序rest — Spring MVC + Spring HATEOAS 应用程序,每个资源的 HAL 示意进化- REST 应用程序,其中一个字段已进化但保留旧数据以实现向后兼容性链接- REST 应用程序,其中条件链接用于向客户端收回无效状态更改信号要查看应用 Spring HATEOAS 的更多示例,请参阅Spring中国教育管理中心#java##spring认证##程序员##spring#以上就是明天对于Spring的一些探讨,对你有帮忙吗?如果你有趣味深刻理解,欢送到Spring中国教育管理中心留言交换! ...

July 28, 2022 · 4 min · jiezi

关于springboot:7SpringBoot注解

@Configuration: 示意这是一个配置类,和以前编写的配置文件一样,也能够给容器中增加组件。@ConfigurationProperties(prefix = "spring.http.encoding") 从配置文件中获取指定的值和bean的属性进行绑定@EnableConfigurationProperties(HttpEncodingProperties.class): @EnableConfigurationProperties的意思是启动指定类的ConfigurationProperties性能;把HttpEncodingProperties类和配置文件绑定起来。配置文件里能配什么看HttpEncodingProperties类有什么属性。将配置文件中对应的值和HttpEncodingProperties类绑定起来;并把HttpEncodingProperties退出到ioc容器中。@ConditionalOnWebApplication Spring底层@Conditional注解,依据不同的条件,如果满足指定的条件,这个配置类才会失效;判断以后利用是否是web利用,如果是,以后配置类失效@ConditionalOnClass(CharacterEncodingFilter.class) 判断以后我的项目有没有这个类CharacterEncodingFilter;CharacterEncodingFilter是SpringMVC中解决乱码的过滤器;@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true) 判断配置文件中是否存在某个配置:spring.http.encoding.enabled;matchIfMissing = true:就算你没配我也认为你配了即便咱们配置文件中不配置spring.http.encoding.enabled=true,也是默认失效的;//spring.http.encoding.enabled = true@Bean 给容器中增加一个组件,这个组件的某些值须要从xxxProperties中获取@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件 //容器中如果没有CharacterEncodingFilter,就往容器中增加,有就不增加。@ConditionalOnMissingBean(CharacterEncodingFilter.class) 判断容器中没有这个CharacterEncodingFilter组件容器中如果没有CharacterEncodingFilter,就往容器中增加,有就不增加。@PathVariable:如果参数的地位,参数是一个map,它会将所有的门路变量的k,v都放进map里(就是把门路上的参数装到map里)@RequestHeader:获取申请头@RequestParam:获取申请参数的值@CookieValue:获取cookie的值@RequestBody:获取申请体[POST],实用于表单提交的时候,获取表单里的值@RequestAttribute:获取申请域中的值@MatrixVariable:矩阵变量如有谬误,欢送指出。如有须要补充的,欢送在评论处回复写博客是为了记住本人容易遗记的货色,另外也是对本人工作的总结。

July 20, 2022 · 1 min · jiezi

关于springboot:Vue3-中如何加载动态菜单

松哥之前写了两篇文章和大家分享了 TienChin 我的项目中的菜单数据问题,还没看过的小伙伴请戳这里: Vue 里,多级菜单要如何设计才显得业余?TienChin 我的项目动静菜单接口分析这两篇文章次要是和大家阐明了后端如何依据以后登录用户,动静生成一个菜单 JSON。 那么当初的问题就是,以后端收到后端返回来的菜单 JSON 之后,该如何将之渲染进去?这就是咱们目前所面临的问题了。 TienChin 我的项目基于 RuoYi 脚手架来实现,所以本文的剖析你也能够看作是对 RuoYi-Vue3 我的项目的剖析。 1. 整体思路首先咱们来梳理下整体上的实现思路,首先一点:整体思路和 vhr 截然不同。 思考到有的小伙伴可能曾经遗记 vhr 中前端动静菜单的实现思路了,因而本文再和大家剖析一下。 为了确保在所有的 .vue 文件中都能拜访到到菜单数据,所以抉择将菜单数据存入 vuex 中,vuex 是 vue 中一个存储数据的公共中央,所有的 .vue 文件都能够从 vuex 中读取到数据。存储在 vuex 中的数据实质上是存在内存中,所以它有一个特点,就是浏览器按 F5 刷新之后,数据就没了。所以在产生页面的跳转的时候,咱们应该去辨别一下,是用户点击了页面上的菜单按钮之后产生了页面跳转还是用户点击了浏览器刷新按钮(或者按了 F5)产生了跳转。 为了实现这一点,咱们须要用到 vue 中的路由导航守卫性能,对于咱们 Java 工程师而言,这些可能听起来有点生疏,然而你把它当作 Java 中的 Filter 来对待就好了解了,实际上咱们视频中和小伙伴们解说的时候就是这么类比的,将一个新事物跟咱们脑海中一个已有的相熟的事物进行类比,就很容易了解了。 vue 中的导航守卫就相似一个监控,它能够监控到所有的页面跳转,在页面跳转中,咱们能够去判断一下 vuex 中的菜单数据是否还在,如果还在,就阐明用户是点击了页面上的菜单按钮实现了跳转的,如果不在,就阐明用户是点击了浏览器的刷新按钮或者是按了 F5 进行页面刷新的,此时咱们就要连忙去服务端从新加载一下菜单数据。 ---xxxxxxxxxxxxxxxxxx--- 整体上的实现思路就是这样,接下来咱们来看看一些具体的实现细节。 2. 实现细节2.1 加载细节首先咱们来看看加载的细节。 小伙伴们晓得,单页面我的项目的入口是 main.js,路由加载的内容在 src/permission.js 文件中,该文件在 main.js 中被引入,src/permission.js 中的前置导航守卫内容如下: router.beforeEach((to, from, next) => { NProgress.start() if (getToken()) { to.meta.title && useSettingsStore().setTitle(to.meta.title) /* has token*/ if (to.path === '/login') { next({ path: '/' }) NProgress.done() } else { if (useUserStore().roles.length === 0) { isRelogin.show = true // 判断以后用户是否已拉取完user_info信息 useUserStore().getInfo().then(() => { isRelogin.show = false usePermissionStore().generateRoutes().then(accessRoutes => { // 依据roles权限生成可拜访的路由表 accessRoutes.forEach(route => { if (!isHttp(route.path)) { router.addRoute(route) // 动静增加可拜访路由表 } }) next({ ...to, replace: true }) // hack办法 确保addRoutes已实现 }) }).catch(err => { useUserStore().logOut().then(() => { ElMessage.error(err) next({ path: '/' }) }) }) } else { next() } } } else { // 没有token if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,间接进入 next() } else { next(`/login?redirect=${to.fullPath}`) // 否则全副重定向到登录页 NProgress.done() } }})我跟大家捋一下这个前置导航守卫中的思路: ...

July 20, 2022 · 3 min · jiezi

关于springboot:如何在spring-boot-使用-gitlab的Api

前言最近有我的项目需要是通过gitlab的api获取其中的数据。来记录一下我的踩坑的过程。首先先尝试了获取users数据,即所有用户的数据。 这里是gitlab官网API文档 https://docs.gitlab.com/ee/ap... 这里说一下我刚开始的做法和老师倡议的做法。 最开始的做法1. 获取Admin access token首先咱们须要获取gitlab的Admin access token, 并且在申请的时候附带这个token,gitlab能力认证咱们的身份并返回数据。 1.登录 GitLab。 2.登录后,点击右上角的头像图标而后抉择 Preferences。 3.在 access token 界面就能够新建token了。 当你是group的管理员之后,就能够通过该token获取users了。 2.应用spring boot单元测试发动申请这里我应用了apache发动http申请,并应用了fastjson解决申请后所返回的json数据。 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.9</version></dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version></dependency>以下是我测试的代码。来解释一下: 首先定义你的要申请的url,格局如代码。也能够看官网文档。构建Http申请类,并设置参数。参数必须要有private_token。这里我加的 without_project_bots能够过滤机器人user。应用execute函数发动get申请,并接管返回的json数据。从json数组转换成实体数组这样就取得了咱们须要的数据。 @Test void getUsers() throws IOException, URISyntaxException { String url = "https://your gitlab address/api/v4/users"; CloseableHttpClient httpclients = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); // 设置参数信息 URI uri = new URIBuilder(httpGet.getURI()) .addParameter("private_token", "your amind token") .addParameter("without_project_bots", "true") .build(); ((HttpRequestBase) httpGet).setURI(uri); // 发动申请 CloseableHttpResponse response = httpclients.execute(httpGet); // 解决数据 HttpEntity httpEntity = (HttpEntity) response.getEntity(); String result = EntityUtils.toString(httpEntity,"UTF-8"); // 从json转换成实体 final ObjectMapper objectMapper = new ObjectMapper(); User[] langList = objectMapper.readValue(result, User[].class); logger.debug(String.valueOf(langList)); response.close(); }期间我遇到的问题1. java须要增加证书因为咱们发动的申请地址是HTTPS的,而https是对链接加了平安证书SSL的,如果服务器中没有相干链接的SSL证书,它就不可能信赖那个链接, 就会报sun.security.validator.ValidatorException谬误。 ...

July 18, 2022 · 2 min · jiezi

关于springboot:knife4j通过js动态刷新全局参数

背景之前在为框架集成knife4j接口调试查看工具,应用了一段时间,应用体验上比拟繁琐,因为接口都须要token,所以每次都要去f12查看token复制再创立全局参数,可能我只须要测试一个接口然而步骤少不了,针对此问题框架做了一些优化 设计剖析框架后端针对系统管理员减少一个依据用户间接生成token的接口,将获取到的token通过js形式间接附加到knife4j的全局参数中,这样就只须要点击获取token按钮咱们就能够间接进行任意接口的调试工作,通过f12剖析发现,knife4j的全局参数变量是存储在浏览器数据库IndexedDB中,数据表为keyvaluepairs,对应的数据行key为Knife4jOfficeParameter 进一步剖析字段名称为SwaggerBootstrapUiInstance68c7b0eebe75b10d20003678a43730cb,存储值就是咱们增加的全局参数设置的数组列表,字段名是由SwaggerBootstrapUiInstance+编码命名的,所以咱们只有搞定编码的生成就能够本人通过js赋值了 因为knife4j集成的doc.html页面是由vue打包生成的,js做过编译解决,所以源码咱们须要具体的vue工程中查看,通过剖析查找SwaggerBootstrapUiInstance关键字 代码门路:knife4j/knife4j-vue/src/core/Knife4jAsync.js 生成规定:生成的编码由name(分组对象)+location(url地址)+version(版本号)生成的字符串md5后的值 上述的name,location,version三个值是通过申请swagger-resources接口获取的,返回值为一个数组,依据抉择的group去匹配 代码实现剖析结束后就能够进行代码操作了,其中波及到IndexedDB的操作简略学习一下即可 获取所有资源 function initResourceInfo() { $.get(resourceUrl, function(data, status) { data.forEach(element => { pageData.resourceMap[element.name] = element; }); }); }设置全局参数 function refreshKnife4jConfig(token) { var selectApiName = window.knife4jFrame.contentWindow.document.getElementsByClassName("ant-select-selection-selected-value")[0].innerText; var resource = pageData.resourceMap[selectApiName]; if ('indexedDB' in window) { var req = indexedDB.open("localforage"); req.onupgradeneeded = function(event) { } req.onsuccess = function(event) { console.log('数据库开启胜利'); var db = event.target.result; var table = db.transaction(['keyvaluepairs'], 'readwrite').objectStore('keyvaluepairs') var key = resource.name + resource.location + resource.swaggerVersion; var id = hpMD5(key).toLowerCase(); var configData = {} configData["SwaggerBootstrapUiInstance" + id] = [ { in: "header", name: "token", pkid: "tokenheader", value: token }, { in: "header", name: "Content-Type", pkid: "Content-Typeheader", value: "application/json" }, ] table.put(configData, "Knife4jOfficeParameter"); } req.onerror = function() { console.log("数据库开启出错"); } } else { console.log('你的浏览器不反对IndexedDB'); } }

July 16, 2022 · 1 min · jiezi

关于springboot:SpringBoot实现自定义路由覆盖

背景公司最近有一个我的项目二期须要对一些性能进行革新,波及局部框架内置业务接口个性化定制,兼容老接口性能并且减少一部分新的数据返回,因为前端调用这些接口散布较多且较为系统,批改测试老本较大,所以打算在框架层面提供路由笼罩性能,放慢我的项目进度缩小无技术含量的批改带来的零碎危险 设计提供自定义注解指定须要笼罩的路由及新路由地址系统启动时扫描所有注解数据并进行映射解决注册自定义路由映射配置类实现注解定义@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface CoverRoute { String value() default "";}注解扫描及治理在系统启动时调用initRoute办法,把原路由和对应的笼罩路由映射到map键值对中 public class ConverRouteUtil { private static HashMap<String, String> mappingRegist = new HashMap<>(); public static void initRoute(Class runtimeClass, List<String> extraPackageNameList) { List<Class<?>> scanClassList = new ArrayList<>(); if (!runtimeClass.getPackage().getName().equals(Application.class.getPackage().getName())) { scanClassList.addAll(ScanUtil.getAllClassByPackageName_Annotation(runtimeClass.getPackage(), CoverRoute.class)); } for (String packageName : extraPackageNameList) { scanClassList.addAll(ScanUtil.getAllClassByPackageName_Annotation(packageName, CoverRoute.class)); } for (Class clazz : scanClassList) { CoverRoute coverRoute = (CoverRoute) clazz.getAnnotation(CoverRoute.class); if (StringUtil.isEmpty(coverRoute.value())) { continue; } RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class); String classRoute = ""; if (requestMapping != null) { classRoute = requestMapping.value()[0]; } else { continue; } List<Method> methodList = Arrays.asList(clazz.getDeclaredMethods()); for (Method method : methodList) { PostMapping postMapping = method.getAnnotation(PostMapping.class); String methodRoute = ""; if (postMapping != null) { methodRoute = postMapping.value()[0]; } else { GetMapping getMapping = method.getAnnotation(GetMapping.class); if (getMapping != null) { methodRoute = getMapping.value()[0]; } } if (!StringUtil.isEmpty(classRoute) && !StringUtil.isEmpty(methodRoute)) { String orginalRoute = coverRoute.value() + methodRoute; String redirectRoute = classRoute + methodRoute; mappingRegist.put(orginalRoute, redirectRoute); } } } if (mappingRegist.size() > 0) { System.out.println("扫描路由办法笼罩:" + mappingRegist.size() + "个"); } } public static boolean checkExistCover(String orginalRoute) { return mappingRegist.containsKey(orginalRoute); } public static String getRedirectRoute(String orginalRoute) { return mappingRegist.get(orginalRoute); }}自定义RequestMappingHandlerMapping继承RequestMappingHandlerMapping重写lookupHandlerMethod办法,在spring进行路由寻址时进行笼罩 ...

July 16, 2022 · 2 min · jiezi

关于springboot:Spring-BootVue3-动态菜单实现思路梳理

对于 Spring Boot + Vue3 的动静菜单,松哥之前曾经写了两篇文章了,这两篇文章次要是从代码上和大家剖析动静菜单最终的实现形式,然而还是有小伙伴感觉没太看明确,感觉不足一个提纲挈领的思路,所以,明天松哥再整一篇文章和大家再来捋一捋这个问题,心愿这篇文章能让小伙伴们彻底搞清楚这个问题。 1. 整体思路首先咱们来看整体思路。 光说思路大家还是云里雾里,咱们联合具体的效果图来看: 最终菜单显示成果相似上图,我把这里的菜单分为了四类: 有父有子:像系统管理那种,既有父菜单,又有子菜单。只有一个一级菜单,这种又细分为三种状况: 一般的菜单,点击之后在左边主页面关上某个性能页面。一个超链接,但不是外链,是一个在以后零碎中关上的内部网页,点击之后,会在左边的主页面中新开一个选项卡,这个选项卡中显示的是一个内部网页(实质上是通过 iframe 标签引入的一个内部网页)。一个超链接,并且还是一个外链,点击之后,间接在浏览器中关上一个新的选项卡,新的选项卡中展现一个内部链接。整体上来说,就分为这四种状况。其中 1、2.1、2.3 应该都好了解,2.2 有的小伙伴可能不分明,我给大家截个图看下就晓得了: 四种菜单对应的 JSON 格局别离如下: 有父有子:{ "name": "Monitor", "path": "/monitor", "hidden": false, "redirect": "noRedirect", "component": "Layout", "alwaysShow": true, "meta": { "title": "系统监控", "icon": "monitor", "noCache": false, "link": null }, "children": [{ "name": "Online", "path": "online", "hidden": false, "component": "monitor/online/index", "meta": { "title": "在线用户", "icon": "online", "noCache": false, "link": null } }, { "name": "Job", "path": "job", "hidden": false, "component": "monitor/job/index", "meta": { "title": "定时工作", "icon": "job", "noCache": false, "link": null } }]}只有一个一级菜单,且一级菜单点击后是一个性能页面:{ "path": "/", "hidden": false, "component": "Layout", "children": [{ "name": "Role", "path": "role", "hidden": false, "component": "system/role/index", "meta": { "title": "角色治理", "icon": "peoples", "noCache": false, "link": null } }]}只有一个一级菜单,且一级菜单点击之后在以后零碎中一个新的选项卡里关上一个网页:{ "name": "Http://www.javaboy.org", "path": "/", "hidden": false, "component": "Layout", "meta": { "title": "TienChin健身官网", "icon": "guide", "noCache": false, "link": null }, "children": [ { "name": "Www.javaboy.org", "path": "www.javaboy.org", "hidden": false, "component": "InnerLink", "meta": { "title": "TienChin健身官网", "icon": "guide", "noCache": false, "link": "http://www.javaboy.org" } } ]}只有一个一级菜单,且一级菜单点击之后在浏览器关上一个新的选项卡:{ "name": "Http://www.javaboy.org", "path": "http://www.javaboy.org", "hidden": false, "component": "Layout", "meta": { "title": "TienChin健身官网", "icon": "guide", "noCache": false, "link": "http://www.javaboy.org" }}依据以上四种不同的 JSON,咱们总结出以下法则: ...

July 15, 2022 · 5 min · jiezi

关于springboot:SpringBoot-使用-Jedis

前置条件1、开启 Redis 服务器,命令行执行 redis-server.exe。2、创立 SpringBoot 工程,在引入根本的依赖之外,还须要额定引入: <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.3</version></dependency>应用 Redis 的五种数据类型前置条件连贯 Redis 服务器并测试通过: Jedis jedis = new Jedis("localhost");System.out.println(jedis.ping());//输入:PONG字符串(String)根本的 get & set 办法应用: jedis.set("myKey", "myValue");System.out.println(jedis.get("myKey"));//输入://myValue其余的罕用办法参考:Redis String 命令。 哈希(Hash)根本的 hmset & hget & hgetAll 办法应用: Map<String, String> map = new HashMap<>();map.put("mapKey1", "mapValue1");map.put("mapKey2", "mapValue2");map.put("mapKey3", "mapValue3");jedis.hmset("myMap", map);System.out.println(jedis.hget("myMap", "mapKey1"));System.out.println(jedis.hgetAll("myMap").toString());//输入://mapValue1//{mapKey2=mapValue2, mapKey1=mapValue1, mapKey3=mapValue3}其余罕用办法参考:Redis Hash 命令。 列表(List)根本的 lpush & lindex & lrange 办法的应用: jedis.lpush("myList", "1", "2", "3", "A", "B", "C");System.out.println(jedis.lindex("myList", 1));System.out.println(jedis.lrange("myList", 0, 2));//输入://B//[C, B, A]留神:lpush 是将一个或多个值插入到列表头部。其余罕用办法参考:Redis List 命令。 ...

July 14, 2022 · 1 min · jiezi

关于springboot:避坑Around与Transactional混用导致事务不回滚

前言上个月,共事出于好奇在群里问AOP的盘绕告诉与事务注解混合用会不会导致出现异常不回滚的状况。这个问题我一下子答复不上来,因为平时没这样用过,在好奇心的驱使下,我调试了半天终于失去后果,明天我就开展讲讲。(源码解读在最初面,感兴趣的能够看看。) 论断首先通知大家的是,同时应用AOP盘绕告诉和事务注解之后,最终生成的拦截器链的绝对程序是事务的拦截器在后面,AOP盘绕告诉的拦截器在前面。 在事务的实现中将拦截器的执行过程包裹在了try-catch块中,产生异样后依据配置来决定是否回滚事务。(详见org.springframework.transaction.interceptor.TransactionInterceptor#invoke),因而事务前面的拦截器都会影响事务的执行后果。如果在AOP盘绕告诉外面将拦截器链执行后果中的异样给吞掉,那么事务就会失常提交而不会回滚。 示例业务代码业务代码中间接抛出异样,代码如下所示。 package com.example.demo.aspect;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;/** * @Author Paul * @Date 2022/7/3 15:52 */@Servicepublic class CustomService { @Transactional(rollbackFor = Exception.class) public void echo(){ boolean s = true; if (s){ throw new RuntimeException("test"); } System.out.println("Hello------"); }}盘绕告诉盘绕告诉中捕获异样并打印日志,代码如下所示。 package com.example.demo.aspect;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * @Author Paul * @Date 2022/7/3 15:49 */@Component@Aspectpublic class CustomAspect { @Pointcut("execution(* com.example.demo.aspect..*(..))") public void pointcut(){ } @Around("pointcut()") public void around(ProceedingJoinPoint joinPoint){ try { joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } }}测试类这里是用get申请来测试(原本应该用 unit test 来测试的,然而懒得写代码了,手动测试和 UT 的成果一样),代码如下所示。 ...

July 13, 2022 · 4 min · jiezi

关于springboot:SpringBoot进行文件上传下载的处理方式

文件上传针对前端form-data模式提交的文件能够间接应用spring的web包下的MultipartFile类进行接管: @PostMapping("/upload") public R<String> uploadImg(MultipartFile file){ return R.success(); }准存到本地有两个办法,一个是应用MultipartFile的transferTo()办法,需注意这个办法入参写入的目标地址要应用绝对路径;二是获取输出流通过传统的形式写入。 文件下载这里采纳的下载方式为间接向浏览器输入文件流的形式: @GetMapping("/download") public void downloadImg(@RequestParam(value = "name", required = false) @NotBlank(message = "文件名称为空") String name, HttpServletResponse response){ try { commonService.doDownload(name, response.getOutputStream()); } catch (IOException e) { log.error(e.getMessage()); throw new RuijiException(ExceptionCodeEnum.FILE_DOWNLOAD_FAIL); } }这个controller办法为应用文件名称下载对应文件,业务层下载逻辑为: public void doDownload(String name, OutputStream outputStream){ String destFilePath = System.getProperty("user.dir") + "\\img"; File file = new File(destFilePath + "\\" + name); try { FileUtils.copyFile(file, outputStream); } catch (IOException e) { log.error(e.getMessage()); log.error("文件下载失败"); throw new RuijiException(ExceptionCodeEnum.FILE_DOWNLOAD_FAIL); } }这里应用commons-io包的FileUtils工具类实现的文件流输入,间接输入到HttpservletResponse的输入流即可。 ...

July 11, 2022 · 1 min · jiezi

关于springboot:springboot整合redis配置

依赖局部<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.X集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency>springboot2.0版本默认应用lettuce连接池,默认不应用,只有在配置文件中应用连接池配置才开启,连接池须要依赖commons-pools2的依赖。 配置文件spring.redis.host=127.0.0.1spring.redis.port=6379spring.redis.database= 0#连贯超时工夫spring.redis.timeout=1800000#连接池最大连接数spring.redis.lettuce.pool.max-active=20#连接池最大阻塞连接时间,负值为无限度spring.redis.lettuce.pool.max-wait=-1#最大阻塞等待时间(正数示意没限度)spring.redis.lettuce.pool.max-idle=5#最大闲暇线程数spring.redis.lettuce.pool.min-idle=0配置RedisTemplate@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化形式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; }

July 11, 2022 · 1 min · jiezi

关于springboot:在jsp里-调用容器使用sevice

jsp获取spring容器<%ServletContext sc = this.getServletConfig().getServletContext();ApplicationContext ac2 = WebApplicationContextUtils.getWebApplicationContext(sc);RedisService redisService=(RedisService)ac2.getBean("redisService");%>service demo@Service("redisService")

July 3, 2022 · 1 min · jiezi

关于springboot:Spring-Boot工程中如何优雅地处理异常

转自: https://taosha.club/topic/61a...对于异样解决, 有几个准则应用异样而非返回码在很久以前,许多语言都不反对异样。这些语言解决和汇报谬误的伎俩都无限。你要么设置一个谬误标识,要么返回给调用者查看的错误码。以下代码展现了这些伎俩 public class DeviceController { public void sendShutDown() { DeviceHandle handle = getHandle(DEV1); // Check the state of the device if (handle != DeviceHandle.INVALID) { // Save the device status to the record field retrieveDeviceRecord(handle); // If not suspended, shut down if (record.getStatus() != DEVICE_SUSPENDED) { pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } else { logger.log("Device suspended. Unable to shut down"); } } else { logger.log("Invalid handle for: " + DEV1.toString()); } }}这类伎俩的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻查看谬误。可怜的是,这个步骤很容易被忘记。所以,遇到谬误时,最好抛出一个异样。调用代码很整洁,其逻辑不会被错误处理搞乱。 ...

June 29, 2022 · 2 min · jiezi

关于springboot:SpringBoot-Docker-认证指南上

许多人应用容器来包装他们的 Spring Boot 应用程序,而构建容器并不是一件简略的事件。这是针对 Spring Boot 应用程序开发人员的指南,容器对于开发人员来说并不总是一个好的形象。它们迫使你去理解和思考低层次的问题。然而,有时可能会要求您创立或应用容器,因而理解构建块是值得的。在本指南中,咱们旨在向您展现如果您面临须要创立本人的容器的前景,您能够做出的一些抉择。 咱们假如您晓得如何创立和构建根本的 Spring Boot 应用程序。如果没有,请转到入门指南之一 ——例如,对于构建REST 服务的指南。从那里复制代码并练习本指南中蕴含的一些想法。 还有一个对于Docker的入门指南,这也是一个很好的终点,但它没有涵盖咱们在此处介绍的抉择范畴或具体介绍它们。 一个根本的 DockerfileSpring Boot 应用程序很容易转换为可执行的 JAR 文件。所有的入门指南都是这样做的,你从Spring Initializr下载的每个应用程序都有一个构建步骤来创立一个可执行的 JAR。应用 Maven,你运行./mvnw install,应用 Gradle,你运行./gradlew build。运行该 JAR 的根本 Dockerfile 将如下所示,位于我的项目的顶层: Dockerfile FROM openjdk:8-jdk-alpineVOLUME /tmpARG JAR_FILECOPY ${JAR_FILE} app.jarENTRYPOINT ["java","-jar","/app.jar"]复制JAR_FILE您能够作为命令的一部分传入docker(Maven 和 Gradle 不同)。对于 Maven,以下命令无效: docker build --build-arg JAR_FILE=target/*.jar -t myorg/myapp .复制对于 Gradle,以下命令无效: docker build --build-arg JAR_FILE=build/libs/*.jar -t myorg/myapp .复制一旦你抉择了一个构建零碎,你就不须要ARG. 您能够对 JAR 地位进行硬编码。对于 Maven,如下所示: Dockerfile FROM openjdk:8-jdk-alpineVOLUME /tmpCOPY target/*.jar app.jarENTRYPOINT ["java","-jar","/app.jar"]复制而后咱们能够应用以下命令构建镜像: docker build -t myorg/myapp .复制而后咱们能够通过运行以下命令来运行它: ...

June 28, 2022 · 5 min · jiezi

关于springboot:亲妈版SpringBoot生成二维码

前言:本文基于JAVA环境,以SpringBoot框架为根底开发。 正式开发步骤:1、引入Maven依赖<!--引入生成二维码的依赖--><!-- https://mvnrepository.com/artifact/com.google.zxing/core --><dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.0</version></dependency><!-- https://mvnrepository.com/artifact/com.google.zxing/javase --><dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.3.0</version></dependency>2、创立生成二维码工具类QRCodeGenerator.java/** * Java生成二维码 * @date: 2022/6/16 21:21 * @author: gxw */public class QRCodeGenerator { //生成的二维码的门路 private static String QR_CODE_IMAGE_PATH = PropertiesValues.getPropertiesValue("wx.pay.img.generator_path", "application.properties"); //正式上线前缀 private static String PAY_PREFIX = PropertiesValues.getPropertiesValue("wx.pay.img.prefix", "application.properties"); //二维码图片的宽度 private static int WIDTH = 500; //二维码图片的高度 private static int HEIGHT = 500; public static String generateQRCodeImage(String content) throws Exception { QR_CODE_IMAGE_PATH = PropertiesValues.getPropertiesValue("wx.pay.img.generator_path", "application.properties"); QRCodeWriter qrCodeWriter = new QRCodeWriter(); Hashtable hints = new Hashtable(); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);//H最高容错等级 hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT,hints); String imgName = RandomUtils.generateRandomString(6) + ".png"; QR_CODE_IMAGE_PATH += imgName; File file = new File(QR_CODE_IMAGE_PATH); if(!file.exists()){ file.mkdirs(); } Path path = FileSystems.getDefault().getPath(QR_CODE_IMAGE_PATH); MatrixToImageWriter.writeToPath(bitMatrix, "PNG", path); return PAY_PREFIX + imgName; }}3、测试生成胜利,能够扫码测试哟 ...

June 24, 2022 · 1 min · jiezi

关于springboot:亲妈版SpringBoot集成微信Native网页支付

前言:本文基于PC网站须要,所开发的微信Native网页领取,技术栈,后端蕴含JAVA、SpringBoot、Maven、Mybatis、Mysql,前端蕴含HTML5、JS、JQUERY、AJAX等 开发前筹备:1、申请APPID申请形式:第一种:微信小程序,请返回小程序平台申请第二种:微信公众号,请返回公众平台申请第三种:如果商户已领有以上两种的其一,则能够返回开放平台申请 2、申请mchId步骤:1、首先返回商户号申请平台申请2、其次申请胜利后,微信官网会向申请时所填写的分割邮箱下发告诉邮件,内容蕴含申请胜利的mchid及其登录账号密码,亦能够,登录商户平台,在【账户核心】的商户信息哪里也能够看到3、最初,请妥善保留2的信息留神:一个mchid只能对应一个结算币种,若须要应用多个币种收款,须要申请对应数量的mchid。 3、绑定APPID及mchid步骤:1、登录微信商户平台,点击【产品核心】,在左侧点击【AppID账号治理】,而后再点击【我关联的AppID账号】,呈现列表,而后点击【+关联AppID】,而后输出商户号,提交就实现第一步了。2、登录微信公众平台(公众号,只有服务号才有微信领取,订阅号没有,望留神!),点击左侧“广告与服务->微信领取->商户号治理->待关联商户号”,而后在列表中找到确认信息,点击确认,即可实现绑定。 留神:只有服务号才有微信领取,订阅号没有,望留神! 提供流程图:1、2、3、 4、去商户平台配置APIV3密钥步骤:1、登录微信商户平台,进入【账户核心 > API平安 】目录,设置APIV3密钥。 2、在弹出窗口中点击“已沟通”。 3、输出API密钥,内容为32位字符,包含数字及大小写字母。点击获取短信验证码。(MD5加密收费获取32位字符)4、输出短信验证码,点击“确认”即设置胜利。 5、实现 5、下载并配置商户证书步骤:1、【商户平台】商户可登录微信商户平台,在【账户核心】->【API平安】->【申请证书】 2、【商户平台】在弹出窗口中点击“确定”。 3、【商户平台】在弹出窗口内点击“下载证书工具”按钮下载证书工具。 4、【证书工具】装置证书工具并关上,抉择证书须要存储的门路后点击“申请证书”。 5、【证书工具】在证书工具中,将复制的商户信息粘贴并点击“下一步”。 6、获取申请串 【证书工具】中操作【商户平台】中操作【商户平台】中操作7、【证书工具】复制证书串 8、【证书工具】粘贴证书串 9、【证书工具】生成证书胜利 10、在【证书工具】-“生成证书”环节,已实现申请证书流程,点击“查看证书文件夹”,查看已生成的证书文件。 11、实现 留神:在后面带【商户平台】,意思是在商户平台中的操作,后面带【证书工具】,意思是在证书工具中操作,望勿弄混! 6、获取证书中的商户序列号,领取须要用到步骤:1、【商户平台】商户可登录微信商户平台,在【账户核心】->【API平安】->【治理证书】2、关上的窗口,展现证书列表,第一列就是证书序列号,将其保留,以待前面用到。 正式开发步骤:提要:步骤分的很细,很费神,但能够更直辩的了解! 1、Maven引入领取依赖包<!--微信领取依赖包--><dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version></dependency>2、Maven引入领取签名、应答签名的验证封装依赖包<!--微信领取签名、应答签名的验证封装包--><dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.7</version></dependency>3、Maven引入生成二维码的依赖包<!--引入生成二维码的依赖--><!-- https://mvnrepository.com/artifact/com.google.zxing/core --><dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.0</version></dependency><!-- https://mvnrepository.com/artifact/com.google.zxing/javase --><dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.3.0</version></dependency>4、创立微信领取工具类WeChatPayUtils.java提要:本工具类:蕴含下单办法、查单办法,以及可能会用到的转换以及获取办法等 /** * 微信领取工具类 * @date 2022/6/16 * @author gxw */public class WeChatPayUtils { /*公众号/小程序信息*/ //appId private static final String APP_ID = PropertiesValues.getPropertiesValue("wx.pay.app_id", "application.properties"); //secret private static final String APP_SECRET = ""; /*商户信息*/ //商户号mch_id private static final String MCH_ID = PropertiesValues.getPropertiesValue("wx.pay.mch_id", "application.properties"); //商户私钥mch_key private static final String MCH_KEY = "下载证书的***key.pem文件内容"; //商户证书序列号 private static final String MCH_SERIAL_NO = PropertiesValues.getPropertiesValue("wx.pay.mch_serial_no", "application.properties"); //API3私钥 private static final String MCH_API_V3_KEY = PropertiesValues.getPropertiesValue("wx.pay.api_key", "application.properties"); /*领取信息*/ //native 对立下单API public static final String NATIVE_PAY_API = PropertiesValues.getPropertiesValue("wx.pay.native.url", "application.properties"); //native 商户订单号查单API public static final String NATIVE_PAY_OUT_TRADE_NO_QUERY_ORDER_API = PropertiesValues.getPropertiesValue("wx.pay.out_trade_no.query_order", "application.properties"); //native 微信零碎订单号查单API public static final String NATIVE_PAY_TRANSACTIONS_ID_QUERY_ORDER_API = PropertiesValues.getPropertiesValue("wx.pay.transactions_id.query_order", "application.properties"); //货币类型 public static final String CURRENCY_CNY = "CNY"; //领取类型 public static final String TRADE_TYPE = "NATIVE"; //异步回调地址 public static final String NOTIFY_URL = PropertiesValues.getPropertiesValue("wx.pay.notify_url", "application.properties"); /** * NATIVE获取CloseableHttpClient */ private static CloseableHttpClient initHttpClient(){ PrivateKey merchantPrivateKey = null; try { merchantPrivateKey = PemUtil .loadPrivateKey(new ByteArrayInputStream(MCH_KEY.getBytes("utf-8")));// //*加载证书管理器实例*//*// // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)// AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(// new WechatPay2Credentials(MCH_ID, new PrivateKeySigner(MCH_SERIAL_NO, merchantPrivateKey)),MCH_API_V3_KEY.getBytes("utf-8"));// //获取单例实例 CertificatesManager certificatesManager = CertificatesManager.getInstance();// //向证书管理器减少商户信息,并开启自动更新 certificatesManager.putMerchant( MCH_ID, new WechatPay2Credentials( MCH_ID, new PrivateKeySigner(MCH_SERIAL_NO, merchantPrivateKey)), MCH_API_V3_KEY.getBytes("utf-8") ); //从证书管理器取得验签器 Verifier verifier = certificatesManager.getVerifier(MCH_ID); CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() .withMerchant(MCH_ID, MCH_SERIAL_NO, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)).build(); return httpClient; } catch (Exception e){ e.printStackTrace(); } return null; } /** * NATIVE 对立下单 * @param money * @param body * @return */ public static Map<String, String> native_payment_order(String money, String body, String outTradeNo) { try { CloseableHttpClient httpClient = initHttpClient(); HttpPost httpPost = new HttpPost(NATIVE_PAY_API); // 申请body参数 String reqdata = "{" //+ "\"time_expire\":\"2018-06-08T10:34:56+08:00\"," + "\"amount\": {" + "\"total\":" + Integer.parseInt(String.valueOf(Float.parseFloat(money) * 100).split("\\.")[0]) + "," + "\"currency\":\"" + CURRENCY_CNY + "\"" + "}," + "\"mchid\":\"" + MCH_ID + "\"," + "\"description\":\"" + body + "\"," + "\"notify_url\":\"" + NOTIFY_URL + "\"," + "\"out_trade_no\":\"" + outTradeNo + "\"," + "\"goods_tag\":\"课程购买\"," + "\"appid\":\"" + APP_ID + "\"" + "}"; StringEntity entity = new StringEntity(reqdata, "utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); //实现签名并执行申请 CloseableHttpResponse response = null; Map<String, String> resultMap = new HashMap<>(); try { response = httpClient.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { //解决胜利 String codeUrl = EntityUtils.toString(response.getEntity()); codeUrl = codeUrl.substring(codeUrl.indexOf("w"), codeUrl.indexOf("}") - 1); String path = QRCodeGenerator.generateQRCodeImage(codeUrl); resultMap.put("code", "200"); resultMap.put("data", path); System.out.println("生成胜利,门路为:" + path); System.out.println("success,return body = " + codeUrl); return resultMap; } else if (statusCode == 204) { //解决胜利,无返回Body System.out.println("success"); resultMap.put("code", "204"); resultMap.put("msg", "解决胜利,但无返回Body"); return resultMap; } else { System.out.println("failed,resp code = " + statusCode + ",return body = " + EntityUtils.toString(response.getEntity())); throw new IOException("request failed"); } } catch (Exception e) { e.printStackTrace(); } finally { response.close(); } } catch (Exception e) { e.printStackTrace(); } return null; } /** * NATIVE 查问订单 * @param outTradeNo 商户订单号 * @return */ public static Map<String, String> native_query_order(String outTradeNo) { CloseableHttpResponse response = null; try { String url = NATIVE_PAY_OUT_TRADE_NO_QUERY_ORDER_API + outTradeNo; //申请URL URIBuilder uriBuilder = new URIBuilder(url); uriBuilder.setParameter("mchid", MCH_ID); //实现签名并执行申请 HttpGet httpGet = new HttpGet(uriBuilder.build()); httpGet.addHeader("Accept", "application/json"); response = initHttpClient().execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); Map<String, String> resultMap = new HashMap<>(); if (statusCode == 200) { String resData = EntityUtils.toString(response.getEntity()); Map<String, Object> data = JSON.parseObject(resData, HashMap.class); String tradeState = String.valueOf(data.get("trade_state")); if("SUCCESS".equals(tradeState)){ resultMap.put("msg", "领取胜利"); }else if("NOTPAY".equals(tradeState)){ resultMap.put("msg", "订单尚未领取"); }else if("CLOSED".equals(tradeState)){ resultMap.put("msg", "此订单已敞开,请从新下单"); }else if("USERPAYING".equals(tradeState)){ resultMap.put("msg", "正在领取中,请尽快领取实现哦"); }else if("PAYERROR".equals(tradeState)){ resultMap.put("msg", "领取失败,请从新下单"); } resultMap.put("code", "200"); resultMap.put("tradeState", tradeState);// resultMap.put("openId", String.valueOf(JSON.parseObject(String.valueOf(data.get("payer")), HashMap.class).get("openid")));// resultMap.put("transactionId", String.valueOf(data.get("transaction_id"))); return resultMap; } else if (statusCode == 204) { System.out.println("success");resultMap.put("code", "204"); resultMap.put("msg", "解决胜利,但无返回Body"); return resultMap; } else { System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity())); throw new IOException("request failed"); } } catch (URISyntaxException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } /** * APIV3密钥版,NATIVE领取回调参数解密 * @param map * @return */ public static Map<String, Object> paramDecodeForAPIV3(Map<String, Object> map){ //应用微信SDK提供的AesUtil工具类和APIV3密钥进行签名验证 AesUtil aesUtil = new AesUtil(MCH_API_V3_KEY.getBytes(StandardCharsets.UTF_8)); JSONObject paramsObj = new JSONObject(map); JSONObject rJ = paramsObj.getJSONObject("resource"); Map<String, String> paramMap = (Map) rJ; try { //如果APIV3密钥和微信返回的resource中的信息不统一,是拿不到微信返回的领取信息 String decryptToString = aesUtil.decryptToString( paramMap.get("associated_data").getBytes(StandardCharsets.UTF_8), paramMap.get("nonce").getBytes(StandardCharsets.UTF_8), paramMap.get("ciphertext")); //验证胜利后将获取的领取信息转为Map Map<String, Object> resultMap = WeChatPayUtils.strToMap(decryptToString); return resultMap; } catch (GeneralSecurityException e) { e.printStackTrace(); } return null; } /** * 交易起始工夫 */ public static String getTimeStart(){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); return sdf.format(new Date()); } /** * 交易完结工夫(订单生效工夫) * @return */ public static String getTimeExpire(){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); Calendar now = Calendar.getInstance(); now.add(Calendar.MINUTE, 30); return sdf.format(now.getTimeInMillis()); } /** * 获取32位随机字符串 * @return */ public static String getRandomStr() { return UUID.randomUUID().toString().replace("-", ""); } /** * 生成订单号 * @return */ public static String generateOrderNo() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); return sdf.format(new Date()) + makeRandom(15); } /** * 生成随机数 纯数字 * * @return */ public static String makeRandom(int len) { return RandomUtils.generateRandomString(len); } /** * 将Map转换为XML格局的字符串 * * @param data Map类型数据 * @return XML格局的字符串 * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { org.w3c.dom.Document document = WXPayXmlUtil.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key: data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(com.gxw.util.StringUtils.toUTF8(value))); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { } return output; } /** * Xml字符串转换为Map * * @param xmlStr * @return */ public static Map<String, String> xmlStrToMap(String xmlStr) { Map<String, String> map = new HashMap<String, String>(); Document doc; try { doc = DocumentHelper.parseText(xmlStr); Element root = doc.getRootElement(); List children = root.elements(); if (children != null && children.size() > 0) { for (int i = 0; i < children.size(); i++) { Element child = (Element) children.get(i); map.put(child.getName(), child.getTextTrim()); } } } catch (DocumentException e) { e.printStackTrace(); } return map; } /** * 办法用处: 对所有传入参数依照字段名的 ASCII 码从小到大排序(字典序),并且生成url参数串<br> * 实现步骤: <br> * * @param paraMap 要排序的Map对象 * @param urlEncode 是否须要URLENCODE * @param keyToLower 是否须要将Key转换为全小写 * true:key转化成小写,false:不转化 * @return */ public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower) { String buff = ""; Map<String, String> tmpMap = paraMap; try { List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet()); // 对所有传入参数依照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { @Override public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 结构URL 键值对的格局 StringBuilder buf = new StringBuilder(); for (Map.Entry<String, String> item : infoIds) { if (StringUtils.isNotBlank(item.getKey())) { String key = item.getKey(); String val = item.getValue(); if (urlEncode) { val = URLEncoder.encode(val, "utf-8"); } if (keyToLower) { buf.append(key.toLowerCase() + "=" + val); } else { buf.append(key + "=" + val); } buf.append("&"); } } buff = buf.toString(); if (buff.isEmpty() == false) { buff = buff.substring(0, buff.length() - 1); } } catch (Exception e) { return null; } return buff; } /** * 字符串转换为Map汇合 * @param str * @return */ public static Map<String, Object> strToMap(String str){ Map<String, Object> map = JSON.parseObject(str, HashedMap.class); return map; }}5、Controller领取下单接口 /** * native 领取下单 * @param request * @param lcGoodsOrder * @return */ @RequestMapping("/nativePaymentOrder") @NoVerify public synchronized Map<String, String> nativePaymentOrder( HttpServletRequest request, @RequestBody LcGoodsOrder lcGoodsOrder){ logger.info("【微信Native领取-领取下单】************开始解决*************"); //商户订单号 String outTradeNo = ALiPayUtils.generateOrderNum(); Map<String, String> data = WeChatPayUtils.native_payment_order(lcGoodsOrder.getTotalAmount(), lcGoodsOrder.getSubject(), outTradeNo); if("200".equals(data.get("code"))){ logger.info("【微信Native领取-领取下单】下单胜利,下单用户:{}, 下单手机号:{}", lcGoodsOrder.getPayUserName(), lcGoodsOrder.getPayUserPhone()); /*订单记录数据库中*/ lcGoodsOrder.setOutTradeNo(outTradeNo); lcGoodsOrder.setPayType("1");//微信Native领取类型 lcGoodsOrderService.wxInsert(lcGoodsOrder); /*解决返回前端须要数据*/ data.put("outTradeNo", outTradeNo); data.put("payUserName", lcGoodsOrder.getPayUserName()); data.put("payUserPhone", lcGoodsOrder.getPayUserPhone()); data.put("totalAmount", lcGoodsOrder.getTotalAmount()); return data; } logger.info("【微信Native领取-领取下单】下单失败,下单用户:{}, 下单手机号:{}", lcGoodsOrder.getPayUserName(), lcGoodsOrder.getPayUserPhone()); logger.info("【微信Native领取-领取下单】************完结解决*************"); return null; }6、Controller领取回调接口/** * native 异步回调解决办法 * @param request * @return */ @PostMapping("/nativeNotifyProcess") @NoVerify public synchronized String nativeNotifyProcess( HttpServletRequest request, HttpServletResponse response) throws Exception { logger.info("【微信Native领取-领取回调】************开始解决*************"); Map<String, Object> map = new ObjectMapper().readValue(request.getInputStream(), Map.class); logger.info("【微信Native领取-领取回调】返回后果:{}",map); Map<String, Object> dataMap = WeChatPayUtils.paramDecodeForAPIV3(map); //判断是否⽀付胜利 if("SUCCESS".equals(dataMap.get("trade_state"))){ logger.info("【微信Native领取-领取回调】判断领取胜利, 商户订单号:{}", dataMap.get("out_trade_no")); //领取胜利,业务解决 LcGoodsOrder lcGoodsOrder = new LcGoodsOrder(); //商户订单号 lcGoodsOrder.setOutTradeNo(String.valueOf(dataMap.get("out_trade_no"))); //付款用户openId lcGoodsOrder.setWxOpenId(String.valueOf(JSON.parseObject(String.valueOf(dataMap.get("payer")), HashMap.class).get("openid"))); //领取胜利 lcGoodsOrder.setOrderStatus("2"); //微信系统生成的订单号 lcGoodsOrder.setWxSysOrderNo(String.valueOf(dataMap.get("transaction_id"))); //批改订单信息 lcGoodsOrderService.updateById(lcGoodsOrder); //给微信发送我已接管告诉的响应 //创立给微信响应的对象 Map<String, String> returnMap = new HashMap<>(); returnMap.put("code", "SUCCESS"); returnMap.put("message", "胜利"); //将返回微信的对象转换为xml String returnXml = WeChatPayUtils.mapToXml(returnMap); logger.info("【微信Native领取-领取回调】微信Native领取胜利,并且给微信返回响应数据!, 商户订单号:{}", dataMap.get("out_trade_no")); return returnXml; } //领取失败 //创立给微信响应的对象 Map<String, String> returnMap = new HashMap<>(); returnMap.put("code", "FALL"); returnMap.put("message", ""); //将返回微信的对象转换为xml String returnXml = WeChatPayUtils.mapToXml(returnMap); logger.info("【微信Native领取-领取回调】判断领取失败, 商户订单号:{}", dataMap.get("out_trade_no")); logger.info("【微信Native领取-领取回调】************完结解决*************"); return returnXml; }7、Controller查问订单接口/** * native 领取查单,通过商户订单号查问订单状态 * @param lcGoodsOrder * @return */ @RequestMapping("/nativeQueryOrder") @NoVerify public synchronized Map<String, String> nativeQueryOrder( @RequestBody LcGoodsOrder lcGoodsOrder){ logger.info("【微信Native领取-商户订单号查单】************开始解决*************,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); Map<String, String> data = WeChatPayUtils.native_query_order(lcGoodsOrder.getOutTradeNo()); //判断查单是否胜利 if("200".equals(data.get("code"))){ logger.info("【微信Native领取-商户订单号查单】查单胜利,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); //判断查问订单是否领取胜利 if("SUCCESS".equals(data.get("tradeState"))){ logger.info("【微信Native领取-商户订单号查单-领取胜利】查单后果领取胜利,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); ResultMap resultMap = lcGoodsOrderService.selectByType("outTradeNo", lcGoodsOrder.getOutTradeNo()); //判断数据库订单查问是否胜利 if("200".equals(resultMap.getCode())){ logger.info("【微信Native领取-商户订单号查单-数据库数据验证】数据库订单数据查问胜利,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); LcGoodsOrder lcGoodsOrder1 = (LcGoodsOrder) resultMap.getData(); //如果数据库数据尚未批改订单为实现状态,则进行批改 if(lcGoodsOrder1.getOrderStatus().equals("1")){ logger.info("【微信Native领取-商户订单号查单-数据库数据验证】数据库订单数据尚未确认订单实现,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); //付款用户openId lcGoodsOrder.setWxOpenId(data.get("openId")); //领取胜利 lcGoodsOrder.setOrderStatus("2"); //微信系统生成的订单号 lcGoodsOrder.setWxSysOrderNo(data.get("transactionId")); lcGoodsOrderService.updateById(lcGoodsOrder); logger.info("【微信Native领取-商户订单号查单-数据库数据验证】数据库订单数据确认订单实现,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); } } }// data.remove("openId");// data.remove("transactionId"); logger.info("【微信Native领取-商户订单号查单】************完结解决*************,商户订单号:{}", lcGoodsOrder.getOutTradeNo()); return data; } return null; }8、PC网站页面调用下单接口/** * 调用微信领取接口 * @param {Object} data * @autors gxw */function wechatPay(data){ var url = "http://127.0.0.1:8686/lcWeChataPay/nativePaymentOrder"; $.ajax({ url: url, type: 'post', data: JSON.stringify({ payUserName: data.payUserName, payUserPhone: data.payUserPhone, payMessage: data.payMessage, goodsId: data.goodsId, totalAmount: data.totalAmount, subject: data.subject, }), contentType: "application/json", dataType: 'json', success: function(res){ if(res != null){ if(res.code == "200"){ window.location.href = "wx_pay.html?otn="+res.outTradeNo +"&pun="+encodeURI(res.payUserName) +"&pup="+res.payUserPhone +"&codePath="+res.data +"&m="+res.totalAmount }else{ alert("网络异样"); alert(res.msg); } }else{ alert("网络异样,确认网络无问题,请刷新页面!"); } }, fail: function(res){ console.log("失败") console.log(res); } })}9、领取实现,点击已领取,调用查单接口,判断是否领取胜利,胜利,则跳转到胜利页面 /** * 查问订单,测验订单状态 * @autors gxw */ function checkPayStatus(){ var url = "http://127.0.0.1:8686/lcWeChataPay/nativeQueryOrder"; $.ajax({ url: url, type: 'post', data: JSON.stringify({ outTradeNo: otn }), contentType: "application/json", dataType: 'json', success: function(res){ if(res != null){ if(res.code == "200"){ if(res.tradeState == "SUCCESS"){ window.location.href = "pay_success.html?otn="+otn +"&pun="+encodeURI(pun) +"&pup="+pup +"&m="+m }else{ alert(res.msg); return false; } }else{ alert("网络异样"); alert(res.msg); } }else{ alert("网络异样,确认网络无问题,请刷新页面!"); } }, fail: function(res){ console.log("失败") console.log(res); } }) }10、这样PC网站微信NATIVE网页领取,即可实现!=========================但这还没有完结==================== ...

June 24, 2022 · 8 min · jiezi

关于springboot:springboot-https配置-ssl

筹备, 曾经申请好域名的jks文件 <resource> <directory>src/main/resources</directory> <filtering>true</filtering> <excludes> <exclude>cc.vaneying.cn.jks</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <filtering>false</filtering> <includes> <include>cc.vaneying.cn.jks</include> </includes> </resource> <nonFilteredFileExtension>p12</nonFilteredFileExtension> <nonFilteredFileExtension>jks</nonFilteredFileExtension>yml配置 ssl: key-store: classpath:cc.vaneying.cn.jks key-store-password: Pongpie#123 key-store-type: JKS enabled: true本地用https调用接口即可

June 23, 2022 · 1 min · jiezi

关于springboot:jeecg-多数据源-DS注解

1th, yml配置 master: url: username: password: driver-class-name: com.mysql.jdbc.Driver multi-datasource1: url: username: password: driver-class-name: com.mysql.jdbc.DriverService里调用master分支, 不必做操作@DS("multi-datasource1")DS注解, 间接连贯到multi-datasource12th, 代码示例, @DS("multi-datasource1") public List<TCompanyCard> getTCompanyCard() { return tCompanyCardMapper.selectCompanyCardInfo(); }com.baomidou.dynamic.datasource.annotation.DS

June 20, 2022 · 1 min · jiezi

关于springboot:SpringBoot自动装配

ef wefe

June 19, 2022 · 1 min · jiezi

关于springboot:springboot项目运行过程中动态注入bean

[toc] 1.指标绝大部分的场景中,咱们的bean(通常是借助几个注解如:@Service、@Component、@Controller等)是在我的项目启动过程中,spring通过其依赖注入的相干伎俩帮咱们主动注入的。有时候咱们的bean须要在特定的业务场景中动静拆卸,在我的项目运行之前,无奈给出其相干的代码,咱们心愿在须要的时候,通过反射等伎俩,构建出一个对象,而后动静注入到spring容器中,交由其托管,后续就能够从容器中获取、应用。原文地址 2.外围api一个spring利用能够通过BeanDefinitionRegistry类中的registerBeanDefinition办法动静注入bean void registerBeanDefinition(String beanName, BeanDefinition beanDefinition2.1 BeanDefinitionBeanDefinition describes a bean instance. It has setter methods that can be used to programmatically set the Spring specific characteristics to a bean, for example, BeanDefinition #setScope(String scope) can be used to set a scope other than default singleton. 2.2 GenericBeanDefinitionThis is the commonly used BeanDefinition implementation. It allows specifying the bean class, bean characteristics plus constructor argument values and property values. ...

June 17, 2022 · 5 min · jiezi

关于springboot:SpringBoot之SpringBoot中使用HATEOAS

简介HATEOAS是实现REST标准的一种准则,通过遵循HATEOAS标准,能够解决咱们理论代码实现的各种个问题。作为java最风行的框架Spring当然也会不缺席HATEOAS的集成。 本文将会通过一个具体的例子来解说如何在SpringBoot中应用HATEOAS。 咱们的指标HATEOAS规定中,返回的数据会带有链接。咱们以相熟的Book为例,来展现这次的HATEOAS,首先创立一个Book entity: @Data@Entitypublic class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title;}咱们心愿可能通过上面的链接来获取到Book的具体数据: GET /book/1返回的数据如下: { "content": { "id": 1, "title": "The Hobbit" }, "_links": { "self": { "href": "http://localhost:8080/book/1" } }}能够看到在返回的数据中除了content蕴含了book的信息之外,还有一个_links属性,示意和该Book相干的资源链接。 构建Entity和Repository在做任何数据之前,咱们都须要构建相应的数据,也就是entity和对应的数据操作,为了简便起见,咱们应用H2的内存数据库。 咱们须要在application.properties中配置如下: spring.jpa.hibernate.ddl-auto=validatespring.datasource.url=jdbc:h2:mem:testdbspring.datasource.driverClassName=org.h2.Driverspring.datasource.username=saspring.datasource.password=passwordspring.jpa.database-platform=org.hibernate.dialect.H2Dialect而后配置对应的repository : public interface BookRepository extends CrudRepository<Book, Long> { long deleteByTitle(String title); @Modifying @Query("delete from Book b where b.title=:title") void deleteBooks(@Param("title") String title);}同时,须要在resources中搁置创立table的schema.sql和插入数据的data.sql。这样在程序启动的时候就能够主动创立相应的数据。 构建HATEOAS相干的RepresentationModel如果要让本人来实现,也能够实现增加链接的操作,然而这样就太简单了,还好咱们有Spring。要在Spring中应用HATEOAS,须要进行如下配置: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-hateoas</artifactId> </dependency>如果咱们想要对Book进行HATEOAS的构建,那么能够构建一个类,继承RepresentationModel即可: public class BookModel extends RepresentationModel<BookModel> { private final Book content; @JsonCreator public BookModel(@JsonProperty("content") Book content) { this.content = content; } public Book getContent() { return content; }}下面的例子中,咱们用RepresentationModel封装了一个Book对象,并将其设置为json的content属性。 ...

June 15, 2022 · 1 min · jiezi

关于springboot:教程系列二Spring-Cloud-Tencent-使用配置中心管理配置

导读:当利用的配置多了之后,往往须要配置核心来治理配置的生命周期,例如批改、公布、版本治理、回滚、多集群治理等。另外也常常遇到须要动静下发配置的场景,例如开关、业务参数等等。本文次要介绍 Spring Cloud Tencent 如何集成北极星配置核心。Github 地址: Spring Cloud Tencent 模块简介配置核心模块是 Spring Cloud Tencent 最外围的模块之一,实现了 Spring Cloud PropertySourceLoader SPI 接口(PolarisConfigFileLocator.java)。在利用启动 Bootstrap 阶段,Spring Cloud 会调用 PolarisConfigFileLocator 从 Polaris 服务端获取配置文件并加载到 Spring 上下文里。通过 Spring Boot 规范的@Value,@ConfigurationProperties 注解即可获取配置内容。动静配置刷新能力,则通过 Spring Cloud 规范的 @RefreshScope 机制实现。 留神: 因为 Spring Cloud PropertySourceLoader SPI 是在 Bootstrap 阶段调用,所以 Polaris Config 相干的配置内容(例如Polaris 服务地址)须要放在 bootstrap.yml 文件里,而不能放在 application.yml 文件里,否则会初始化失败。疾速入门本章节将介绍如何在 Spring Cloud 我的项目中应用 Spring Cloud Tencent Config 的性能。残缺 Example 代码请参考:polaris-config-example 第一步:引入 Polaris 服务端形式一:搭建本地北极星服务搭建北极星服务请参考 Polaris Getting Started ...

June 15, 2022 · 2 min · jiezi

关于springboot:Springboot更改Thymeleaf中js块的对象序列化方式

在默认状况下Thymeleaf对于js块中的对象解析是应用jackson进行序列化,然而特定状况下咱们须要更换成其余类型的序列话工具。例如:因为springboot默认的序列化应用的jackson,而咱们的脱敏规定也是建设在jackson序列化时对属性进行脱敏。 在thymeleaf模板中注入html块变量时能够间接通过对象设值,不存在序列化的动作,然而对于js块中的对象须要javaScriptSerializer进行序列化因而会存在列表中是脱敏的数据,进行编辑页面如果属性赋值是通过thymeleaf的js块,则会触发jackson的脱敏规定。咱们对象中曾经存在很多jackson属性的注解例如日期等,所以为了防止大规模改变,我抉择更改thymeleafjs块的序列化形式。通过浏览Thymeleaf源码咱们发现javaScriptSerializer默认应用的为Jackson的ObjectMapper进行序列化,然而察看调用方咱们发现如果曾经设置javaScriptSerializer。则不会应用jackson,除jackson之外还存在一个默认的序列化器,因为其属于外部类,无奈内部调用初始化,因此我抉择通过实现IStandardJavaScriptSerializer来定义本人的js对象序列化器,其中应用fastjson,至此咱们能够躲避在js中应用对象时,属性被脱敏的状况产生。咱们首先须要实现能够作为JavaScript序列化的通用接口类来自定义序列化器 package com.common.nmg.utils;import com.alibaba.fastjson.JSONObject;import org.thymeleaf.standard.serializer.IStandardJavaScriptSerializer;import java.io.Writer;public class FastJsonJavaScriptSerializer implements IStandardJavaScriptSerializer { @Override public void serializeValue(Object object, Writer writer) { JSONObject.writeJSONString(writer,object); }}再将自定义的序列化器注入到spring的thymeleaf模板解析器中 package com.nmg.framework.config;import com.nmg.common.utils.FastJsonJavaScriptSerializer;import com.nmg.common.utils.spring.SpringUtils;import org.springframework.context.annotation.Configuration;import org.springframework.util.CollectionUtils;import org.thymeleaf.spring5.SpringTemplateEngine;import org.thymeleaf.standard.StandardDialect;import javax.annotation.PostConstruct;/** * * @author zengzp * @date 2022年6月14日 17点38分 * @desc */@Configurationpublic class ThymeleafConfig { @PostConstruct public void init() { SpringTemplateEngine springTemplateEngine = SpringUtils.getBean(SpringTemplateEngine.class); StandardDialect standardDialect = CollectionUtils.findValueOfType(springTemplateEngine.getDialects(), StandardDialect.class); standardDialect.setJavaScriptSerializer(new FastJsonJavaScriptSerializer()); }}

June 14, 2022 · 1 min · jiezi

关于springboot:SpringBoot网页支付宝支付对接流程

场景:针对PC网站领取场景 一、支付宝开放平台设置1、登录支付宝开放平台2、进入控制台,在【我的利用】抉择【网页&挪动利用】,而后点击【创立利用】 3、填写好响应的利用信息,点击【确认创立】 4、创立好之后,在控制台进入利用,增加"电脑网站领取能力"阐明:增加这个能力,会须要进行填写签约信息,按着步骤走就行,不过填写的网站必须和企业支付宝的营业执照统一 5、点击左侧【利用信息】,进行设置开发信息,只设置了接口加签 5.1、设置接口加签形式,有公钥证书和公钥两个抉择,个别抉择公钥5.2、而后须要生成密钥,能力生成支付宝公钥,生成密钥也有三种形式:第一种:下载密钥生成工具1、下载密钥生成工具:https://opendocs.alipay.com/o...windows版本电脑下载windows版本工具。mac版本电脑下载mac_osx版本工具。 2、运行装置密钥生成工具点击下载的“AlipayDevelopmentAssistant-1.0.1.exe”(即支付宝开放平台凋谢助手)进行装置并运行。阐明:本工具只会记录上传点击事件操作行为,不会记录上传用户的任何用户信息以及公私钥等敏感信息。 3、生成密钥抉择密钥格局和密钥长度,点击“生成密钥”进行密钥生成 。(1)密钥长度RSA2:密钥长度为2048位。注:开放平台从2018年1月5日开始创立的利用都没有RSA密钥的设置入口,只能上传RSA2格局密钥 RSA:密钥长度为1024位。国密:目前暂不反对国密的加签形式,即便获取后也无奈设置加签。 (2)密钥格局PKCS8:JAVA开发语言实用。PKCS1:非JAVA开发语言实用。4、配置密钥开放平台(open.alipay.com)抉择要上传的利用,点击利用进入具体页,抉择利用信息内的“接口加签形式”进行设置。 如利用未上线,须要抉择概览外面的接口加签形式进行设置。(1)利用私钥:开放平台没有上传设置的地位,须要本人进行保留并设置到代码中,且因为其波及资金平安不能将其提供给别人,若不小心失落或泄露,请及时进行更新批改。(2)利用公钥:须要将其传入开放平台利用中(每次更换密钥时都要将其从新上传开放平台),如图:5、接口中以及支付宝开放平台配置密钥生成密钥后切记成对妥善保存,防止测试时因为公私钥不匹配导致签名验签等一系列不必要的谬误产生。 (1)正式环境中配置密钥开发技术人员在接口中配置正式环境配置密钥,需抉择要上传的利用,点击利用进入具体页,抉择利用信息内的“接口加签形式”进行设置。 (2)沙箱环境中配置密钥沙箱环境配置密钥,需抉择在沙箱利用中,抉择利用信息内的“RSA2(SHA256)密钥(举荐)”进行设置。 沙箱环境利用配置点击沙箱利用理解。 第二种:web在线生成密钥1、生成密钥1.1、关上Web 在线加密(无下载,新上线)->抉择“生成密钥”->抉择“保留密钥”。1.2、抉择密钥格局和密钥长度,点击“生成密钥”进行密钥生成 。(1)密钥长度RSA2:密钥长度为2048位。注:开放平台从2018年1月5日开始创立的利用都没有RSA密钥的设置入口,只能上传RSA2格局密钥RSA:密钥长度为1024位。(2)密钥格局PKCS8:JAVA开发语言实用。PKCS1:非JAVA开发语言实用。2、配置密钥配置密钥流程与“密钥生成工具生成密钥”的配置密钥流程雷同。将利用公钥上传到开放平台利用的接口加签中,利用私钥请放在接口代码私钥地位。 第三种:应用OpenSSL生成除了应用支付宝提供的一键生成工具外,也能够应用OpenSSL工具命令生成密钥。 应用OpenSSL工具命令生成,命令语句如下:1、生成RSA2私钥(2048位)命令: genrsa -out app_private_key.pem 2048 ; 2、把私钥转换成PKCS8格局并输入新文件命令: pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem; 3、生成公钥命令: rsa -in app_private_key.pem -pubout -out app_public_key.pem;之后把生成的公钥上传给支付宝,并获取支付宝公钥(ALIPAY_PUBLIC_KEY)。 具体见应用OpenSSL工具生成密钥在线文档。 注:生成密钥后切记成对妥善保存,若不小心失落或泄露,请及时进行生成生成并从新上传到支付宝开放平台,避免出现损失。 6、配置好开发信息,而后再编辑利用信息,公布上线,即可开始开发正式环境 二、Springboot代码编写1、首先创立支付宝领取工具类/** * 支付宝领取工具类 */public class ALiPayUtils { /*======沙箱环境=======*/ //沙箱网关// private static final String GATEWAY_URL = "https://openapi.alipaydev.com/gateway.do"; //沙箱APPID// private static final String APP_ID = "换成本人沙箱APPID"; //本人利用私钥// private static final String PRIVATE_KEY = "生成的利用私钥"; //支付宝公钥(非应用公钥)// private static final String PUBLIC_KEY = "生成的支付宝公钥"; //沙箱领取实现跳转页面 private static final String RETURN_URL = PropertiesValues.getPropertiesValue("pay.service.return_url", "application.properties"); //沙箱领取实现回调接口 private static final String NOTIFY_URL = PropertiesValues.getPropertiesValue("pay.service.notify_url", "application.properties"); /*========根底数据===========*/ //网关 private static final String GATEWAY_URL = "https://openapi.alipay.com/gateway.do"; //APPID public static final String APP_ID = "换成本人正式APPID"; //本人利用私钥 private static final String PRIVATE_KEY = "生成的利用私钥"; //支付宝公钥(非应用公钥) private static final String PUBLIC_KEY = "生成的支付宝公钥"; //返回数据格式 private static final String FORMAT = "json"; //编码类型 private static final String CHART_TYPE = "utf-8"; //签名类型 private static final String SIGN_TYPE = "RSA2"; /*领取销售产品码,目前支付宝只反对FAST_INSTANT_TRADE_PAY*/ public static final String PRODUCT_CODE = "FAST_INSTANT_TRADE_PAY"; private static AlipayClient alipayClient = null; /*=====商户对应数据=====*/ //商户PID public static final String PID = "2088441150504125"; /*========相应接口=========*/ //领取实现跳转页面// private static final String RETURN_URL = ""; //领取实现回调接口// private static final String NOTIFY_URL = ""; public ALiPayUtils(){ if(alipayClient == null){ alipayClient = new DefaultAlipayClient( GATEWAY_URL, APP_ID, PRIVATE_KEY, FORMAT, CHART_TYPE, PUBLIC_KEY, SIGN_TYPE); } }; /*================PC网页领取====================*/ /** * 对立下单并调用领取页面接口 * @param outTradeNo * @param totalAmount * @param subject * @return */ public Map<String, Object> placeOrderAndPayForPCWeb(String outTradeNo, float totalAmount, String subject){ AlipayTradePagePayRequest request = new AlipayTradePagePayRequest(); request.setNotifyUrl(RETURN_URL); request.setReturnUrl(NOTIFY_URL); JSONObject bizContent = new JSONObject(); bizContent.put("out_trade_no", outTradeNo); bizContent.put("total_amount", totalAmount); bizContent.put("subject", subject); bizContent.put("product_code", PRODUCT_CODE); //bizContent.put("time_expire", "2022-08-01 22:00:00"); //// 商品明细信息,按需传入 //JSONArray goodsDetail = new JSONArray(); //JSONObject goods1 = new JSONObject(); //goods1.put("goods_id", "goodsNo1"); //goods1.put("goods_name", "子商品1"); //goods1.put("quantity", 1); //goods1.put("price", 0.01); //goodsDetail.add(goods1); //bizContent.put("goods_detail", goodsDetail); //// 扩大信息,按需传入 //JSONObject extendParams = new JSONObject(); //extendParams.put("sys_service_provider_id", "2088511833207846"); //bizContent.put("extend_params", extendParams); request.setBizContent(bizContent.toString()); AlipayTradePagePayResponse response = null; try { response = alipayClient.pageExecute(request); } catch (AlipayApiException e) { e.printStackTrace(); } Map<String, Object> resultMap = new HashMap<>(); resultMap.put("isSuccess", response.isSuccess()); if(response.isSuccess()){ System.out.println("调用胜利"); System.out.println(JSON.toJSONString(response)); resultMap.put("body", response.getBody()); } else { System.out.println("调用失败"); System.out.println(response.getSubMsg()); resultMap.put("subMsg", response.getSubMsg()); } return resultMap; } /** * 交易订单查问 * @param out_trade_no * @return */ public Map<String, Object> tradeQueryForPCWeb(String out_trade_no){ AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); JSONObject bizContent = new JSONObject();// bizContent.put("out_trade_no", out_trade_no); bizContent.put("trade_no", out_trade_no); request.setBizContent(bizContent.toString()); AlipayTradeQueryResponse response = null; try { response = alipayClient.execute(request); } catch (AlipayApiException e) { e.printStackTrace(); } Map<String, Object> resultMap = new HashMap<>(); resultMap.put("isSuccess", response.isSuccess()); if(response.isSuccess()){ System.out.println("调用胜利"); System.out.println(JSON.toJSONString(response)); resultMap.put("status", response.getTradeStatus()); } else { System.out.println("调用失败"); System.out.println(response.getSubMsg()); resultMap.put("subMsg", response.getSubMsg()); } return resultMap; } /** * 验证签名是否正确 * @param sign * @param content * @return */ public static boolean CheckSignIn(String sign, String content){ try { return AlipaySignature.rsaCheck(content, sign, PUBLIC_KEY, CHART_TYPE, SIGN_TYPE); } catch (AlipayApiException e) { e.printStackTrace(); } return false; } /** * 将异步告诉的参数转化为Map * @return */ public static Map<String, String> paramstoMap(HttpServletRequest request) throws UnsupportedEncodingException { Map<String, String> params = new HashMap<String, String>(); Map<String, String[]> requestParams = request.getParameterMap(); for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String[] values = (String[]) requestParams.get(name); String valueStr = ""; for (int i = 0; i < values.length; i++) { valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ","; } // 乱码解决,这段代码在呈现乱码时应用。// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8"); params.put(name, valueStr); } return params; }}2、编写下单接口以及回调解决接口@RestController@CrossOrigin@RequestMapping("lcAliPay")public class LcALiPayController { @Autowired LcAliPayService lcAliPayService; @Autowired LcGoodsOrderService lcGoodsOrderService; /*==========PC网页领取============*/ /*===支付宝===*/ /** * 对立下单并调用领取页面,PC * @param lcGoodsOrder * @return */ @PostMapping("/tradeOrderForPCWeb") @NoVerify public ResultMap tradeOrderForPCWeb(@RequestBody LcGoodsOrder lcGoodsOrder){ return lcAliPayService.tradeOrderForPCWeb(lcGoodsOrder); } /** * 领取实现回调接口,PC * @return */ @RequestMapping("/notifyForPCWeb") @NoVerify public String notifyForPCWeb(HttpServletRequest request){ try { Map<String, String> map = ALiPayUtils.paramstoMap(request); String tradeNo = map.get("trade_no");// Map<String, Object> payData = aLiPayUtils.tradeQueryForPCWeb(tradeNo); String sign = map.get("sign"); String content = AlipaySignature.getSignCheckContentV1(map); boolean signVerified = ALiPayUtils.CheckSignIn(sign, content); //验证业务数据是否统一 if(!lcAliPayService.checkData(map)){ System.out.println("返回业务数据验证失败,订单:" + tradeNo ); return "fail"; } //签名验证胜利 if(signVerified){ System.out.println("支付宝签名验证胜利,订单:" + tradeNo); //验证领取状态 String tradeStatus = request.getParameter("trade_status"); if(tradeStatus.equals("TRADE_SUCCESS")){ System.out.println("领取胜利,订单:"+tradeNo); LcGoodsOrder lcGoodsOrder = new LcGoodsOrder(); lcGoodsOrder.setTradeNo(map.get("trade_no")); lcGoodsOrder.setSellerId(map.get("seller_id")); lcGoodsOrder.setOrderStatus("2");//领取胜利 lcGoodsOrderService.updateById(lcGoodsOrder); return "success"; }else{ System.out.println("领取失败,订单:" + tradeNo ); return "fail"; } }else{ System.out.println("签名验证失败,订单:" + tradeNo ); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "fail"; }}3、service层代码@Servicepublic class LcAliPayServiceImpl implements LcAliPayService { @Resource LcGoodsOrderMapper lcGoodsOrderMapper; //日志对象 private static Logger logger = LoggerFactory.getLogger(LcAliPayServiceImpl.class); @Override public ResultMap tradeOrderForPCWeb(LcGoodsOrder lcGoodsOrder) { logger.info("【申请开始-在线购买-交易创立】*********对立下单开始*********"); ALiPayUtils aLiPayUtils = new ALiPayUtils(); String tradeNo = ALiPayUtils.generateOrderNum(); Map<String, Object> map = aLiPayUtils.placeOrderAndPayForPCWeb(tradeNo, Float.parseFloat(lcGoodsOrder.getTotalAmount()), lcGoodsOrder.getSubject()); ResultMap resultMap = new ResultMap(); if(Boolean.parseBoolean(String.valueOf(map.get("isSuccess")))){ logger.info("【申请开始-在线购买-交易创立】对立下单胜利,开始保留订单数据"); resultMap.setCode("200"); resultMap.setData(map.get("body")); String lcGoodsOrderId = UUID.getRandom(); lcGoodsOrder.setOrderId(lcGoodsOrderId); lcGoodsOrder.setOrderCreateTime(DateTime.getNowDateStamp()); lcGoodsOrder.setOutTradeNo(tradeNo); lcGoodsOrder.setOrderStatus("1");//订单状态 lcGoodsOrder.setProductCode(ALiPayUtils.PRODUCT_CODE); //保留订单信息 int count = lcGoodsOrderMapper.insert(lcGoodsOrder); if(count > 0){ logger.info("【胜利-在线购买-交易创立】保留订单数据胜利,增加条数:{}", count); resultMap.setCode(ResponseCodeUtis.SERVER_TRUE_200); resultMap.setMsg(ResponseCodeUtis.MSG_TRUE_InsertById); resultMap.setData(map.get("body")); }else{ logger.error("【失败-在线购买-交易创立】保留订单数据失败"); resultMap.setCode(ResponseCodeUtis.SERVER_ERROR_500); resultMap.setMsg("增加数据失败"); } logger.info("【申请胜利-在线购买-交易创立】*********对立下单完结*********"); return resultMap; }else{ resultMap.setCode("501"); resultMap.setMsg(String.valueOf(map.get("subMsg"))); logger.info("【失败:申请失败-在线购买-交易创立】*********对立下单完结*********"); } return resultMap; } @Override public boolean checkData(Map<String, String> map) { logger.info("【申请开始-交易回调-订单确认】*********校验订单确认开始*********"); LcGoodsOrder lcGoodsOrder2 = lcGoodsOrderMapper.selectByOutTradeNo(map.get("out_trade_no")); //验证订单号是否精确,并且订单状态为待领取 if(lcGoodsOrder2 != null && lcGoodsOrder2.getOrderStatus().equals("1")){ float amount1 = Float.parseFloat(map.get("total_amount")); float amount2 = Float.parseFloat(lcGoodsOrder2.getTotalAmount()); //判断金额是否相等 if(amount1 == amount2){ //验证收款商户id是否统一 if(map.get("seller_id").equals(ALiPayUtils.PID)){ //判断appid是否统一 if(map.get("app_id").equals(ALiPayUtils.APP_ID)){ logger.info("【胜利:申请开始-交易回调-订单确认】*********校验订单确认胜利*********"); return true; } } } } logger.info("【失败:申请开始-交易回调-订单确认】*********校验订单确认失败*********"); return false; }}4、网站页面领取下单申请代码/** * 调用支付宝领取接口 * @param {Object} data */function aliPay(data){ var url = "服务器接口"; $.ajax({ url: url, type: 'post', data: JSON.stringify({ payUserName: data.payUserName, payUserPhone: data.payUserPhone, payMessage: data.payMessage, goodsId: data.goodsId, totalAmount: data.totalAmount, subject: data.subject, }), contentType: "application/json", dataType: 'json', success: function(res){ if(res.code == "200"){ const div=document.createElement('divform'); div.innerHTML=res.data; document.body.appendChild(div); document.forms[0].setAttribute('target', '_blank') document.forms[0].submit(); }else{ alert("网络异样"); alert(res.msg); } }, fail: function(res){ console.log("失败") console.log(res); } })}5、下单接口回调,解决返回表单解决问题if(res.code == "200"){ const div=document.createElement('divform'); div.innerHTML=res.data; document.body.appendChild(div); document.forms[0].setAttribute('target', '_blank') document.forms[0].submit();}else{ alert("网络异样"); alert(res.msg);}6、申请胜利图 ...

June 13, 2022 · 5 min · jiezi

关于springboot:mdsspringboot一个基于SpringBoot2x的支持任意场景的多数据源框架

mds-spring-boot根本简介mds-spring-boot是什么? mds-spring-boot是一个基于SpringBoot2.x的、全场景反对的、多数据源框架,反对Spring-JDBC、Mybatis、Mybatis-Plus、Mybatis-Tiny、ShardingSphere、Mycat等,反对本地事务及残缺的基于Spring-@Transactional申明式事务(及事务流传个性)。 我的项目地址:https://github.com/penggle/md... 个性及限度全场景反对: 反对单纯的Spring-JDBC原生用法(即不应用其余ORM框架)Spring-JDBC(spring-boot-starter-jdbc)是最根本的反对SpringBoot-Mybatis(mybatis-spring-boot-starter)是可选反对,即应用时须要手动引入mybatis-spring-boot-starter依赖反对单纯的Mybatis原生用法(即不应用Mybatis-Plus、Mybatis-Tiny等懒人框架)反对MybatisPlus、MybatisTiny等懒人框架用法反对本地事务,不反对分布式事务【反对本地事务】即反对在同一个JVM中通过JDBC操作多个数据源(数据库),保障其正确的Spring事务流传个性反对传统单库多数据源场景,这也是最常见的反对客户端分库分表的多数据源场景【客户端分库分表】即诸如ShardingSphere-JDBC这样的客户端(嵌入在利用外部的)分库分表框架这种状况下还反对混用,即存在单库数据源(例如我的项目中大多数表不须要分库分表,其不属于ShardingSphere治理,而属于利用自身来治理),也存在ShardingSphereDataSource(比方我的项目中有几个表的确需借助ShardingSphere来做分库分表),这种混用的场景在理论我的项目中也十分常见。示例代码就是这种混用形式反对服务端分库分表多数据源场景【服务端分库分表】即诸如ShardingSphere-Proxy、Mycat这样的服务端(伪装成一个数据库Server)分库分表中间件这种状况下跟传统的单库多数据源场景一样了,因为作为利用的客户端看来,ShardingSphere-Proxy、Mycat这样的分库分表中间件就是一个数据库Server再回头看看,是不是反对全场景?(基本上是全场景了吧)疾速入门Talk is cheap,show me the code!第一步引入依赖<dependency> <groupId>io.github.penggle</groupId> <artifactId>mds-spring-boot-starter</artifactId> <!-- 版本阐明:2.1指的是基于mybatis-spring-boot-starter 2.1.x版本的意思 --> <version>2.1</version></dependency><!-- 当然mybatis-spring-boot-starter是须要手动引入的 --><dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version></dependency>在SpringBoot启动类上启用多数据源反对 @SpringBootApplication@EnableMultiDataSource({@NamedDatabase("product"), @NamedDatabase("order")})public class GmdsExample1Application { public static void main(String[] args) { SpringApplication.run(GmdsExample1Application.class, args); }}XxxMapper接口上标记所属数据源 @NamedDatabase("product")public interface ProductMapper { ...}@NamedDatabase("order")public interface MainOrderMapper { ...}@NamedDatabase("order")public interface OrderLineMapper { ...}application.yml配置 springboot-datasource配置(必要配置) spring: #数据源配置 datasource: #公共连接池配置 hikari: #最小闲暇连贯数量 minimum-idle: 5 #闲暇连贯存活最大工夫,默认600000(10分钟) idle-timeout: 180000 #连接池最大连接数,默认是10 maximum-pool-size: 10 #池中连贯的默认主动提交行为,默认值true auto-commit: true #池中连贯的最长生命周期,0示意有限生命周期,默认1800000(30分钟) max-lifetime: 1800000 #期待来自池的连贯的最大毫秒数,默认30000(30秒) connection-timeout: 30000 #连贯测试语句 connection-test-query: SELECT 1 #商品库配置(逻辑名称) product: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/ec_product?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false&rewriteBatchedStatements=true&useCursorFetch=true #订单库配置(逻辑名称) order: username: root password: 123456 url: jdbc:mysql://127.0.0.1:3306/ec_order?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8&useSSL=false&rewriteBatchedStatements=true&useCursorFetch=truemybatis-spring-boot-starter模块配置(可选配置) ...

June 12, 2022 · 2 min · jiezi

关于springboot:activiti整合springboot问题总结

因我的项目需要,将cloud我的项目的工作流模块迁徙到springboot我的项目中。将中途遇到的一些问题整合到此处。 已解决局部activiti依赖spring与我的项目spring不兼容。解决<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>${spring-boot-starter.version}</version> <scope>import</scope> <type>pom</type> </dependency> </dependencies></dependencyManagement>MyBatis依赖抵触启动报错: ***************************APPLICATION FAILED TO START***************************Description:An attempt was made to call a method that does not exist. The attempt was made from the following location: org.mybatis.spring.SqlSessionFactoryBean.buildSqlSessionFactory(SqlSessionFactoryBean.java:564)The following method did not exist: org.apache.ibatis.session.Configuration.setDefaultEnumTypeHandler(Ljava/lang/Class;)VThe method's class, org.apache.ibatis.session.Configuration, is available from the following locations: jar:file:/D:/Program%20Files/maven/.m2/repository/org/mybatis/mybatis/3.4.2/mybatis-3.4.2.jar!/org/apache/ibatis/session/Configuration.classIt was loaded from the following location: file:/D:/Program%20Files/maven/.m2/repository/org/mybatis/mybatis/3.4.2/mybatis-3.4.2.jarAction:Correct the classpath of your application so that it contains a single, compatible version of org.apache.ibatis.session.ConfigurationDisconnected from the target VM, address: '127.0.0.1:64349', transport: 'socket'Process finished with exit code 1解决引入activiti时排除MyBatis ...

June 10, 2022 · 2 min · jiezi

关于springboot:Mendmix代码解析百搭的配置文件读取工具ResourceUtils

很久很久以前当微服务还没呈现、配置核心还没呈现、yaml配置文件也还没风行的时候,咱们都习惯在我的项目外面写一个相似ResourceUtils或者PropertiesUtil的工具,无论在静态方法还是jsp代码都屡试不爽。现在Springcloud各种参数化配置、各种profile,Apollo、Nacos各种配置核心以及Properties、Yaml各种配置格局你的配置文件读取工具还好么?接下来咱们解说Mendmix我的项目的ResourceUtils工具类开启咱们Mendmix代码解析系列课程的篇章。 介绍ResourceUtils贯通了Mendmix我的项目各个生命周期,在整个我的项目中被大量应用。目前反对Properties、Yaml配置文件解析、兼容Springcloud的profile配置形式以及无缝兼容Apollo、Nacos等各种各样的配置核心。同时还提供了诸多疾速配置文件读取的办法,如:getListValue,getMapValue、getBeanValue等。 ## 代码剖析 ### 加载过程 通过动态代码块加载本地配置文件static { loadLocalConfigs();}loadLocalConfigs这个办法首先尝试获取spring.profiles.active的值,为了兼容通过--spring.profiles.active=prd的形式指定spring.profiles.active的值,Mendmix提供了一个利用启动类基类BaseApplicationStarter,在这个基类外面会解决各种运行参数并把设置为零碎变量。 为了兼容本地运行或打包运行提供了loadPropertiesFromFile和loadPropertiesFromJarFile两个读取配置的办法,通过这两个办法会把本地的所有 .properties及.yaml文件找进去(你也拦不住两种格局的配置文件混用),并建设<文件后缀,[文件名称列表]>的映射关系如下: { ".properties" : [ "/.../application-local.properties", "/.../application.properties" ]}接下来调用parseSameExtensionFiles办法顺次循环解析每个后缀名的配置文件,这个办法做了两件事件:加载所有不带profile的配置文件和找出profile的配置文件。为了确保profile的配置文件能笼罩默认配置,找出的profile的配置文件放在做好放入解析好的所有配置文件Properties外面。这样整个解析过程就实现了。 解决${}援用配置间接贴代码吧,有点长,相似办法实际上Spring提供的有,仅仅是思考这只是一个工具类,进来少依赖,所以就本人写了。 public static String replaceRefValue(Properties properties,String value ) { if(!value.contains(PLACEHOLDER_PREFIX)){ return value; } String[] segments = value.split("\\$\\{"); String seg; StringBuilder finalValue = new StringBuilder(); for (int i = 0; i < segments.length; i++) { seg = StringUtils.trimToNull(segments[i]); if(StringUtils.isBlank(seg))continue; if(seg.contains(PLACEHOLDER_SUFFIX)){ String refKey = seg.substring(0, seg.indexOf(PLACEHOLDER_SUFFIX)).trim(); //其余非${}的占位符如:{{host}} String withBraceString = null; if(seg.contains("{")){ withBraceString = seg.substring(seg.indexOf(PLACEHOLDER_SUFFIX)+1); } //如果蕴含默认值,如:${host:127.0.0.1} String defaultValue = null; int defaultValSpliterIndex = refKey.indexOf(":"); if(defaultValSpliterIndex > 0){ defaultValue = refKey.substring(defaultValSpliterIndex + 1); refKey = refKey.substring(0,defaultValSpliterIndex); } String refValue = System.getProperty(refKey); if(StringUtils.isBlank(refValue))refValue = System.getenv(refKey); if(StringUtils.isBlank(refValue))refValue = properties.getProperty(refKey); if(StringUtils.isBlank(refValue)){ refValue = defaultValue; } if(StringUtils.isBlank(refValue)){ finalValue.append(PLACEHOLDER_PREFIX + refKey + PLACEHOLDER_SUFFIX); }else{ finalValue.append(refValue); } if(withBraceString != null){ finalValue.append(withBraceString); }else{ String[] segments2 = seg.split("\\}"); if(segments2.length == 2){ finalValue.append(segments2[1]); } } }else{ finalValue.append(seg); } }整合配置核心思考到各种各样的配置核心,所以咱们不能与具体配置核心产品绑定。所以Mendmix从Spring加载的生命周期下手。在Environment对象加载实现后对所有配置进行一次合并,代码如下: ...

June 8, 2022 · 2 min · jiezi

关于springboot:进入-SpringBoot27有一个重要的类过期了

明天来聊一个简略的话题~是一个小伙伴在星球上的发问。 进入到 SpringBoot2.7 时代,有小伙伴发现有一个罕用的类突然过期了: 在 Spring Security 时代,这个类可太重要了。过期的类当然能够持续应用,然而你要是决定顺当,只须要略微看一下正文,基本上就明确该怎么玩了。 咱们来看下 WebSecurityConfigurerAdapter 的正文: 从这段正文中咱们大略就明确了咋回事了。 以前咱们自定义类继承自 WebSecurityConfigurerAdapter 来配置咱们的 Spring Security,咱们次要是配置两个货色: configure(HttpSecurity)configure(WebSecurity)前者次要是配置 Spring Security 中的过滤器链,后者则次要是配置一些门路放行规定。 当初在 WebSecurityConfigurerAdapter 的正文中,人家曾经把意思说的很明确了: 当前如果想要配置过滤器链,能够通过自定义 SecurityFilterChain Bean 来实现。当前如果想要配置 WebSecurity,能够通过 WebSecurityCustomizer Bean 来实现。那么接下来咱们就通过一个简略的例子来看下。 首先咱们新建一个 Spring Boot 工程,引入 Web 和 Spring Security 依赖,留神 Spring Boot 抉择最新的 2.7。 接下来咱们提供一个简略的测试接口,如下: @RestControllerpublic class HelloController { @GetMapping("/hello") public String hello() { return "hello 江南一点雨!"; }}小伙伴们晓得,在 Spring Security 中,默认状况下,只有增加了依赖,咱们我的项目的所有接口就曾经被通通爱护起来了,当初启动我的项目,拜访 /hello 接口,就须要登录之后才能够拜访,登录的用户名是 user,明码则是随机生成的,在我的项目的启动日志中。 当初咱们的第一个需要是应用自定义的用户,而不是零碎默认提供的,这个简略,咱们只须要向 Spring 容器中注册一个 UserDetailsService 的实例即可,像上面这样: ...

June 8, 2022 · 2 min · jiezi

关于springboot:Shiro多realm实践

Shiro多realm实际 开发中忽然接到一个新的需要,须要在原始零碎中退出一个单点登录的性能,这不是很简略的一个需要吗,几行代码就能够搞定的事。拿到文档后间接开搞,发现用户方提供了一个token查问用户信息的接口,那么需要变得更清晰了,写一个链接,参数就是用户方的token,完事拿着token去申请平台接口,将获取到的用户信息间接注册到平台。上面写一点伪代码意思一下: public void sso(String token){ Result result = HttpUtil.post(url+token); if(result.isOk()){ User user = result.data(); if(!check(user)){ // 如果用户在零碎中不存在 register(user); } //将单点登录的用户登录零碎中 login(user.getLoginName(),user.getPassword()); }}以上代码根本解决了登录问题,原本这样就能够交差了,然而忽然想到,零碎还有锁屏以及定时批改明码等问题,因而单点登录须要绕过明码登录,然而零碎应用的是shiro,realm是用的明码和账号受权很显然无奈满足了。因而笔者想到了以下两种计划: 单点登录实现,主动初始化明码,保障库内明码统一革新零碎登录模块,减少独自针对sso的realm很显然,这里第二种计划更靠谱,上面开始登录革新。 一、创立单点登录须要的Token类应用shiro登录时默认会应用UsernamePasswordToken,咱们这边为了实现疏忽明码的形式,这里间接抉择继承BearerToken,进行自定义token,这里贴出局部代码,能够依据本人需要就行更改 public class SSOToken extends BearerToken { private SysUser user; public SSOToken(SysUser user,String token) { super(token); this.user = user; } public SysUser getUser() { return user; } public void setUser(SysUser user) { this.user = user; }}二、创立单点登录应用的realm家喻户晓,shiro登录及权限获取的逻辑次要是在realm中实现,默认状况下,咱们创立一个realm用于明码登录即可,这里为实现用户名明码和token两种登录形式,须要创立两个realm,此处本人贴出自定义代码,读者可按需批改 public class SSORealm extends AuthorizingRealm implements Serializable { /** * 受权 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roles = new HashSet<>(); roles.add("admin"); info.setRoles(roles); return info; } /** * 登录认证 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { SSOToken upToken = (SSOToken) token; // 获取sso登录传入的user实体 User user = upToken.getUser(); //不传入回绝登录 if(user==null){ throw new UnknownAccountException(); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, upToken.getToken(), getName()); return info; }}三、自定义身份验证器编写一个自定义的身份验证器,依据条件抉择指定的realm进行登录验证。 ...

June 8, 2022 · 2 min · jiezi

关于springboot:Spring-Boot-Autowired-Resource-属性赋值时机

还是先贴出测试类: @Servicepublic class TransactionServiceTest { @Resource private IQrcodeAdScheduleService qrcodeAdScheduleService;}Spring Boot启动之后的的调用栈信息如下:图1图2由图1,图2可知InjectionMetadata.inject()执行属性织入逻辑,上面是局部细节 protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable { if (this.isField) { Field field = (Field) this.member; ReflectionUtils.makeAccessible(field); //通过反射对指标target对象也就是咱们之前定义的TransactionServiceTest的属性赋值 field.set(target, getResourceToInject(target, requestingBeanName)); }}其中,CommonAnnotationBeanPostProcessor.ResourceElement的member属性存储的是Filed信息,对于本示例就是:图3对于@Autowired来说,就是AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.inject(): protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; if (this.cached) { value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); try { //递归调用createBean()实例化指标bean的属性bean实例 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } synchronized (this) { if (!this.cached) { if (value != null || this.required) { this.cachedFieldValue = desc; registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName) && beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { this.cachedFieldValue = new ShortcutDependencyDescriptor( desc, autowiredBeanName, field.getType()); } } } else { this.cachedFieldValue = null; } this.cached = true; } } } if (value != null) { //通过field进行赋值 ReflectionUtils.makeAccessible(field); field.set(bean, value); } }}其余的流程一毛一样啊~好了,总结的事件,交给读者敌人吧~(太困了,该劳动了) ...

June 6, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-如何处理Resource

先造一个测试用例: public class TransactionServiceTest { @Resource private IQrcodeAdScheduleService qrcodeAdScheduleService;}而后启动Spring Boot能够看到上面的调用栈信息:图1 由上图可知,在创立完bean实例后,通过applyMergedBeanDefinitionPostProcessors()批改beanDefinition构造(针对这种场景能够了解为解析@Resource对应的bean信息) protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof MergedBeanDefinitionPostProcessor) { MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp; //执行CommonAnnotationBeanPostProcessor类postProcessMergedBeanDefinition() bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName); } } }图2有图2可知,解决@Resource的PostProcessor是“CommonAnnotationBeanPostProcessor”,而后看一下CommonAnnotationBeanPostProcessor的局部细节: private InjectionMetadata buildResourceMetadata(final Class<?> clazz) { LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<>(); Class<?> targetClass = clazz; do { final LinkedList<InjectionMetadata.InjectedElement> currElements = new LinkedList<>(); ReflectionUtils.doWithLocalFields(targetClass, field -> { if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields"); } currElements.add(new WebServiceRefElement(field, field, null)); } else if (ejbRefClass != null && field.isAnnotationPresent(ejbRefClass)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@EJB annotation is not supported on static fields"); } currElements.add(new EjbRefElement(field, field, null)); } //解析@Resource.class else if (field.isAnnotationPresent(Resource.class)) { if (Modifier.isStatic(field.getModifiers())) { throw new IllegalStateException("@Resource annotation is not supported on static fields"); } if (!ignoredResourceTypes.contains(field.getType().getName())) { currElements.add(new ResourceElement(field, field, null)); } } });}下面的代码块呈现了期待已久的“Resource.class”关键字,咱们就释怀了。咱们再回顾一下,其流程是这样的:在AbstractAutowireCapableBeanFactory.populateBean()->ibp.postProcessPropertyValue()->CommonAnnotationBeanPostProcessor.postProcessPropertyValue()去实例化@Resource作用的bean; ...

June 6, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-实例化bean如何选择代理方式

图1咱们再回顾一下之前的事务源码剖析有提到,执行到AbstractAutowireCapableBeanFactory.initializeBean()->applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)->AbstractAutoProxyCreator.postProcessAfterInitialization()->AbstractAutoProxyCreator.wrapIfNecessary()->DefaultAopProxyFactory.createAopProxy()链条创立代理; public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } //如果是接口 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } //如果不是接口 return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }通过下面的形式判断cglib还是jdk动静代理;

June 6, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-如何处理Autowired二

Spring Boot 如何解决@Autowired(一)这篇小文可知AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues()最终执行对@Autowired的属性实例进行织入;而后以DefaultListableBeanFactory.resolveDependency()->DefaultListableBeanFactory.doResolveDependency()->DependencyDescriptor.resolveCandidate()->beanFactory.getBean()的链条进行创立field bean实例: public Object resolveDependency(){ //省略其余code { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; }}public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException { return beanFactory.getBean(beanName); }当初是不是恍然大悟了?整个调用链路就清晰起来了~,原本有点懒,就不总结了,总结就交给读者敌人了,欢送留言加友探讨~

June 6, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-如何处理Autowired

咱们还以TransactionServiceTest为例: @Servicepublic class TransactionServiceTest { @Autowired private IQrcodeAdScheduleService qrcodeAdScheduleService;}图1图2由图1,图2能够看出Spring Boot执行beanFactory.preInstantiateSingletons()办法的过程中,须要实例化指标 bean(TransactionServiceTest);图3 for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; //bean实例化后,进行后置解决 if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } }}图4图3,图4能够看到是这个AutowiredAnnotationBeanPostProcessor是专门来解决@Autowired的。 图5图6 图5,图6以看到通过AutowiredAnnotationBeanPostProcessor解决之后,接着实例化 被@Autowired作用的属性bean(也就是qrcodeAdScheduleService)! 那么问题来了AutowiredAnnotationBeanPostProcessor是怎么辨认@Autowired属性信息的呢?咱们接着往下看: 图4图4能够分明地看到其中的调用堆栈信息。也就是说这个applyMergedBeanDefinitionPostProcessors()是解析@Autowired并进行存储的入口 // Allow post-processors to modify the merged bean definition. synchronized (mbd.postProcessingLock) { if (!mbd.postProcessed) { try { //后置处理器批改BeanDefinition构造 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", ex); } mbd.postProcessed = true; } }对于AutowiredAnnotationBeanPostProcessor来说时找到指标bean外面@Autowired注解信息: ...

June 6, 2022 · 1 min · jiezi

关于springboot:springboot里的Async分析

1,@Async,@EnableAsync都是spring里的注解,如果间接应用@Async则默认应用SimpleAsyncTaskExecutor,它的问题:不复用创立的线程,每次有工作时都会创立新的线程,重大的话会引起引发OutOfMemoryError。能够通过设定concurrencyLimit创立线程的下限。倡议应用ThreadPoolTaskExecutor。 2,@Async,@EnableAsync原理:https://blog.csdn.net/qq_4190... 3,在springboot中应用@Async,如果未自定义线程池,也未自定义线程池配置,则应用TaskExecutionProperties类里的配置创立线程池ThreadPoolTaskExecutor。开展:spring里实现的线程池:https://blog.csdn.net/lemon_T... 4,最佳实际https://blog.csdn.net/lemon_T...自定义,返回值,异样

June 4, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-如果一个service-Transactional所在方法不是public会发生什么三

Spring Boot 如果一个service @Transactional所在办法不是public会产生什么?(二)这篇文章里咱们晓得了,no-public 的帽子办法(其实办法就是@Transaction被润饰^_^)最终执行的实例并不是代理类,那么在什么时候代理类被替换掉的呢? 图1 刚开始执行时,的确是代理类~ 图2 咱们剖析一下DynamicAdvisedInterceptor.intercept(): public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; Object target = null; TargetSource targetSource = this.advised.getTargetSource(); try { if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // Get as late as possible to minimize the time we "own" the target, in case it comes from a pool... target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; //这里判断指标办法是不是public if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { // We can skip creating a MethodInvocation: just invoke the target directly. // Note that the final invoker must be an InvokerInterceptor, so we know // it does nothing but a reflective operation on the target, and no hot // swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = methodProxy.invoke(target, argsToUse); } else { // 因为不是public,须要执行这里 retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); } retVal = processReturnType(proxy, target, method, retVal); return retVal; } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } }最终调CglibAopProxy.invokeJoinpoint();图3 ...

June 3, 2022 · 2 min · jiezi

关于springboot:Spring-Boot-如果一个service-Transactional所在方法不是public会发生什么二

由Spring Boot 如果一个service @Transactional所在办法不是public会产生什么?咱们能够晓得,当一个servie 中被@Transactional润饰的办法全部都是no-public的时候,Spring不会创立代理类,也就是咱们的事务没有方法失效~。 那要是@Transactional润饰的办法有no-public,也有public的时候呢?这种状况下,spring 也是会创立代理类的。那么问题来了,如果这个public办法 调用了一个no-public 办法呢?有两种状况: 1. 这个no-public 办法上有一个帽子(@Transactional);2. 这个no-public 办法上没有帽子;上面详细分析:图1 final void testSaveAd() { try { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$testSaveAd$0$Method, CGLIB$emptyArgs, CGLIB$testSaveAd$0$Proxy); } else { super.testSaveAd(); } } catch (Error | RuntimeException var1) { throw var1; } catch (Throwable var2) { throw new UndeclaredThrowableException(var2); } } public final void testSaveAd2() { try { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$testSaveAd2$1$Method, CGLIB$emptyArgs, CGLIB$testSaveAd2$1$Proxy); } else { super.testSaveAd2(); } } catch (Error | RuntimeException var1) { throw var1; } catch (Throwable var2) { throw new UndeclaredThrowableException(var2); } } final void save() { try { MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; if (var10000 == null) { CGLIB$BIND_CALLBACKS(this); var10000 = this.CGLIB$CALLBACK_0; } if (var10000 != null) { var10000.intercept(this, CGLIB$save$2$Method, CGLIB$emptyArgs, CGLIB$save$2$Proxy); } else { super.save(); } } catch (Error | RuntimeException var1) { throw var1; } catch (Throwable var2) { throw new UndeclaredThrowableException(var2); } }下面三段code,别离是代理类对咱们定义的三个办法进行增强解决的状况; ...

June 3, 2022 · 2 min · jiezi

关于springboot:Spring-boot-如果一个serivice-Transactional所在方法不是public会发生什么

咱们先说如果该办法是public,钻研一下其中的细节 @Servicepublic class TestServiceImpl { @Transactional(rollbackFor = Exception.class) public void trans1(){}} AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(),其中applyBeanPostProcessorsAfterInitialization()继承自AbstractAutoProxyCreator,上面是办法细节 public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; //getBeanPostProcessors()是sptring boot启动时系统配置的BeanPostProcessor;其中有一个BeanPostProcessor叫做AnnotationAwareAspectJAutoProxyCreator; for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) { //当遍历AnnotationAwareAspectJAutoProxyCreator时,执行其postProcessAfterInitialization() Object current = beanProcessor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; } return result; }以下是AbstractAutoProxyCreator.postProcessAfterInitialization()细节: public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { //这个办法的重要逻辑是,依据beanName获取告诉列表,如果列表不为空,创立代理类; return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }图2图3 ...

June 2, 2022 · 2 min · jiezi

关于springboot:SpringBoot整合atomikos实现跨库事务

背景框架之前实现了多数据源的动静切换及事务的解决,想更近一步提供一个简略的跨库事务处理性能,通过网上的搜寻调研,大抵有XA事务/SEGA事务/TCC事务等计划,因为业务次要波及政府及企业且并发量不大,所以采纳XA事务,尽管性能有所损失,然而能够保证数据的强一致性 方案设计针对注册的数据源拷贝一份用于XA事务,使得本地事务和XA全局事务互相独立可抉择的应用 Maven配置引入atomikos第三方组件 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency>注册XA数据源应用Druid连接池,须要应用DruidXADataSource数据源对象,再应用AtomikosDataSourceBean进行包装 注册数据源时针对同一个连贯注册两份,一份失常数据源,一份用于XA事务的数据源,数据源标识辨别并关联 因为spring默认注册了XA事务管理器后,所有事务操作不再走本地事务,咱们通过切换不同的数据源决定走本地事务还是XA事务 //主数据源xa模式 @Bean @Qualifier("masterXADataSource") public DataSource masterXADataSource() { DruidXADataSource datasource = new DruidXADataSource(); if(driverClassName.equals("com.mysql.cj.jdbc.Driver")){ if(!dbUrl.contains("useOldAliasMetadataBehavior")){ dbUrl += "&useOldAliasMetadataBehavior=true"; } if(!dbUrl.contains("useAffectedRows")){ dbUrl += "&useAffectedRows=true"; } } datasource.setUrl(this.dbUrl); datasource.setUsername(username); datasource.setPassword(password); datasource.setDriverClassName(driverClassName); //configuration datasource.setInitialSize(1); datasource.setMinIdle(3); datasource.setMaxActive(20); datasource.setMaxWait(60000); datasource.setTimeBetweenEvictionRunsMillis(60000); datasource.setMinEvictableIdleTimeMillis(60000); datasource.setValidationQuery("select 'x'"); datasource.setTestWhileIdle(true); datasource.setTestOnBorrow(false); datasource.setTestOnReturn(false); datasource.setPoolPreparedStatements(true); datasource.setMaxPoolPreparedStatementPerConnectionSize(20); datasource.setLogAbandoned(false); //移除泄露连贯产生是是否记录日志 try { datasource.setFilters("stat,slf4j"); } catch (SQLException e) { logger.error("druid configuration initialization filter", e); } datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//connectionProperties); AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean(); atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); atomikosDataSourceBean.setUniqueResourceName("master-xa"); atomikosDataSourceBean.setXaDataSource(datasource); atomikosDataSourceBean.setPoolSize(5); atomikosDataSourceBean.setMaxPoolSize(20); atomikosDataSourceBean.setTestQuery("select 1"); return atomikosDataSourceBean; }注册XA事务管理器应用spring内置的JtaTransactionManager事务管理器对象,设置AllowCustomIsolationLevels为true,否则指定自定义事务隔离级别会报错 ...

May 31, 2022 · 2 min · jiezi

关于springboot:Spring-Boot-Component-Configuration-同时声明一个对象会怎样呢

先贴出示例:接下来开始剖析过程,启动我的项目,会发现ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry()执行咱们申明的指标类:图1-2 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (ConfigurationClassUtils.isFullConfigurationClass(beanDef) || ConfigurationClassUtils.isLiteConfigurationClass(beanDef)) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // Sort by previously determined @Order value, if applicable configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } // Parse each @Configuration class //创立ConfigurationClassParser类,用来解析带有@Configuration的类 ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { //执行解析逻辑 parser.parse(candidates); parser.validate(); //保留所有带@Configuration的类 Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { //申明@configuration读取类 this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } //加载 this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }其中reader.loadBeanDefinitions(configClasses),最终调用loadBeanDefinitionsForConfigurationClass() ...

May 31, 2022 · 3 min · jiezi

关于springboot:SpringBootUniapp实战开发全新仿抖音短视频App

download:SpringBoot+Uniapp实战开发全新仿抖音短视频Appjava中的static关键字说明显还得靠JVM 前言Java中Static想必大家肯定使用过吧。他是用来润饰类或者成员变量或者方法的。对于Static的用法还是很简略的,因为他就是一个修饰词。然而如果不理解他润饰的作用原理的话,可能会闹出bug来变量那么咱们拜访对象中的属性天然也就存放在堆中的。然而当static润饰属性之后他就发生了变动了。 class Demo { //成员变量 public int num = 100; //动态成员变量 public static int count = 200; //动态方法 public static void method(){ System.out.println(count);} }复制代码num属性属于惯例属性,count属性属于动态变量。他们不只仅是名称上的不同。从JVM的角度看他的存放地位也不同。首先num依赖于具体的对象,所以他和对象存放在一起都是堆中。而count独立于对象。JVM中顺便有一块空间用于存放动态变量。这个空间咱们叫做方法区。方法除了润饰变量外,static还可能润饰方法。被润饰的方法咱们叫做动态方法 。动态方法的个性和动态变量一样都属于类而不是对象。动态方法外部只能拜访动态变量而无奈通过this对象进行拜访对象属性。总结下来就是动态方法外部只能拜访动态变量无法访问非动态变量。除了动态方法外,还有一个非凡的方法叫做动态代码块。这个方法不需要咱们筹备方法名,入参,出参等等。只需要筹备方法体。对于方法体外部和动态方法外部申请是一样的。 对于动态代码块和动态方法他们和一般方法还有一个重要的区别就是执行时机。动态变量与一般变量的区别就是内存分布地位,而方法是在栈中操作的,不涉及内存的存储,所以区别就是方法执行的时机。这里需要咱们提前了解点类加载机制。首先咱们一个类的加载分为五个过程。首先是加载class元信息,最初一步是进行初始化。至于后面三步咱们这里可能不理解。重点知道在类加载的最初阶段会进行初始化,而初始化的操作就是执行动态方法和动态代码块。从类加载过程中咱们也能够看的进去动态方法是不依赖于对象的调用的。因为动态方法中只能使用到动态属性。也就是说动态属性使用时还没有创建对象。这也佐证了动态变量不依赖对象的说法。总结本文次要讲解Java基础,请原谅我没有华丽的词藻渲染杰出的文章。诚然基础但经常是咱们容易忽略的学识点。只有不断的学习,才能不断的提高,对于static的进一步使用场景,目前我能想到的就是单例模式中会使用。

May 30, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-Feign调用探究二

明天咱们具体分析FeignClientFactoryBean的作用,剖析之前先贴出来@EnableFeignClients的局部细节: @Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(FeignClientsRegistrar.class)public @interface EnableFeignClients {}为什么要贴出@EnableFeignClients呢?因为这个FeignClientsRegistrar类是咱们解析Feign类的入口,上面的代码块是FeignClientsRegistrar的局部细节: private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) { String className = annotationMetadata.getClassName(); //赋值FeignClientFactoryBean类 BeanDefinitionBuilder definition = BeanDefinitionBuilder .genericBeanDefinition(FeignClientFactoryBean.class);}由下面的代码片段,能够看到 FeignClientsRegistrar 会解析@EnableFeignClients注解,而后在构建BeanDefinition过程中,会注入FeignClientFactoryBean类,这样一来在后续实例化咱们申明的Feign类型的Bean过程中,会依据咱们配置的name/value,url等信息,实例化适合的FeignClient类。 当初咱们大抵理解了FeignClientFactoryBean的作用。而后再看一下FeignClientFactoryBean的getObject(): @Override public Object getObject() throws Exception { FeignContext context = applicationContext.getBean(FeignContext.class); Feign.Builder builder = feign(context); //如果咱们没有配置url if (!StringUtils.hasText(this.url)) { String url; if (!this.name.startsWith("http")) { url = "http://" + this.name; } else { url = this.name; } url += cleanPath(); //调用负载平衡逻辑 return loadBalance(builder, context, new HardCodedTarget<>(this.type, this.name, url)); } //如果配置了url if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) { this.url = "http://" + this.url; } String url = this.url + cleanPath(); Client client = getOptional(context, Client.class); if (client != null) { if (client instanceof LoadBalancerFeignClient) { // not load balancing because we have a url, // but ribbon is on the classpath, so unwrap client = ((LoadBalancerFeignClient)client).getDelegate(); } builder.client(client); } Targeter targeter = get(context, Targeter.class); //底层会构建HttpUrlConnection对象,其实也就是一般的http申请 return targeter.target(this, builder, context, new HardCodedTarget<>( this.type, this.name, url)); }当初有没有恍然大迷瞪?!这个FeignClientFactoryBean决定了要创立哪一种的FeignClient对象! ...

May 28, 2022 · 1 min · jiezi