关于springboot:Mybatis-plus-多表联查Column-‘id‘-in-where-clause-is-ambiguous-转载

转载自 https://blog.csdn.net/qq_3383... 一、报错信息Caused by: Column 'xxxx' in where clause is ambiguous 二、报错起因表 person 和 表 class 都有字段 id 和 name ,所以要给它们减少别名来进行辨别。 PersonVOMapper.java public interface PersonVOMapper extends BaseMapper<PersonVO> { List<PersonVO> getPersonVOList(@Param(Constants.WRAPPER) Wrapper<PersonVO> queryWrapper); }PersonVOMapper.xml<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sionas.mapper.PersonVOMapper"> <select id="getPersonVOList" resultType="com.sionas.domain.PersonVO"> SELECT p.id AS personId, p.name AS personName, p.sex, p.age, c.id AS classId, c.name AS className FROM person p LEFT JOIN class c ON c.id = p.class_id; ${ew.customSqlSegment} </select></mapper>${ew.customSqlSegment} 是Mybatis Plus的动静条件结构器的最终条件SQL ...

October 6, 2021 · 1 min · jiezi

关于springboot:springboot整合activitiworkflow系统可迁移工作流

前言activiti工作流,企业erp、oa、hr、crm等审批零碎轻松落地,销假审批demo从流程绘制到审批完结实例。 一、我的项目模式springboot+vue+activiti集成了activiti在线编辑器,疾速开发平台,可插拔工作流服务。 二、我的项目介绍本我的项目领有用户治理,部门治理,代码生成,零碎监管,报表,大屏展现,业务审批等性能。性能太强大,只能粗矿的介绍,所见即所得,体验一下吧。 三、工作流1.流程模型绘制进入流程模型菜单,创立流程模型,这里波及到网关流转,须要设置流转条件,咱们这里是三十岁以上的走上面分支,三十岁以下的走下面的分支。点击分支线,设置流转条件即可。${age<=30}。保留后咱们在列表中点击公布即可。 2.流程配置公布后,就到了已公布模型列表,在启用之前,咱们须要先对进行节点设置和关联具体单据。 审批人员能够依据角色,间接指定人,部门,部门负责人,发起人部门负责人来进行配置,基本上满足所有的流转需要,并且能够设置表单变量。 设置流程表单,目前就做了一个销假的测试表单,并且能够对相应角色受权,做到自定义权限。 设置完后启动即可。 3.流程提交填写销假表单 提交单据,优先级分为一般,重要,紧急。音讯告诉能够抉择站内告诉,短信,邮件。 提交之后能够撤回单据。 查看流程流转进度状况。 也能够挂起,删除流程。 4.流程审批办理人审批列表,能够解决单据(驳回或者通过),也能够委托别人待办。 审批通过。 委托别人待代。 审批通过后进入已办列表。 年龄大于30岁,进入上面分支流转。 审批通过。 5.待办信息推送站内音讯推送。 总结下面只是展现了平台的审批流性能,还有其余很多性能没展现进去,本人也写了一些十分好用的组件,做到零碎麻利疾速开发,大大减少开发工夫和老本,目前正在对接挪动端审批。之前因为没有工夫去部署线上测试环境,思考近期部署,目前能够独自找我,近程演示,有须要源码的分割我。q:2500564056。 鸣谢: jeecgboot开源版http://jeecg.com/ 咖啡兔activiti实战https://kafeitu.me/

September 29, 2021 · 1 min · jiezi

关于springboot:springboot常量设置-IntelliJ-Idea解决Could-not-autowire

转自 https://blog.csdn.net/u012453...原博删除在此记录 1.问题形容 在Idea的spring工程里,常常会遇到Could not autowire. No beans of 'xxxx' type found的谬误提醒。但程序的编译和运行都是没有问题的,这个谬误提醒并不会产生影响。但红色的谬误提醒在有些有强迫症的程序员眼里,多多少少有些不太难受。 起因 起因可能有两个,第一个是IntellijIDEA自身工具的问题。第二个便是咱们导入@Service包的时候导入包谬误造成的 第一种起因,spring auto scan配置,在编辑状况下,无奈找不到对应的bean,于是提醒找不到对应bean的谬误。常见于mybatis的mapper,如下: <!-- mapper scanner configurer --><bean id="mapperScannerConfig" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.adu.spring_test.mybatis.dao" /><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /></bean> 解决方案 针对第一种起因,解决办法是:升高Autowired检测的级别,将Severity的级别由之前的error改成warning或其它能够疏忽的级别。 针对第二种起因,解决方案当然是导入正确的包。首先咱们来看下最容易导入的谬误包,如下所示:import com.alibaba.dubbo.config.annotation.Service; 正确的包应该是上面这个import org.springframework.stereotype.Service; 切记切记啊!!!!

September 29, 2021 · 1 min · jiezi

关于springboot:Swagger2报No-operations-defined-in-spec-关闭WebMvcConfig的注解后又正常

明天在迁徙一个老我的项目时,查看Swagger文档,发现间接报错No operations defined in spec。跟了一会儿代码后发现返回的数据是有的。api也都被swagger扫描到了。又看了会儿发现问题在于零碎里有个类实现了WebMvcConfigurer。 把@Configuration敞开了swagger就失常了。所以是该类里的货色导致了swagger异样。最初网上找了一会儿发现起因是 configureMessageConverters导致的。 修复办法: 1.新建一个类 import com.google.gson.JsonElement;import com.google.gson.JsonParser;import com.google.gson.JsonSerializationContext;import com.google.gson.JsonSerializer;import springfox.documentation.spring.web.json.Json;import java.lang.reflect.Type;public class SpringfoxJsonToGsonAdapter implements JsonSerializer<Json> { @Override public JsonElement serialize(Json json, Type type, JsonSerializationContext context) { //这里原博客用了过期的办法,把它改过来了 return JsonParser.parseString(json.value()); }}2.批改WebMvcConfigurer的实现类 @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.removeIf(httpMessageConverter -> httpMessageConverter instanceof MappingJackson2HttpMessageConverter); converters.add(gsonHttpMessageConverters()); } @Bean public GsonHttpMessageConverter gsonHttpMessageConverters() { Gson gson = new GsonBuilder() .registerTypeAdapter(Json.class, new SpringfoxJsonToGsonAdapter()) .create(); GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter(gson); return gsonConverter; }重启下问题就解决了 ...

September 28, 2021 · 1 min · jiezi

关于springboot:聊聊如何把第三方服务注册到我们项目的spring容器中

前言最近业务部门向咱们反馈一个问题,咱们部门原先提供的组件忽然用不了了。前面排查是因为咱们提供进来的组件类没有注入到spring 容器中,之前没问题是因为业务部门的根包名跟咱们组件的根包名是一样,后续他们根包名换了,导致咱们的组件类没法注入到spring中,过后的解决方案是形如下 @SpringBootApplication(scanBasePackages = {"业务根包","组件根包"})就是在业务的启动类上加上扫描组件根包。 尽管这样的形式能够解决,然而预先复盘了一下,业务方是否须要理解组件根包?是否还有更优雅一点的形式?本文就来聊聊如何把第三方服务注册到咱们我的项目的spring容器中 注入形式1、注入的组件个数比拟少1、主动拆卸机制 + @Bean的模式示例: @Configuration@Slf4j@EnableConfigurationProperties(XxlJobProperty.class)public class XxlJobAutoConfiguration { @Bean @ConditionalOnMissingBean public XxlJobSpringExecutor xxlJobExecutor(XxlJobProperty property) { log.info(">>>>>>>>>>> xxl-job config init."); XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor(); xxlJobSpringExecutor.setAdminAddresses(property.getAdminAddresses()); xxlJobSpringExecutor.setAppname(property.getExecutorAppname()); xxlJobSpringExecutor.setAddress(property.getExecutorAddress()); xxlJobSpringExecutor.setIp(property.getExecutorIp()); xxlJobSpringExecutor.setPort(property.getExecutorPort()); xxlJobSpringExecutor.setAccessToken(property.getAccessToken()); xxlJobSpringExecutor.setLogPath(property.getExecutorLogPath()); xxlJobSpringExecutor.setLogRetentionDays(property.getExecutorLogRetentionDays()); return xxlJobSpringExecutor; }在META-INF/spring.factories退出 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.github.lybgeek.autoconfiure.XxlJobAutoConfiguration 2、利用@Eanblexxx + @Import机制示例: @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(HelloSeviceImpl.class)public @interface EnableHelloSvc{}在业务我的项目启动加上@EnableHelloSvc 3、调用beanFactory.registerSingleton()示例: @Slf4jpublic class HelloSvcBeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { String beanName = StringUtils.uncapitalize(HelloService.class.getSimpleName()); log.info("register bean : beanName:{}",beanName); beanFactory.registerSingleton(beanName,new HelloServiceImpl()); }}2、注入的组件个数比拟多1、主动拆卸机制 + @ComponentScan示例: ...

September 28, 2021 · 1 min · jiezi

关于springboot:SpringBoot源码-run方法启动流程

咱们从主办法启动的入口开始,如下: public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args);}run办法次要干了两件事,一件是创立 SpringApplication 并进行初始化,初始化如下图: 另一件是run的执行,咱们重点看run的执行流程,一路点击run办法,直到: 这个就是run办法启动的主流程了,上面一步步来看: 创立秒表计时器,开始计时 StopWatch stopWatch = new StopWatch();stopWatch.start();配置Headless属性 private void configureHeadlessProperty() { System.setProperty("java.awt.headless", System.getProperty("java.awt.headless", Boolean.toString(this.headless)));}这里同一个属性,取了有设置回去,看起来截然不同,其实是因为 getProperty带两个参数的时候,会给默认值,所以再设置一次就能保障该属性都有值从META-INF/spring.factories找到所有SpringApplicationRunListener的监听器,启动监听器 SpringApplicationRunListeners listeners = this.getRunListeners(args);listeners.starting(bootstrapContext, this.mainApplicationClass);配置环境 ConfigurableEnvironment,退出到监听器对象中 ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);点击prepareEnvironment办法进去,办法里首先创立环境对象: ConfigurableEnvironment environment = this.getOrCreateEnvironment();接着配置环境: this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());而 configureEnvironment()办法,会进行以下两个操作: // 加载配置源参数和命令行属性this.configurePropertySources(environment, args);// 配置以后activede的形容文件this.configureProfiles(environment, args);回到prepareEnvironment办法: 筹备零碎环境: listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);接下来bindToSpringApplication()办法: protected void bindToSpringApplication(ConfigurableEnvironment environment) { try { Binder.get(environment).bind("spring.main", Bindable.ofInstance(this)); } catch (Exception var3) { throw new IllegalStateException("Cannot bind to SpringApplication", var3); } }将配置文件中的spring.main结尾的配置信息绑定到SpringApplication类对应的属性中。 ...

September 27, 2021 · 1 min · jiezi

关于springboot:SpringBoot-如何进行参数校验老鸟们都这么玩的

大家好,我是飘渺。 前几天写了一篇《SpringBoot如何对立后端返回格局?老鸟们都是这样玩的!》浏览成果还不错,而且被很多号主都转载过,明天咱们持续第二篇,来聊聊在SprinBoot中如何集成参数校验Validator,以及参数校验的高阶技巧(自定义校验,分组校验)。 此文是依赖于前文的代码根底,曾经在我的项目中退出了全局异样校验器。(代码仓库在文末)首先咱们来看看什么是Validator参数校验器,为什么须要参数校验? 为什么须要参数校验在日常的接口开发中,为了避免非法参数对业务造成影响,常常须要对接口的参数做校验,例如登录的时候须要校验用户名明码是否为空,创立用户的时候须要校验邮件、手机号码格局是否精确。靠代码对接口参数一个个校验的话就太繁琐了,代码可读性极差。 Validator框架就是为了解决开发人员在开发的时候少写代码,晋升开发效率;Validator专门用来进行接口参数校验,例如常见的必填校验,email格局校验,用户名必须位于6到12之间 等等... Validator校验框架遵循了JSR-303验证标准(参数校验标准), JSR是 Java Specification Requests的缩写。接下来咱们看看在SpringbBoot中如何集成参数校验框架。 SpringBoot中集成参数校验第一步,引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId></dependency>注:从 springboot-2.3开始,校验包被独立成了一个 starter组件,所以须要引入validation和web,而 springboot-2.3之前的版本只须要引入 web 依赖就能够了。第二步,定义要参数校验的实体类@Datapublic class ValidVO { private String id; @Length(min = 6,max = 12,message = "appId长度必须位于6到12之间") private String appId; @NotBlank(message = "名字为必填项") private String name; @Email(message = "请填写正确的邮箱地址") private String email; private String sex; @NotEmpty(message = "级别不能为空") private String level;}在理论开发中对于须要校验的字段都须要设置对应的业务提醒,即message属性。 常见的束缚注解如下: 注解性能@AssertFalse能够为null,如果不为null的话必须为false@AssertTrue能够为null,如果不为null的话必须为true@DecimalMax设置不能超过最大值@DecimalMin设置不能超过最小值@Digits设置必须是数字且数字整数的位数和小数的位数必须在指定范畴内@Future日期必须在以后日期的将来@Past日期必须在以后日期的过来@Max最大不得超过此最大值@Min最大不得小于此最小值@NotNull不能为null,能够是空@Null必须为null@Pattern必须满足指定的正则表达式@Size汇合、数组、map等的size()值必须在指定范畴内@Email必须是email格局@Length长度必须在指定范畴内@NotBlank字符串不能为null,字符串trim()后也不能等于“”@NotEmpty不能为null,汇合、数组、map等size()不能为0;字符串trim()后能够等于“”@Range值必须在指定范畴内@URL必须是一个URL注:此表格只是简略的对注解性能的阐明,并没有对每一个注解的属性进行阐明;可详见源码。 第三步,定义校验类进行测试@RestController@Slf4j@Validatedpublic class ValidController { @ApiOperation("RequestBody校验") @PostMapping("/valid/test1") public String test1(@Validated @RequestBody ValidVO validVO){ log.info("validEntity is {}", validVO); return "test1 valid success"; } @ApiOperation("Form校验") @PostMapping(value = "/valid/test2") public String test2(@Validated ValidVO validVO){ log.info("validEntity is {}", validVO); return "test2 valid success"; } @ApiOperation("单参数校验") @PostMapping(value = "/valid/test3") public String test3(@Email String email){ log.info("email is {}", email); return "email valid success"; }}这里咱们先定义三个办法test1,test2,test3,test1应用了 @RequestBody注解,用于承受前端发送的json数据,test2模仿表单提交,test3模仿单参数提交。留神,当应用单参数校验时须要在Controller上加上@Validated注解,否则不失效。 ...

September 24, 2021 · 4 min · jiezi

关于springboot:弃用Java-8Apache-Kafka发布30正式版

昨天,Apache Kafka 3.0 版本正式公布,这是一个波及多方面的大版本。在这个版本中,Apache Kafka 3.0 引入了各种新性能、突破性的 API 更改以及对 KRaft 的改良:Apache Kafka 的内置共识机制将取代 Apache ZooKeeper™。 尽管 KRaft 尚未被举荐用于生产(已知差距列表),但咱们对 KRaft 元数据和 API 进行了许多改良。Exactly-once 和分区重新分配反对值得强调,咱们激励开发者查看 KRaft 的新性能并在开发环境中试用它。 并且,从 Apache Kafka 3.0 开始,生产者默认启用最强的交付保障 ( acks=all, enable.idempotence=true),这意味着用户当初默认取得排序和持久性。此外,不要错过 Kafka Connect 工作重启加强、KStreams 基于工夫戳同步的改良以及 MirrorMaker2 更灵便的配置选项。 惯例变动KIP-750:弃用 Kafka 中对 Java 8 的反对在 3.0 中,Apache Kafka 我的项目的所有组件都已弃用对 Java 8 的反对。这将使用户有工夫在下一个次要版本 (4.0) 之前进行调整,届时 Java 8 反对将被勾销。 KIP-751:弃用 Kafka 中对 Scala 2.12 的反对对 Scala 2.12 的反对在 Apache Kafka 3.0 中也已弃用。与 Java 8 一样,咱们给用户工夫来适应,因为打算在下一个次要版本 (4.0) 中删除对 Scala 2.12 的反对。 ...

September 24, 2021 · 2 min · jiezi

关于springboot:聊聊springboot项目如何实现自定义actuator端点

前言用过springboot的敌人,可能会晓得springboot有四大神器:主动拆卸、starter、cli、actuator。其中actuator可帮忙你在将应用程序推送到生产环境时监控和管理应用程序。你能够抉择应用 HTTP 端点或 JMX 来治理和监控你的应用程序。 审计、衰弱和指标收集也能够主动利用于你的应用程序。 actuator默认为咱们内置了以下端点 ID形容默认启用默认公开auditevents公开以后应用程序的审计事件信息YesNobeans显示应用程序中所有Spring bean的残缺列表YesNoconditions显示在配置和主动配置类上评估的条件以及它们是否匹配的起因YesNoconfigprops显示所有@ConfigurationProperties对照的列表YesNoenv从Spring的ConfigurableEnvironment中公开属性YesNoflyway显示已利用的任何Flyway数据库迁徙YesNohealth显示应用程序衰弱信息YesYeshttptrace显示HTTP跟踪信息(默认状况下,最初100个HTTP申请-响应交互)YesNoinfo显示任意应用程序信息YesYesloggers显示和批改应用程序中记录器的配置YesNoliquibase显示已利用的任何Liquibase数据库迁徙YesNometrics显示以后应用程序的“指标”信息YesNomappings显示所有@RequestMapping门路对照的列表YesNoscheduledtasks显示应用程序中调度的工作YesNosessions容许从Spring Session反对的会话存储中检索和删除用户会话YesNoshutdown让应用程序优雅地敞开NoNothreaddump执行线程转储YesNo如果你的应用程序是一个web应用程序(Spring MVC、Spring WebFlux或Jersey),你能够应用以下附加端点 ID形容默认启用默认公开heapdump返回一个GZip压缩的hprof堆转储文件YesNojolokia在HTTP上公开JMX bean(当Jolokia在类门路上时,WebFlux不可用)YesNologfile返回日志文件的内容,反对应用HTTP Range header来检索日志文件内容的一部分YesNoprometheus公开指标,该格局能够被Prometheus服务器采集YesNo注: actuator 在springboot 1.X 和springboot 2.X 存在较大的差别,本文以springboot 2.X 作为本文的解说 通常状况下,actuator内置的端点就能够满足咱们的日常需要了,但有时候咱们须要自定义端点。上面就列举一下几种罕用的自定义端点 自定义端点自定义前置条件,在pom.xml引入 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>1、自定义health当内置的health端点信息不满足用来判断咱们我的项目是否衰弱时,咱们能够自定义health 通过实现org.springframework.boot.actuate.health.HealthIndicator接口,形如 @Componentpublic class CustomHealthIndicator implements HealthIndicator { @Override public Health health() { int errorCode = check(); if (errorCode == 1) { return Health.down().withDetail("Error Code", errorCode).build(); } return Health.up().build(); } private int check() { // perform some specific health check return ThreadLocalRandom.current().nextInt(5); }}或者通过继承org.springframework.boot.actuate.health.AbstractHealthIndicator,形如 ...

September 20, 2021 · 2 min · jiezi

关于springboot:技术栈springboot2xvueactiviti522mysql带工作流系统

前言activiti工作流,企业erp、oa、hr、crm等审批零碎轻松落地,销假审批demo从流程绘制到审批完结实例。 一、我的项目模式springboot+vue+activiti集成了activiti在线编辑器,疾速开发平台,可插拔工作流服务。 二、我的项目介绍本我的项目领有用户治理,部门治理,代码生成,零碎监管,报表,大屏展现,业务审批等性能。性能太强大,只能粗矿的介绍,所见即所得,体验一下吧。 三、工作流1.流程模型绘制进入流程模型菜单,创立流程模型,这里波及到网关流转,须要设置流转条件,咱们这里是三十岁以上的走上面分支,三十岁以下的走下面的分支。点击分支线,设置流转条件即可。${age<=30}。保留后咱们在列表中点击公布即可。 2.流程配置公布后,就到了已公布模型列表,在启用之前,咱们须要先对进行节点设置和关联具体单据。 审批人员能够依据角色,间接指定人,部门,部门负责人,发起人部门负责人来进行配置,基本上满足所有的流转需要,并且能够设置表单变量。 设置流程表单,目前就做了一个销假的测试表单,并且能够对相应角色受权,做到自定义权限。 设置完后启动即可。 3.流程提交填写销假表单 提交单据,优先级分为一般,重要,紧急。音讯告诉能够抉择站内告诉,短信,邮件。 提交之后能够撤回单据。 查看流程流转进度状况。 也能够挂起,删除流程。 4.流程审批办理人审批列表,能够解决单据(驳回或者通过),也能够委托别人待办。 审批通过。 委托别人待代。 审批通过后进入已办列表。 年龄大于30岁,进入上面分支流转。 审批通过。 5.待办信息推送站内音讯推送。 总结下面只是展现了平台的审批流性能,还有其余很多性能没展现进去,本人也写了一些十分好用的组件,做到零碎麻利疾速开发,大大减少开发工夫和老本,目前正在对接挪动端审批。之前因为没有工夫去部署线上测试环境,思考近期部署,目前能够独自找我,近程演示,有须要源码的分割我。q:2500564056。 鸣谢: jeecgboot开源版http://jeecg.com/ 咖啡兔activiti实战https://kafeitu.me/

September 18, 2021 · 1 min · jiezi

关于springboot:SpringBoot源码-自动配置原理

关上主程序的代码,如下: @SpringBootApplicationpublic class MainApplication { public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); }}能够看到主程序很简略,只有一个注解 @SpringBootApplication,这就是springboot启动的外围点击 @SpringBootApplication 进去,能够发现它是一个组合注解,包含两个外围注解: @SpringBootConfiguration@EnableAutoConfigurationpublic @interface SpringBootApplication {……}点击@SpringBootConfiguration 进去,能够看到它只有一个注解@Configuration: @Configurationpublic @interface SpringBootConfiguration {……}@Configuration 代表着这是一个 Spring 配置类,能够认为 @SpringBootConfiguration = @Configuration再点击另一个注解 @EnableAutoConfiguration进去看,能够看到蕴含两个注解 @AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})public @interface EnableAutoConfiguration {……}点击 @AutoConfigurationPackage 进去,能够看到: @Import({Registrar.class})public @interface AutoConfigurationPackage {……}再点 Registrar 进去,看到这个办法: public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));}默认状况下,就是将主配置类(@SpringBootApplication)所在的包以及子包里的组件扫描到容器中。 回到EnableAutoConfiguration,点击 AutoConfigurationImportSelector 进去,能够看到有个 getCandidateConfigurations()办法,能够通过外面的loadFactoryNames()获取所有配置: List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());再点开 SpringFactoriesLoader.loadFactoryNames()办法,能够看到: 而在 SpringFactoriesLoader.loadFactoryNames() 办法参数中点开 getSpringFactoriesLoaderFactoryClass(),返回的是EnableAutoConfiguration.class: ...

September 16, 2021 · 1 min · jiezi

关于springboot:SpringBoot短视频小程序开发全栈式实战项目网盘下载

download:SpringBoot短视频小程序开发全栈式实战我的项目1。四位数字字母验证码的生成实例 复制代码 1 import random 2 if name =="__main__": #四位数字字母验证码的生成 3 checkcode="" #保留验证码的变量 4 for i in range(4): 5 index=random.randrange(0,4) #生成一个0~3中的数 6 if index!=i and index +1 !=i: 7 checkcode +=chr(random.randint(97,122)) # 生成a~z中的一个小写字母 8 elif index +1==i: 9 checkcode +=chr(random.randint(65,90) ) # 生成A~Z中的一个大写字母10 else:11 checkcode +=str(random.randint(1,9)) # 数字1-912 print(checkcode)复制代码输入为:m47A、8wQ9、vugS 2。格式化工夫函数 1 def formatTime(longtime):2 '''格式化工夫的函数'''3 import time 4 return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(longtime))3。记录显示登录日志实例 复制代码import time def show_info(): print('''输出提醒数字,执行相应操作0:退出1:查看登录日志 ''')def write_loginfo(username): """将用户名和登录工夫写入日志:param username: 用户名"""with open('log.txt','a') as f: string = "用户名:{} 登录工夫:{}\n".format(username ,time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))) f.write(string)def read_loginfo(): ...

September 16, 2021 · 3 min · jiezi

关于springboot:SpringBootVue3-项目实战打造企业级在线办公系统网盘分享

download:SpringBoot+Vue3 我的项目实战,打造企业级在线办公零碎什么是SpringBoot?SpringBoot是Spring开源阻止下的子项目,是Spring组件一站式解决打算,次要是简化了使用Spring的难度,简省了沉重的配置,提供了各种启动器,使开发者可能疾速上手。 为什么要使用SpringBoot?疾速开发,疾速整合,配置简略,内嵌服务容器 SpringBoot有哪些有点?容易上手,提成开发效率,简略开发。开箱即用,简省繁琐配置提供了一系列大型项目的非业务性功能。例如:内嵌服务器、运行数据监控、运行状况查看、和内部优化配置等总结:编码简略、配置简略、部署简略、监控变简略SpringBoot核心注解是哪几个?启动类下面的@SpringBootApplication,它也是SpringBoot的核心注解,蕴含了以下3个注解 @SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能@EnableAutoConfiguration:打开主动配置的功能@ComponentScan:扫描组件SpringBoot Starter的工作原理在SpringBoot启动时由@SpringBootApplication注解会主动去maven去读取每个starter中的spring.factories文件,该文件外面配置了所有需要被创建的bean,并且进行主动拆卸把bean注入Spring content中去 SpringBoot2.x 与SpringBoot1.x有什么区别?配置变更JDK版本升级第三方类库升级响应式Spring编程反对Http/2反对配置属性绑定SprigBoot反对什么前端模板?thymeleaf、freemarker、jsp 开启SpringBoot个性有哪几种形式?集成Spring-boot-starter-parent我的项目导入Spring-boot-dependencies我的项目依赖SpringBoot实现热部署有哪几种形式?Spring loadedSpring-boot-devtoolsSpringBoot事物怎么使用?在SpringBoot启动类下面使用@EnableTransactionManagement 开启事物之后 在service方法下面使用@TransactionalAysnc 异步调用方法在SpringBoot中使用异步调用是很简略,只需要在方法上使用@Async注解就可能实现方法的以部调用。需要开启@EnableAsync开启@Async才会失效。 如何在SpringBoot 启动的时候运行一些特定的代码?可能实现接口ApplicationRunnher或者CommandLineRunner,而后在run方法执行特定操作,该类会在SpringBoot启动实现运行 SpringBoot 有哪几种读取配置的形式?SpringBoot可能通过@PropertySource,@Value,@Enviornment,@ConfigurationProperties注解来绑定变量。 SpringBoot的主动配置原理是什么?次要是SpringBoot的启动类上的核心注解@SpringBootApplication注解配置类,有了这个主配置类启动的时候就会为SpringBoot开启一个@EnableAutoConfiguration注解主动配置功能有了这个@EnableAutoConfiguration 的话就会从配置文件META_INF/Spring.factories加载可能用到的主动配置类去重,并将execulde和excludeName属性携带的类排除过滤,将满足条件的主动配置类返回SpringBoot可能使用哪几种形式来加载配置?propertiesYAML文件零碎环境变量命令行参数还有更多形式什么是YAML?YAML是一种可读的序列化语言,通常用于配置文件,与属性文件相比,YAML更加结构化,更少混淆。 YAML配置的劣势有哪些?配置有序简单明了相比properties配置文件,YAML还有一个缺点,不反对@PropertySource注解导入自定义的YAML配置SpringBoot是否可能使用XML配置?是可能的,然而SpringBoot 推荐采纳Java配置而且而非XML配置。 Springboot 核心配置文件是什么?bootstrap.properties 和application.properties的区别bootstrap由父ApplicationContext加载的,比application优先加载,配置的应用程序上下文的疏导阶段失效。一般来说在SpringCloud配置的时候会使用到这个文件application由ApplicationContext加载,用于SpringBoot我的项目的自动化配置SpringBoot多数据源拆分思路?先在properties 配置文件中配置两个数据源,创建分包mapper,使用@ConfigurationProperties读取propeties中的配置,使用@MapperScan注册到对应的mapper中去 SpringBoot多数据源事物如何治理?第一种形式是在service层的@TransactionManager中使用transactionManager指定datasourceConfig配置的事物第二种是使用ta-atomikos实现分布式事物治理保护SpringBoot利用的方法有哪些?生产种使用Https使用snyk查看依赖关系升级到最新版本启动CRSF 保护使用安全策略形式XSS攻打如何实现SpringBoot应用程序的安全性?使用spring-boot-starter-security依赖项,并且增加安全配置。配置类扩大WebSecurityConfigurerAdapter 并且覆盖 比较Spring security和Shiro的优缺点?security是一个重量级框架,shiro是个轻量级框架security 配置繁琐,shiro简略security功能弱小 shiro功能简略SpringBoot种如何解决跨域问题?跨域可能在前端通过JSONP来解决,然而只能解决GET请求。SpringBoot可能通过实现WebMvcConfigurer接口而后重写addCorsMappings方法解决跨域问题。 SpringBoot中的监视器是什么?SpringBoot actuator是spring启动框架中的重要功能之一,SpringBoot监视器可帮助拜访生产环境的利用以后状态。 如何监听SpringBoot微服务?SpringBoot提供监视器端点,以及监控各个微服务的度量,这些端点对于获取是否失常运行很有帮助。咱们将使用SpringBoot Actuator之上,它提供了一个webui 使咱们可监督多个应用程序的度量 SpringBoot我的项目如何性能优化?我的项目比较大就采纳@Compoment指定扫描包范畴在我的项目启动时设置JVM初始化内存和最大内存雷同 SpringBoot 微服务中如何实现session共享?使用Spring session+redis实现session共享 SpringBoot中的starter到底是什么?这份starter是基于spring来实现的,它提供一个自动化配置拆卸类。在这个配置中通过条件来决定一个配置是否失效。而后提供了一系列的默认配置,也可能开发者自定义配置 什么是springboot 用来简化spring利用的初始搭建以及开发过程 使用特定的形式来进行配置(properties或yml文件) 创建独立的spring引用程序 main方法运行 嵌入的Tomcat 无需部署war文件 简化maven配置 主动配置spring增加对应功能starter自动化配置 springboot罕用的starter有哪些 spring-boot-starter-web 嵌入tomcat和web开发需要servlet与jsp反对 spring-boot-starter-data-jpa 数据库反对 spring-boot-starter-data-redis redis数据库反对 spring-boot-starter-data-solr solr反对 mybatis-spring-boot-starter 第三方的mybatis集成starterspringboot主动配置的原理 在spring程序main方法中 增加@SpringBootApplication或者@EnableAutoConfiguration 会主动去maven中读取每个starter中的spring.factories文件 该文件里配置了所有需要被创建spring容器中的beanspringboot读取配置文件的形式 ...

September 16, 2021 · 1 min · jiezi

关于springboot:Junit4升级至Junit5

为什么要降级junit5的个性和长处能够参考降级到junit5。其中我最看中的点是,Junit4错过了好多Java8的好多个性。Junit5能够很好得利用java8的个性,代码的表白更加清晰和简洁。 以下降级形式即能兼容原来junit4的单测(不须要改存量单测),又能应用junit5的新个性,是不是很爽! 降级的具体步骤pom中引入依赖 <dependencyManagement> <dependencies> <!-- 引入junit5的bom --> <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.5.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies></dependencyManagement><dependencies> <!-- Jupiter --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <!-- 用于兼容Junit4和junit3 --> <dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> </dependency></dependencies>异样重试性能在单测执行中,遇到数据库死锁的异样,最直观的想法是进行重试,junit5反对异样重试,具体操作如下 引入rerunner-jupiter<dependencies> <dependency> <groupId>io.github.artsok</groupId> <artifactId>rerunner-jupiter</artifactId> <version>2.1.6</version> <scope>test</scope> </dependency></dependencies>针对具体单测增加重试注解各一个用户核心重试的例子,针对 MySQLTransactionRollbackException 异样进行了3次重试。更多技能解锁,请移步rerunner-jupiter文档 @RepeatedIfExceptionsTest(repeats = 3, exceptions = MySQLTransactionRollbackException.class, name = "Retry deadlock failed test. Attempt {currentRepetition} of {totalRepetitions}")

September 14, 2021 · 1 min · jiezi

关于springboot:SpringBoot短视频小程序开发全栈式实战项目网盘分享

download:SpringBoot短视频小程序开发全栈式实战我的项目1。四位数字字母考据码的生成实例 print('登录胜利')write_loginfo(username) # 写入日志show_info() # 提示信息num = int(input('输出操作数字:')) # 输出数字while True: if num == 0: print('退出胜利') break elif num == 1: print('查看登录日志') read_loginfo() show_info() num = int(input('输出操作数字:')) else: print('您输出的数字有误') show_info() num = int(input('输出操作数字:'))3。模拟淘宝客服主动回复复制代码 1 # 工作2:模拟淘宝客服主动回复 2 3 def find_answer(question): 4 with open('reply.txt','r') as f : 5 while True: 6 line=f.readline() 7 if not line: #也可能为if line=='' 8 break 9 keyword=line.split('|')[0]1else: print ("平年")复制代码6。Python统计字符串中数字,字母,汉字的个数复制代码 1 import re 2 str_test='abcdefgHABC123456中华民族' 3 4 #把正则表达式编译成对象,如果常常使用该对象,此种形式可提高肯定效率 5 num_regex = re.compile(r'[0-9]') 6 zimu_regex = re.compile(r'[a-zA-z]') 7 hanzi_regex = re.compile(r'[\u4E00-\u9FA5]') 8 9 print('输出字符串:',str_test)10 #findall获取字符串中所有匹配的字符11 num_list = num_regex.findall(str_test)12 print('蕴含的数字:',num_list)13 zimu_list = zimu_regex.findall(str_test)14 print('蕴含的字母:',zimu_list)15 hanzi_list = hanzi_regex.findall(str_test)16 print('蕴含的汉字:',hanzi_list)复制代码 #羊车门问题 ...

September 13, 2021 · 1 min · jiezi

关于springboot:创建一个纯净的-SpringBoot-项目

据说有的同学还不晓得怎么创立一个洁净的 springboot 我的项目,难堪~ 明天给你露一手 ~ 1. idea创立关上idea,抉择 File -> New -> Project 抉择 Spring Initializr 抉择打包格局、java版本,填写项目名称 抉择依赖,也能够什么都不选,还有springboot 版本 点下一步接下来就实现了,能够看到代码里有了启动类,间接启动 2. 官网创立如果你不会用idea,那就进入官网创立(https://start.spring.io/),更简略,吊打idea 点击GENERATE 后,保留生成的包,用你喜爱的工具间接关上即可 搞掂,开销啤酒庆贺一下~~

September 12, 2021 · 1 min · jiezi

关于springboot:SpringBoot打造企业微信点餐系统新版微服务时代必学

代码主动生成mybatis-generator-maven-plugin的idea主动生成插件 <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.2</version> <configuration> <configurationFile>D:/GitFile/ka-product/ka-product-soa/src/main/resources/generatorConfig.xml</configurationFile> <verbose>true</verbose> <overwrite>true</overwrite> </configuration> </plugin> generatorConfig.xml <?xmlversion="1.0"encoding="UTF-8"?> generatorConfigurationPUBLIC"-//mybatis.org//DTDMyBatisGeneratorConfiguration1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <classPathEntrylocation="E:\repository\mysql\mysql-connector-java\5.1.30\mysql-connector-java-5.1.30.jar"/> <contextid="DB2Tables"targetRuntime="MyBatis3"> <commentGenerator> <propertyname="suppressDate"value="true"/> <propertyname="suppressAllComments"value="true"/> </commentGenerator> <jdbcConnectiondriverClass="com.mysql.jdbc.Driver"connectionURL="jdbc:mysql://192.168.147.35:3306/ka_product"userId="root"password="123456"/> <javaTypeResolver> <propertyname="forceBigDecimals"value="false"/> </javaTypeResolver> <javaModelGeneratortargetPackage="site.muhu.cmp.ucenter.model"targetProject="src/main/java"> <propertyname="enableSubPackages"value="true"/> <propertyname="trimStrings"value="true"/> </javaModelGenerator> <sqlMapGeneratortargetPackage="mapping"targetProject="src/main/resources"> <propertyname="enableSubPackages"value="true"/> </sqlMapGenerator> <javaClientGeneratortype="XMLMAPPER"targetPackage="site.muhu.cmp.ucenter.mapper"targetProject="src/main/java"> <propertyname="enableSubPackages"value="true"/> </javaClientGenerator> <tabletableName="b2b_sku_base"domainObjectName="B2bSkuBase"enableCountByExample="false"enableUpdateByExample="false"enableDeleteByExample="false"enableSelectByExample="false"selectByExampleQueryId="false"> <generatedKeycolumn="id"sqlStatement="MySql"identity="true"/> </table> </context> </generatorConfiguration>

September 10, 2021 · 1 min · jiezi

关于springboot:基于springbootvue前后端分离的activiti工作流后台管理系统

前言activiti工作流,企业erp、oa、hr、crm等审批零碎轻松落地,销假审批demo从流程绘制到审批完结实例。 一、我的项目模式springboot+vue+activiti集成了activiti在线编辑器,疾速开发平台,可插拔工作流服务。 二、我的项目介绍本我的项目领有用户治理,部门治理,代码生成,零碎监管,报表,大屏展现,业务审批等性能。性能太强大,只能粗矿的介绍,所见即所得,体验一下吧。 三、工作流1.流程模型绘制进入流程模型菜单,创立流程模型,这里波及到网关流转,须要设置流转条件,咱们这里是三十岁以上的走上面分支,三十岁以下的走下面的分支。点击分支线,设置流转条件即可。${age<=30}。保留后咱们在列表中点击公布即可。 2.流程配置公布后,就到了已公布模型列表,在启用之前,咱们须要先对进行节点设置和关联具体单据。 审批人员能够依据角色,间接指定人,部门,部门负责人,发起人部门负责人来进行配置,基本上满足所有的流转需要,并且能够设置表单变量。 设置流程表单,目前就做了一个销假的测试表单,并且能够对相应角色受权,做到自定义权限。 设置完后启动即可。 3.流程提交填写销假表单 提交单据,优先级分为一般,重要,紧急。音讯告诉能够抉择站内告诉,短信,邮件。 提交之后能够撤回单据。 查看流程流转进度状况。 也能够挂起,删除流程。 4.流程审批办理人审批列表,能够解决单据(驳回或者通过),也能够委托别人待办。 审批通过。 委托别人待代。 审批通过后进入已办列表。 年龄大于30岁,进入上面分支流转。 审批通过。 5.待办信息推送站内音讯推送。 总结下面只是展现了平台的审批流性能,还有其余很多性能没展现进去,本人也写了一些十分好用的组件,做到零碎麻利疾速开发,大大减少开发工夫和老本,目前正在对接挪动端审批。之前因为没有工夫去部署线上测试环境,思考近期部署,目前能够独自找我,近程演示,有须要源码的分割我。q:2500564056。 鸣谢: jeecgboot开源版http://jeecg.com/ 咖啡兔activiti实战https://kafeitu.me/

September 10, 2021 · 1 min · jiezi

关于springboot:spring-boot整合mybatisplus

1 MyBatis-plus 介绍MyBatis- plus是MyBatis的一个功能强大的加强工具包,用于简化开发。该工具包为MyBatis提供了一些高效、有用、开箱即用的个性,应用它能够无效地节俭开发工夫。 1.1 劣势MyBatis-plus 提供根底增删改查、分页查问等接口,可能无效简化开发,晋升开发效率。尤其是后盾管理系统、经营零碎等场景,特地适宜应用MyBatis-plus进行开发。自带的代码生成器也很好用,能够生成器能够疾速生成Mapper.java、Mapper.xml、dao、service、controller层代码。反对多种数据库:反对 mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb 等多种数据库2 spring-boot 整合 MyBatis-plus2.1 导入pom依赖<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.17</version></dependency>PS:能够自行批改版本。 2.2 配置数据库ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai type: com.alibaba.druid.pool.DruidDataSource# Logger Configlogging: level: com.baomidou.mybatisplus.samples: debug2.3 减少config文件@Configuration@MapperScan("com.prepared.mapper")public class MybatisPlusConfig {}PS: 如果须要分页,须要减少分页配置 // 旧版// @Bean// public PaginationInterceptor paginationInterceptor() {// PaginationInterceptor paginationInterceptor = new PaginationInterceptor();// // 设置申请的页面大于最大页后操作, true调回到首页,false 持续申请 默认false// // paginationInterceptor.setOverflow(false);// // 设置最大单页限度数量,默认 500 条,-1 不受限制// // paginationInterceptor.setLimit(500);// // 开启 count 的 join 优化,只针对局部 left join// paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));// return paginationInterceptor;// } // 最新版@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor;}2.4 集成代码生成器导入pom包 ...

September 10, 2021 · 3 min · jiezi

关于springboot:SpringBootVue3-项目实战打造企业级在线办公系统

download:SpringBoot+Vue3 我的项目实战,打造企业级在线办公零碎一、Springboot + maven 多模块我的项目 [springboot整合jsp][springboot整合freemarker][springboot整合mybatis与分页插件][springboot开启任务调度][springboot整合logback][springboot应用过滤器和监听器][springboot应用拦截器][springboot应用AOP][springboot自定义异样解决页面][springboot对立异样解决][springboot读取*.properties和自定义properties文件]springboot打包、部署......

September 9, 2021 · 1 min · jiezi

关于springboot:在SpringBoot下结合shiro对静态资源实现认证访问权限控制

背景在我的项目中咱们可能会存在一些动态资例如者图片、excel、pdf之类的文件拜访是须要受权才可拜访的。谬误示例1、咱们通过localhost/profile/girl.jpg能够拜访到美女图片,假如图片是存储在咱们的服务器的/home/data目录下,咱们能够通过nginx的反向动态资源代理能够间接将profile的申请文件转入到/home/data上来读取图片文件。2、咱们能够将图片间接存储至云服务器,或者一个外网拜访地址例如:https://yunurl/profile/girl.jpg。这种状况咱们无需配置nginx代理。以上两种状况都是在用户未认证,拿到地址即可拜访文件 解决方案引入shiro,springboot对文件的申请门路减少映射时shiro进行认证权限判断。咱们同样分两种状况1、文件存储在本地,即与利用在同一服务器,例如咱们的文件是在/home/data目录下,咱们能够通过此配置来映射 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/profile/**").addResourceLocations("file:/home/data"); }以上配置使得拜访localhost/profile/girl.jpg时会映射至本地门路上来查找文件,并且shiro会先对此申请进行认证的权限拜访,如果没有登录拜访此链接则会跳转至登录页面。2、对于存储在云端的文件,咱们同样也须要对其进行认证校验,而做法如上统一,联合addResourceLocations的源码咱们剖析 /** * Add one or more resource locations from which to serve static content. * Each location must point to a valid directory. Multiple locations may * be specified as a comma-separated list, and the locations will be checked * for a given resource in the order specified. * <p>For example, {{@code "/"}, {@code "classpath:/META-INF/public-web-resources/"}} * allows resources to be served both from the web application root and * from any JAR on the classpath that contains a * {@code /META-INF/public-web-resources/} directory, with resources in the * web application root taking precedence. * <p>For {@link org.springframework.core.io.UrlResource URL-based resources} * (e.g. files, HTTP URLs, etc) this method supports a special prefix to * indicate the charset associated with the URL so that relative paths * appended to it can be encoded correctly, e.g. * {@code [charset=Windows-31J]https://example.org/path}. * @return the same {@link ResourceHandlerRegistration} instance, for * chained method invocation */ public ResourceHandlerRegistration addResourceLocations(String... resourceLocations) { this.locationValues.addAll(Arrays.asList(resourceLocations)); return this; }动态资源的映射反对类门路、本地文件、并且反对url形式的门路映射,因而假如文件存储在https://yunurl/profile/girl.jpg此云门路下。咱们能够同样来通过此办法来映射 ...

September 8, 2021 · 1 min · jiezi

关于springboot:在SpringBoot下升级shiro之后导致中文路径400非法的解决方案

背景有数据文件是存储在服务器中应用的中文名,而数据库中对应保留了这些文件对应的存储门路,咱们应用SpringBoot的动态资源映射时默认会被shiro拦挡,并且有的状况下咱们也须要对文件的拜访进行认证权限拦挡。所以咱们的拜访的门路是会通过shiro的拦挡,而shiro1.7之后的版本对中文门路都进行了非法拦挡,因此中文门路会报400异样。 源码剖析InvalidRequestFilter这是一个Shiro在springboot中默认配置的全局过滤器,作用是拦挡过滤非法字符的Url,这个过滤器会间接把中文字符是为非法,从而拦挡申请,返回错误代码400。 解决方案鉴于背景与源码剖析,晓得是因为shiro降级1.7之后InvalidRequestFilter对中文门路进行了非法校验,而起关键作用的属性为blockNonAscii,因而咱们在SpringBoot中注入shiro框架的时候从新定义InvalidRequestFilter并将blockNonAscii设置为false @Bean public InvalidRequestFilter invalidRequestFilter(){ InvalidRequestFilter invalidRequestFilter = new InvalidRequestFilter(); invalidRequestFilter.setBlockNonAscii(false); return invalidRequestFilter; }至此,问题解决

September 8, 2021 · 1 min · jiezi

关于springboot:SpringBoot使用Thymeleaf模板实现一个小项目

1.创立SpringBoot我的项目,引入依赖pom.xml <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.4</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>ems-thymeleaf</artifactId> <version>0.0.1-SNAPSHOT</version> <name>ems-thymeleaf</name> <description>ems-thymeleaf</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>2.5.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>2.批改配置文件application.ymlserver: port: 8989 servlet: context-path: /emsspring: thymeleaf: cache: false suffix: .html prefix: classpath:/templates/ datasource: driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://localhost:3306/mydata?useUnicode=true&characterEncoding=UTF-8 username: root password: 835081 web: #裸露哪些资源能够通过我的项目名拜访 resources: static-locations: classpath:/static/,file:${img.file.dir}logging: level: root: info com.example.emsthymeleaf: debugmybatis: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.example.emsthymeleaf.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl# 上传文件门路img: file: dir: D:\develop\javademo\baizhidemo\ems-thymeleaf\file3.创立Config文件,将对html文件的拜访对应到Controller这样做的目标是为了简化拜访thymeleaf的html文件,不用为每个html文件独自创立controller申请 ...

September 5, 2021 · 7 min · jiezi

关于springboot:Spring-Boot-项目打包-Shell-脚本部署实践太有用了

本篇和大家分享的是 Spring Boot 打包并联合 Shell 脚本命令部署,重点在分享一个 shell 程序启动工具,心愿能便当工作; profiles指定不同环境的配置maven-assembly-plugin打公布压缩包分享shenniu_publish.sh程序启动工具linux上应用shenniu_publish.sh启动程序profiles指定不同环境的配置通常一套程序分为了很多个部署环境:开发,测试,uat,线上 等,咱们要想对这些环境辨别配置文件,能够通过两种形式: 通过application.yml中编码指定 profile.active=uat 形式指定通过mvn中profiles来辨别不同环境对应的配置文件夹,人工能够手动在idea勾选生成不同环境的包(举荐)这里咱们要讲的是第二种,首先在mvn中配置如下内容: <profiles> <profile> <id>node</id> <properties> <!--传递给脚本的参数值--> <activeProfile>node</activeProfile> <package-name>${scripts_packageName}</package-name> <boot-main>${scripts_bootMain}</boot-main> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>node1</id> <properties> <activeProfile>node1</activeProfile> <package-name>${scripts_packageName}</package-name> <boot-main>${scripts_bootMain}</boot-main> </properties> </profile> <profile> <id>node2</id> <properties> <activeProfile>node2</activeProfile> <package-name>${scripts_packageName}</package-name> <boot-main>${scripts_bootMain}</boot-main> </properties> </profile> </profiles>节点粗解: id:用来指定不同环境配置文件所在的目录,如下我这里: properties:该节点中的节点是可作为参数传递给其余配置文件,如我这里的package-name节点值就能够在另外的assembly.xml或者shell脚本文件中通过${package-name}获取到,如下: activeByDefault:指定默认环境配置文件夹 maven-assembly-plugin打公布压缩包对于springboot程序打包,能够分为jar和war,这里是jar包;有场景是咋们配置文件或者第三方等依赖包不想放到工程jar中,并且把这些文件压缩成一个zip包,不便上传到linux;此时通过maven-assembly-plugin和maven-jar-plugin就能够做到,mvn的配置如: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <addMavenDescriptor>false</addMavenDescriptor> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>${scripts_bootMain}</mainClass> </manifest> </archive> <!--打包排除项--> <excludes> <exclude>**/*.yml</exclude> <exclude>**/*.properties</exclude> <exclude>**/*.xml</exclude> <exclude>**/*.sh</exclude> </excludes> </configuration> <executions> <execution> <id>make-a-jar</id> <phase>compile</phase> <goals> <goal>jar</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>2.4</version> <!-- The configuration of the plugin --> <configuration> <!-- Specifies the configuration file of the assembly plugin --> <descriptors> <descriptor>${project.basedir}/src/main/assembly/assembly.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>值得注意的中央如下几点: ...

September 2, 2021 · 3 min · jiezi

关于springboot:springdataredis-动态切换数据源

最近遇到了一个麻烦的需要,咱们须要一个微服务利用同时拜访两个不同的 Redis 集群。个别咱们不会这么应用 Redis,然而这两个 Redis 原本是不同业务集群,当初须要一个微服务同时拜访。 其实咱们在理论业务开发的时候,可能还会遇到相似的场景。例如 Redis 读写拆散,这个也是 spring-data-redis 没有提供的性能,底层连接池例如 Lettuce 或者 Jedis 都提供了获取只读连贯的 API,然而缺点有两个: 下层 spring-data-redis 并没有封装这种接口基于 redis 的架构实现的,哨兵模式须要配置 sentinel 的地址,集群模式须要感知集群拓扑,在云原生环境中,这些都默认被云提供商暗藏了,裸露到里面的只有一个个动静 VIP 域名。因而,咱们须要在 spring-data-redis 的根底上实现一个动静切换 Redis 连贯的机制。 spring-data-redis 的配置类为:org.springframework.boot.autoconfigure.data.redis.RedisProperties,能够配置单个 Redis 实例或者 Redis 集群的连贯配置。依据这些配置,会生成对立的 Redis 连贯工厂 RedisConnectionFactory spring-data-redis 外围接口与背地的连贯相干形象关系为: 通过这个图,咱们能够晓得,咱们实现一个能够动静返回不同 Redis 连贯的 RedisConnectionFactory 即可,并且依据 spring-data-redis 的主动装载源码能够晓得,框架内的所有 RedisConnectionFactory 是 @ConditionalOnMissingBean 的,即咱们能够应用咱们本人实现的 RedisConnectionFactory 进行替换。 我的项目地址:https://github.com/JoJoTec/sp...咱们能够给 RedisProperties 配置外层封装一个多 Redis 连贯的配置,即MultiRedisProperties: @Data@NoArgsConstructor@ConfigurationProperties(prefix = "spring.redis")public class MultiRedisProperties { /** * 默认连贯必须配置,配置 key 为 default */ public static final String DEFAULT = "default"; private boolean enableMulti = false; private Map<String, RedisProperties> multi;}这个配置是在原有配置根底上的,也就是用户能够应用原有配置,也能够应用这种多 Redis 配置,就是须要配置 spring.redis.enable-multi=true。multi 这个 Map 中放入的 key 是数据源名称,用户能够在应用 RedisTemplate 或者 ReactiveRedisTemplate 之前,通过这个数据源名称指定用哪个 Redis。 ...

August 31, 2021 · 4 min · jiezi

关于springboot:Springboot项目打war包流程及过程中出现的问题

1.将Application启动类所在的module中的pom文件,把<packaging>标签批改成 <packaging>war</packaging> 2.移除自带内置tomcat <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions></dependency>3.增加servlet依赖 <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope></dependency>4.减少war的启动类 class WarStarterApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 指向Application这个springboot启动类 return builder.sources(Application.class); }}实现以上步骤后,应用maven的package性能打包即能放到tomcat中运行。 此时,如果想在我的项目中持续应用启动类运行我的项目,会呈现“ Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.”报错,但在tomcat中是能失常运行的。 2021-08-30 11:00:05 ERROR SpringApplication:858 - Application run failedorg.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:157) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) at com.jimmy.Application.main(Application.java:25)Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory(ServletWebServerApplicationContext.java:206) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:180) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:154) ... 8 more呈现该谬误的起因是上述步骤2,移除了内置的tomcat,而放在tomcat中的war包能失常运行,是因为tomcat中已自带了这个相干的包。 ...

August 30, 2021 · 1 min · jiezi

关于springboot:Springboot深入探究三springboot初始化过程

August 30, 2021 · 0 min · jiezi

关于springboot:Springboot深入探究二自动装配原理及自定义Starter

谈到springboot的外围机制,即主动拆卸。主动拆卸是在服务初始化时,主动将须要的组件注入到IOC容器种,以待后续的应用。 步骤: 获取各组件种META-INF/spring.factories文件依据其中value值(其value为类名),通过反射形式创立实体类将实体类注入到IOC容器中,待后续应用。这就是主动拆卸的三板斧。 为了便于了解主动拆卸,可通过本人实现starter jar来了解。 创立starter包 - 创立Empty Project及module,命名其为xxx-starter①定义配置文件标准及对应对象类,比方我规定波及我的starter的配置构造为cyz.pro //此注解相似@Value,能获取到配置文件中的配置条目@ConfigurationProperties(prefix = "cyz")public class PrefixPojo { private String pro; public String getPro() { return pro; } public void setPro(String pro) { this.pro = pro; }}②编写配置组件类,将配置对象搁置到容器中。只有申明为组件,便会注入到IOC容器中。 @Configuration@EnableConfigurationProperties(value = PrefixPojo.class)@ConditionalOnWebApplication //示意web利用条件下激活public class PrefixConfiguration { PrefixPojo pojo; @Autowired public PrefixConfiguration(PrefixPojo pojo) { this.pojo = pojo; } public String service(){ return pojo.getPro() + "!!"; }}③在resource门路创立“META-INF”门路,创立spring.factories文件,指定组件类 org.springframework.boot.autoconfigure.EnableAutoConfiguration = \pro.yizheng.yizhengstarter.config.PrefixConfiguration将其导出为jar ①其pom如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional></dependency>②通过maven install命令将此工程打到maven库中至此,自定义starter步骤完结。 ...

August 30, 2021 · 1 min · jiezi

关于springboot:SpringBoot深入探究一Springboot体系结构

最近为公司面试后端高级及中级开发人员,发现大部分开发人员对于springboot、springcloud的根底应用(即什么状况应用什么注解)以及一些死记硬背的实践(比方熔断器、服务降级、服务发现)都能说的差不多,但一旦谈到对其了解,能够说是无所不知。 无论软件工程、计算机科学,大到一门开发语言,小到一种开发框架,每一种招式背地都是澎湃简单的常识体系。波及算法、数据结构、设计模式、内存模式等等。仅仅理解某几个注解,背一些算法和面试题,这个并不合乎一个“软件工程师”的身份。 软件行业其实就像武侠世界,有不同的门派,不同的套路与招式。如果说计算机网络、算法与数据结构是各门各派通用的扎马步和左右直拳,那么java门派的spring系列肯定就是易筋经、如来神掌一类的外围招式。 如果读一本易筋经,只晓得其中的动作姿态,对于呼吸吐纳、真气运行压根就不晓得,那他的易筋经绝不会练成。各门各派的文治心法都是雷同的规定。放到Spring系列,也是一样。 所以想要写【spring深刻探索】这么一个系列,一方面做一个总结,最近我的项目多且杂,工作也越来越偏差治理,有一些知识点曾经含糊,另一方面也算是一个记录,以先后顺序对springboot常识体系做一个总结。 依据目前的总结,springboot的畛域分为以下几个方面: 外围实践 - 主动拆卸、IOC容器等根底入门 - web集成形式、Bean、事务、设计模式等。要害组件 - 任务调度、平安(Security、Shiro)、拦截器过滤器、分布式(dubbo+zookeeper)深刻了解 - 手动实现springboot框架(本人写一个)目前打算依照如此的程序来做springboot的记录。

August 30, 2021 · 1 min · jiezi

关于springboot:近期学习总结

前言在之前进行用户认证单元测试的时候,应用的是RestTemplate来模仿发动申请,然而平时用的更多的是MockMvc来进行后盾的申请。他们两个有何不同呢? RestTemplate部署的一个实在的web服务器来监听http申请,响应后果既然是实在的,那么它最大的弊病就是无奈进行事物回滚:在测试完结后,发现并没有对数据进行回滚,然而我在认证单元测试的时候并没有波及到数据库回滚的问题,所以跟mockmvc没有区别起因:@Transactional注解对于RestTemplate是生效的,所以不会进行事物回滚 MockMvcmockmvc通常是通过设置一个残缺的高低完应用程序来模仿HTTP申请和响应创立的是一个假的DispatcherServlet来模仿MVC堆栈的运行,并没有真正的网络链接既然是模仿的,那么咱们能够通过注解@Transactional来进行事务的回滚,并不会因为测试对数据库造成副作用。 总结综上:在更多时候进行单元测试的时候咱们还是应该偏向于MockMvc的,然而当有非凡需要的时候,咱们能够应用RestTemplate。 单元测试遇到的问题:问题一:仿照之前老我的项目写update的单元测试代码: @Test void update() throws Exception { Long id = new Random().nextLong(); Town oldTown = getOneTown(); oldTown.setId(id); Town newTown = getOneTown(); String jsonString = JSON.toJSONString(oldTown); String url = baseUrl + "/" + id.toString(); Mockito.doReturn(newTown).when(this.townService).update(id, oldTown); MockHttpServletRequestBuilder putRequest = MockMvcRequestBuilders .put(url) .contentType(MediaType.APPLICATION_JSON) .content(jsonString); this.mockMvc.perform(putRequest) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(newTown.getId())) .andExpect(MockMvcResultMatchers.jsonPath("$.name").exists()) .andExpect(MockMvcResultMatchers.jsonPath("$.pinyin").exists()) .andExpect(status().isOk()); ArgumentCaptor<Town> townArgumentCaptor = ArgumentCaptor.forClass(Town.class); ArgumentCaptor<Long> longArgumentCaptor = ArgumentCaptor.forClass(Long.class); Mockito.verify(this.townService).update(longArgumentCaptor.capture(), townArgumentCaptor.capture()); org.assertj.core.api.Assertions.assertThat(longArgumentCaptor.getValue()).isEqualTo(id); org.assertj.core.api.Assertions.assertThat(townArgumentCaptor.getValue().getName()).isEqualTo(oldTown.getName()); }首先是谬误翻译:返回值的id属性不存在 排查:1.第一反馈认为是我C层Long属性写成Integer类型了 2.查看响应状态:passed,申请胜利 3.猜想返回值出了问题,因为申请是没有问题的:认真想了之后:oldTown在模仿发动申请的时候,传入的oldTown与mock中的oldTown就不是一个对象了,那他必然不会返回newTown! 总结还是对于单元测试短少锤炼,还须要通过一直的写来加深了解。 问题二:依据id获取对象c层测试: ...

August 28, 2021 · 1 min · jiezi

关于springboot:SpringBootVue3-项目实战打造企业级在线办公系统

download:SpringBoot+Vue3 我的项目实战,打造企业级在线办公零碎package com.zzger.model; import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.concurrent.CountDownLatch; import com.zzger.module.queue.UrlQueue;import com.zzger.util.HttpUtils;import com.zzger.util.RegexUtils; public class WebSite { /** * 站点url */private String url; /** * 须要匍匐的url队列 */private UrlQueue<String> urls = new UrlQueue<>(); /** * 已匍匐过的页面url */private List<String> exitUrls = Collections.synchronizedList(new ArrayList<>()); private static final int TOTAL_THREADS = 12; private final CountDownLatch mStartSignal = new CountDownLatch(1); private final CountDownLatch mDoneSignal = new CountDownLatch(TOTAL_THREADS); public WebSite(String url){ this.url = url; urls.offer(url);//把网站首页退出须要匍匐的队列中} public void guangDu(){ new Thread(new Runnable() { @Override public void run() { paxing(HttpUtils.httpGet(url)); } }).start();} public void paxing(String html){ if(html.lastIndexOf("下一页</a></li></ul></div>")<0) return ; String strList = html.substring(html.indexOf("<li class=\\"next-page\\">"), html.lastIndexOf("下一页</a></li></ul></div>")); String url = RegexUtils.RegexString("<a href=\\"(.+?)\\"", strList); if(url.equals("Nothing")) return ; urls.put(url);//把url存储到队列中 paxing(HttpUtils.httpGet(url));} public void dxcPx(){ Page<DuanZi> page = new Gxpage(urls.take()); List<Section<DuanZi>> list = page.ybhqSection().getSections(); for(Section<DuanZi> section : list){ new Thread(new Runnable() { @Override public void run() { mStartSignal.countDown();// 计数减一为0,工作线程真正启动具体操作 try { mStartSignal.await();// 阻塞,期待mStartSignal计数为0运行前面的代码 // 所有的工作线程都在期待同一个启动的命令 } catch (InterruptedException e) { e.printStackTrace(); } DuanZi duanzi = section.select().getModel(); System.out.println(duanzi.getTitle()); mDoneSignal.countDown();// 实现当前计数减一 } } ).start(); } try { mDoneSignal.await();// 期待所有工作线程完结 } catch (InterruptedException e) { e.printStackTrace(); } dxcPx();//线程工作执行完后,再次获取url队列进行工作}public static void main(String[] args) { WebSite web = new WebSite("http://duanziwang.com"); web.guangDu(); for(int i = 0; i<10;i++){ new Thread(new Runnable() { @Override public void run() { web.dxcPx(); } }).start(); } }} ...

August 27, 2021 · 1 min · jiezi

关于springboot:springboot整合activiti自带在线流程编辑器整个程序源码请假审批

前言目前市场上有很多开源平台没有整合工作流,即便有,也是价格不菲的商业版,来看这篇文章的预计也理解了行情,必定不便宜。我这个疾速开发平台在零碎根底性能(用户治理,部门治理…)上整合了工作流,你能够间接用来开发ERP,OA,CRM等企业级利用,不必再放心如何再去花大量的工夫集成工作流进来。博主是集体开发者。钻研工作流有几年了,依稀记得第一次写工作流是用在江苏某省局的用车申请业务上,那时候年轻气盛,精力充沛可是能力无限,熬了几十个夜整进去了,即便进去了,也是代码很乱。前面也在好几个零碎参加了工作流的开发,目前是独自把这一套给抽取进去了,做成了可插拔的,能够十分不便的整合到你的程序中。上面咱们来摸索吧。 一、我的项目模式springboot+vue+activiti集成了activiti在线编辑器,疾速开发平台,可插拔工作流服务。 二、我的项目介绍本我的项目领有用户治理,部门治理,代码生成,零碎监管,报表,大屏展现,业务审批等性能。性能太强大,只能粗矿的介绍,所见即所得,体验一下吧。 三、工作流1.流程模型绘制进入流程模型菜单,创立流程模型,这里波及到网关流转,须要设置流转条件,咱们这里是三十岁以上的走上面分支,三十岁以下的走下面的分支。点击分支线,设置流转条件即可。${age<=30}。保留后咱们在列表中点击公布即可。 2.流程配置公布后,就到了已公布模型列表,在启用之前,咱们须要先对进行节点设置和关联具体单据。 审批人员能够依据角色,间接指定人,部门,部门负责人,发起人部门负责人来进行配置,基本上满足所有的流转需要,并且能够设置表单变量。 设置流程表单,目前就做了一个销假的测试表单,并且能够对相应角色受权,做到自定义权限。设置完后启动即可。 3.流程提交填写销假表单 提交单据,优先级分为一般,重要,紧急。音讯告诉能够抉择站内告诉,短信,邮件。 提交之后能够撤回单据。查看流程流转进度状况。 也能够挂起,删除流程。 4.流程审批办理人审批列表,能够解决单据(驳回或者通过),也能够委托别人待办。审批通过。委托别人待代。 审批通过后进入已办列表。 年龄大于30岁,进入上面分支流转。 审批通过。 5.待办信息推送站内音讯推送。 总结下面只是展现了平台的审批流性能,还有其余很多性能没展现进去,本人也写了一些十分好用的组件,做到零碎麻利疾速开发,大大减少开发工夫和老本,目前正在对接挪动端审批。之前因为没有工夫去部署线上测试环境,思考近期部署,目前能够独自找我,近程演示,有须要源码的分割我。q:2500564056。 鸣谢:jeecgboot开源版http://jeecg.com/咖啡兔activiti实战https://kafeitu.me/

August 25, 2021 · 1 min · jiezi

关于springboot:如何设计一个安全可靠的API接口

起源:https://www.cnblogs.com/juren... 前言阐明:在理论的业务中,难免会跟第三方零碎进行数据的交互与传递,那么如何保证数据在传输过程中的平安呢(防窃取)?除了https的协定之外,能不能加上通用的一套算法以及标准来保障传输的安全性呢? 上面咱们就来探讨下罕用的一些API设计的平安办法,可能不肯定是最好的,有更牛逼的实现形式,然而这篇是我本人的教训分享 目录token简介timestamp 简介sign 简介避免反复提交应用流程示例代码1. Token简介Token:拜访令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证,缩小用户名和明码的传输次数。个别状况下客户端(接口调用方)须要先向服务器端申请一个接口调用的账号,服务器会给出一个appId和一个key, key用于参数签名应用,留神key保留到客户端,须要做一些平安解决,避免泄露。 Token的值个别是UUID,服务端生成Token后须要将token做为key,将一些和token关联的信息作为value保留到缓存服务器中(redis),当一个申请过去后,服务器就去缓存服务器中查问这个Token是否存在,存在则调用接口,不存在返回接口谬误,个别通过拦截器或者过滤器来实现,Token分为两种: API Token(接口令牌): 用于拜访不须要用户登录的接口,如登录、注册、一些根本数据的获取等。 获取接口令牌须要拿appId、timestamp和sign来换,sign=加密(timestamp+key)USER Token(用户令牌): 用于拜访须要用户登录之后的接口,如:获取我的根本信息、保留、批改、删除等操作。获取用户令牌须要拿用户名和明码来换对于Token的时效性:token能够是一次性的、也能够在一段时间范畴内是无效的,具体应用哪种看业务须要。个别状况下接口最好应用https协定,如果应用http协定,Token机制只是一种缩小被黑的可能性,其实只能防小人不能防君子。 个别token、timestamp和sign 三个参数会在接口中会同时作为参数传递,每个参数都有各自的用处。 2. timestamp 简介timestamp: 工夫戳,是客户端调用接口时对应的以后工夫戳,工夫戳用于避免DoS攻打。当黑客劫持了申请的url去DoS攻打,每次调用接口时接口都会判断服务器以后零碎工夫和接口中传的的timestamp的差值,如果这个差值超过某个设置的工夫(如果5分钟),那么这个申请将被拦挡掉,如果在设置的超时工夫范畴内,是不能阻止DoS攻打的。 timestamp机制只能加重DoS攻打的工夫,缩短攻打工夫。如果黑客批改了工夫戳的值可通过sign签名机制来解决。 DoS DoS是Denial of Service的简称,即拒绝服务,造成DoS的攻击行为被称为DoS攻打,其目标是使计算机或网络无奈提供失常的服务。最常见的DoS攻打有计算机网络带宽攻打和连通性攻打。DoS攻打是指成心的攻打网络协议实现的缺点或间接通过横蛮伎俩仁慈地耗尽被攻打对象的资源,目标是让指标算机或网络无奈提供失常的服务或资源拜访,使指标零碎服务零碎进行响应甚至解体,而在此攻打中并不包含侵入指标服务器或指标网络设备。 这些服务资源包含网络带宽,文件系统空间容量,凋谢的过程或者容许的连贯。这种攻打会导致资源的匮乏,无论计算机的处理速度多快、内存容量多大、网络带宽的速度多快都无奈防止这种攻打带来的结果。 Pingflood: 该攻打在短时间外向目标主机发送大量ping包,造成网络梗塞或主机资源耗尽。Synflood: 该攻打以多个随机的源主机地址向目标主机发送SYN包,而在收到目标主机的SYN ACK后并不回应,这样,目标主机就为这些源主机建设了大量的连贯队列,而且因为没有收到ACK始终保护着这些队列,造成了资源的大量耗费而不能向失常申请提供服务。Smurf:该攻打向一个子网的播送地址发一个带有特定申请(如ICMP回应申请)的包,并且将源地址伪装成想要攻打的主机地址。子网上所有主机都回应播送包申请而向被攻打主机发包,使该主机受到攻打。Land-based:攻击者将一个包的源地址和目标地址都设置为指标主机的地址,而后将该包通过IP坑骗的形式发送给被攻打主机,这种包能够造成被攻打主机因试图与本人建设连贯而陷入死循环,从而很大水平地升高了零碎性能。Ping of Death:依据TCP/IP的标准,一个包的长度最大为65536字节。只管一个包的长度不能超过65536字节,然而一个包分成的多个片段的叠加却能做到。当一个主机收到了长度大于65536字节的包时,就是受到了Ping of Death攻打,该攻打会造成主机的宕机。Teardrop:IP数据包在网络传递时,数据包能够分成更小的片段。攻击者能够通过发送两段(或者更多)数据包来实现TearDrop攻打。第一个包的偏移量为0,长度为N,第二个包的偏移量小于N。为了合并这些数据段,TCP/IP堆栈会调配超乎寻常的微小资源,从而造成系统资源的不足甚至机器的重新启动。PingSweep:应用ICMP Echo轮询多个主机。3. Sign 简介nonce:随机值,是客户端随机生成的值,作为参数传递过去,随机值的目标是减少sign签名的多变性。随机值个别是数字和字母的组合,6位长度,随机值的组成和长度没有固定规定。 sign: 个别用于参数签名,避免参数被非法篡改,最常见的是批改金额等重要敏感参数, sign的值个别是将所有非空参数依照升续排序,再+token+key+timestamp+nonce(随机数)拼接在一起,最初应用某种加密算法进行加密,作为接口中的一个参数sign来传递,也能够将sign放到申请头中。 接口在网络传输过程中如果被黑客挟持,并批改其中的参数值,而后再持续调用接口,尽管参数的值被批改了,然而因为黑客不晓得sign是如何计算出来的,不晓得sign都有哪些值形成,不晓得以怎么的程序拼接在一起的,最重要的是不晓得签名字符串中的key是什么,所以黑客能够篡改参数的值,但没法批改sign的值,当服务器调用接口前会依照sign的规定从新计算出sign的值而后和接口传递的sign参数的值做比拟,如果相等示意参数值没有被篡改,如果不等,示意参数被非法篡改了,就不执行接口了。 4. 避免反复提交对于一些重要的操作须要避免客户端反复提交的(如非幂等性重要操作),具体办法是当申请第一次提交时将sign作为key保留到redis,并设置超时工夫,超时工夫和Timestamp中设置的差值雷同。当同一个申请第二次拜访时会先检测redis是否存在该sign,如果存在则证实反复提交了,接口就不再持续调用了。 如果sign在缓存服务器中因过期工夫到了,而被删除了,此时当这个url再次申请服务器时,因token的过期工夫和sign的过期工夫始终,sign过期也意味着token过期,那样同样的url再拜访服务器会因token谬误会被拦挡掉,这就是为什么sign和token的过期工夫要保持一致的起因。回绝反复调用机制确保URL被他人截获了也无奈应用(如抓取数据)。 对于哪些接口须要避免反复提交能够自定义个注解来标记。 留神:所有的安全措施都用上的话有时候不免太过简单,在理论我的项目中须要依据本身状况作出裁剪,比方能够只应用签名机制就能够保障信息不会被篡改,或者定向提供服务的时候只用Token机制就能够了。如何裁剪,全看我的项目理论状况和对接口安全性的要求。5. 应用流程接口调用方(客户端)向接口提供方(服务器)申请接口调用账号,申请胜利后,接口提供方会给接口调用方一个appId和一个key参数客户端携带参数appId、timestamp、sign去调用服务器端的API token,其中sign=加密(appId + timestamp+key)客户端拿着api\_token 去拜访不须要登录就能拜访的接口当拜访用户须要登录的接口时,客户端跳转到登录页面,通过用户名和明码调用登录接口,登录接口会返回一个usertoken, 客户端拿着usertoken 去拜访须要登录能力拜访的接口sign的作用是避免参数被篡改,客户端调用服务端时须要传递sign参数,服务器响应客户端时也能够返回一个sign用于客户度校验返回的值是否被非法篡改了。客户端传的sign和服务器端响应的sign算法可能会不同。 6. 示例代码1. dependency<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency>    <groupId>redis.clients</groupId>    <artifactId>jedis</artifactId>    <version>2.9.0</version></dependency><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-web</artifactId></dependency>2. RedisConfiguration@Configurationpublic class RedisConfiguration {    @Bean    public JedisConnectionFactory jedisConnectionFactory(){        return new JedisConnectionFactory();    }    /**     * 反对存储对象     * @return     */    @Bean    public RedisTemplate<String, String> redisTemplate(){        RedisTemplate<String, String> redisTemplate = new StringRedisTemplate();        redisTemplate.setConnectionFactory(jedisConnectionFactory());        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);        ObjectMapper objectMapper = new ObjectMapper();        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);        redisTemplate.afterPropertiesSet();        return redisTemplate;    }}3. TokenController@Slf4j@RestController@RequestMapping("/api/token")public class TokenController {    @Autowired    private RedisTemplate redisTemplate;    /**     * API Token     *     * @param sign     * @return     */    @PostMapping("/api_token")    public ApiResponse<AccessToken> apiToken(String appId, @RequestHeader("timestamp") String timestamp, @RequestHeader("sign") String sign) {        Assert.isTrue(!StringUtils.isEmpty(appId) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "参数谬误");        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);        Assert.isTrue(reqeustInterval < 5 * 60 * 1000, "申请过期,请从新申请");        // 1. 依据appId查询数据库获取appSecret        AppInfo appInfo = new AppInfo("1", "12345678954556");        // 2. 校验签名        String signString = timestamp + appId + appInfo.getKey();        String signature = MD5Util.encode(signString);        log.info(signature);        Assert.isTrue(signature.equals(sign), "签名谬误");        // 3. 如果正确生成一个token保留到redis中,如果谬误返回错误信息        AccessToken accessToken = this.saveToken(0, appInfo, null);        return ApiResponse.success(accessToken);    }    @NotRepeatSubmit(5000)    @PostMapping("user_token")    public ApiResponse<UserInfo> userToken(String username, String password) {        // 依据用户名查问明码, 并比拟明码(明码能够RSA加密一下)        UserInfo userInfo = new UserInfo(username, "81255cb0dca1a5f304328a70ac85dcbd", "111111");        String pwd = password + userInfo.getSalt();        String passwordMD5 = MD5Util.encode(pwd);        Assert.isTrue(passwordMD5.equals(userInfo.getPassword()), "明码谬误");        // 2. 保留Token        AppInfo appInfo = new AppInfo("1", "12345678954556");        AccessToken accessToken = this.saveToken(1, appInfo, userInfo);        userInfo.setAccessToken(accessToken);        return ApiResponse.success(userInfo);    }    private AccessToken saveToken(int tokenType, AppInfo appInfo,  UserInfo userInfo) {        String token = UUID.randomUUID().toString();        // token有效期为2小时        Calendar calendar = Calendar.getInstance();        calendar.setTime(new Date());        calendar.add(Calendar.SECOND, 7200);        Date expireTime = calendar.getTime();        // 4. 保留token        ValueOperations<String, TokenInfo> operations = redisTemplate.opsForValue();        TokenInfo tokenInfo = new TokenInfo();        tokenInfo.setTokenType(tokenType);        tokenInfo.setAppInfo(appInfo);        if (tokenType == 1) {            tokenInfo.setUserInfo(userInfo);        }        operations.set(token, tokenInfo, 7200, TimeUnit.SECONDS);        AccessToken accessToken = new AccessToken(token, expireTime);        return accessToken;    }    public static void main(String[] args) {        long timestamp = System.currentTimeMillis();        System.out.println(timestamp);        String signString = timestamp + "1" + "12345678954556";        String sign = MD5Util.encode(signString);        System.out.println(sign);        System.out.println("-------------------");        signString = "password=123456&username=1&12345678954556" + "ff03e64b-427b-45a7-b78b-47d9e8597d3b1529815393153sdfsdfsfs" + timestamp + "A1scr6";        sign = MD5Util.encode(signString);        System.out.println(sign);    }}4. WebMvcConfiguration@Configurationpublic class WebMvcConfiguration extends WebMvcConfigurationSupport {    private static final String[] excludePathPatterns  = {"/api/token/api_token"};    @Autowired    private TokenInterceptor tokenInterceptor;    @Override    public void addInterceptors(InterceptorRegistry registry) {        super.addInterceptors(registry);        registry.addInterceptor(tokenInterceptor)                .addPathPatterns("/api/**")                .excludePathPatterns(excludePathPatterns);    }}5. TokenInterceptor@Componentpublic class TokenInterceptor extends HandlerInterceptorAdapter {    @Autowired    private RedisTemplate redisTemplate;    /**     *     * @param request     * @param response     * @param handler 拜访的指标办法     * @return     * @throws Exception     */    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        String token = request.getHeader("token");        String timestamp = request.getHeader("timestamp");        // 随机字符串        String nonce = request.getHeader("nonce");        String sign = request.getHeader("sign");        Assert.isTrue(!StringUtils.isEmpty(token) && !StringUtils.isEmpty(timestamp) && !StringUtils.isEmpty(sign), "参数谬误");        // 获取超时工夫        NotRepeatSubmit notRepeatSubmit = ApiUtil.getNotRepeatSubmit(handler);        long expireTime = notRepeatSubmit == null ? 5 * 60 * 1000 : notRepeatSubmit.value();        // 2. 申请工夫距离        long reqeustInterval = System.currentTimeMillis() - Long.valueOf(timestamp);        Assert.isTrue(reqeustInterval < expireTime, "申请超时,请从新申请");        // 3. 校验Token是否存在        ValueOperations<String, TokenInfo> tokenRedis = redisTemplate.opsForValue();        TokenInfo tokenInfo = tokenRedis.get(token);        Assert.notNull(tokenInfo, "token谬误");        // 4. 校验签名(将所有的参数加进来,避免他人篡改参数) 所有参数看参数名升续排序拼接成url        // 申请参数 + token + timestamp + nonce        String signString = ApiUtil.concatSignString(request) + tokenInfo.getAppInfo().getKey() + token + timestamp + nonce;        String signature = MD5Util.encode(signString);        boolean flag = signature.equals(sign);        Assert.isTrue(flag, "签名谬误");        // 5. 回绝反复调用(第一次拜访时存储,过期工夫和申请超时工夫保持一致), 只有标注不容许反复提交注解的才会校验        if (notRepeatSubmit != null) {            ValueOperations<String, Integer> signRedis = redisTemplate.opsForValue();            boolean exists = redisTemplate.hasKey(sign);            Assert.isTrue(!exists, "请勿反复提交");            signRedis.set(sign, 0, expireTime, TimeUnit.MILLISECONDS);        }        return super.preHandle(request, response, handler);    }}6. MD5Util ----MD5工具类,加密生成数字签名public class MD5Util {    private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };    private static String byteArrayToHexString(byte b[]) {        StringBuffer resultSb = new StringBuffer();        for (int i = 0; i < b.length; i++)            resultSb.append(byteToHexString(b[i]));        return resultSb.toString();    }    private static String byteToHexString(byte b) {        int n = b;        if (n < 0)            n += 256;        int d1 = n / 16;        int d2 = n % 16;        return hexDigits[d1] + hexDigits[d2];    }    public static String encode(String origin) {        return encode(origin, "UTF-8");    }    public static String encode(String origin, String charsetname) {        String resultString = null;        try {            resultString = new String(origin);            MessageDigest md = MessageDigest.getInstance("MD5");            if (charsetname == null || "".equals(charsetname))                resultString = byteArrayToHexString(md.digest(resultString                        .getBytes()));            else                resultString = byteArrayToHexString(md.digest(resultString                        .getBytes(charsetname)));        } catch (Exception exception) {        }        return resultString;    }}7. @NotRepeatSubmit  -----自定义注解,避免反复提交。/** * 禁止反复提交 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NotRepeatSubmit {    /** 过期工夫,单位毫秒 **/    long value() default 5000;}8. AccessToken@Data@AllArgsConstructorpublic class AccessToken {    /** token */    private String token;    /** 生效工夫 */    private Date expireTime;}9. AppInfo@Data@NoArgsConstructor@AllArgsConstructorpublic class AppInfo {    /** App id */    private String appId;    /** API 秘钥 */    private String key;}10. TokenInfo@Datapublic class TokenInfo {    /** token类型: api:0 、user:1 */    private Integer tokenType;    /** App 信息 */    private AppInfo appInfo;    /** 用户其余数据 */    private UserInfo userInfo;}11. UserInfo@Datapublic class UserInfo {    /** 用户名 */    private String username;    /** 手机号 */    private String mobile;    /** 邮箱 */    private String email;    /** 明码 */    private String password;    /** 盐 */    private String salt;    private AccessToken accessToken;    public UserInfo(String username, String password, String salt) {        this.username = username;        this.password = password;        this.salt = salt;    }}12. ApiCodeEnum/** * 错误码code能够应用纯数字,应用不同区间标识一类谬误,也能够应用纯字符,也能够应用前缀+编号 * * 错误码:ERR + 编号 * * 能够应用日志级别的前缀作为谬误类型辨别 Info(I) Error(E) Warning(W) * * 或者以业务模块 + 谬误号 * * TODO 错误码设计 * * Alipay 用了两个code,两个msg(https://docs.open.alipay.com/api_1/alipay.trade.pay) */public enum ApiCodeEnum {    SUCCESS("10000", "success"),    UNKNOW_ERROR("ERR0001","未知谬误"),    PARAMETER_ERROR("ERR0002","参数谬误"),    TOKEN_EXPIRE("ERR0003","认证过期"),    REQUEST_TIMEOUT("ERR0004","申请超时"),    SIGN_ERROR("ERR0005","签名谬误"),    REPEAT_SUBMIT("ERR0006","请不要频繁操作"),    ;    /** 代码 */    private String code;    /** 后果 */    private String msg;    ApiCodeEnum(String code, String msg) {        this.code = code;        this.msg = msg;    }    public String getCode() {        return code;    }    public String getMsg() {        return msg;    }}13. ApiResult@Data@NoArgsConstructor@AllArgsConstructorpublic class ApiResult {    /** 代码 */    private String code;    /** 后果 */    private String msg;}14. ApiUtil -------这个参考支付宝加密的算法写的.我间接Copy过去了。public class ApiUtil {    /**     * 按参数名升续拼接参数     * @param request     * @return     */    public static String concatSignString(HttpServletRequest request) {        Map<String, String> paramterMap = new HashMap<>();        request.getParameterMap().forEach((key, value) -> paramterMap.put(key, value[0]));        // 依照key升续排序,而后拼接参数        Set<String> keySet = paramterMap.keySet();        String[] keyArray = keySet.toArray(new String[keySet.size()]);        Arrays.sort(keyArray);        StringBuilder sb = new StringBuilder();        for (String k : keyArray) {            // 或略掉的字段            if (k.equals("sign")) {                continue;            }            if (paramterMap.get(k).trim().length() > 0) {                // 参数值为空,则不参加签名                sb.append(k).append("=").append(paramterMap.get(k).trim()).append("&");            }        }        return sb.toString();    }    public static String concatSignString(Map<String, String> map) {        Map<String, String> paramterMap = new HashMap<>();        map.forEach((key, value) -> paramterMap.put(key, value));        // 依照key升续排序,而后拼接参数        Set<String> keySet = paramterMap.keySet();        String[] keyArray = keySet.toArray(new String[keySet.size()]);        Arrays.sort(keyArray);        StringBuilder sb = new StringBuilder();        for (String k : keyArray) {            if (paramterMap.get(k).trim().length() > 0) {                // 参数值为空,则不参加签名                sb.append(k).append("=").append(paramterMap.get(k).trim()).append("&");            }        }        return sb.toString();    }    /**     * 获取办法上的@NotRepeatSubmit注解     * @param handler     * @return     */    public static NotRepeatSubmit getNotRepeatSubmit(Object handler) {        if (handler instanceof HandlerMethod) {            HandlerMethod handlerMethod = (HandlerMethod) handler;            Method method = handlerMethod.getMethod();            NotRepeatSubmit annotation = method.getAnnotation(NotRepeatSubmit.class);            return annotation;        }        return null;    }}15. ApiResponse@Data@Slf4jpublic class ApiResponse<T> {    /** 后果 */    private ApiResult result;    /** 数据 */    private T data;    /** 签名 */    private String sign;    public static <T> ApiResponse success(T data) {        return response(ApiCodeEnum.SUCCESS.getCode(), ApiCodeEnum.SUCCESS.getMsg(), data);    }    public static ApiResponse error(String code, String msg) {        return response(code, msg, null);    }    public static <T> ApiResponse response(String code, String msg, T data) {        ApiResult result = new ApiResult(code, msg);        ApiResponse response = new ApiResponse();        response.setResult(result);        response.setData(data);        String sign = signData(data);        response.setSign(sign);        return response;    }    private static <T> String signData(T data) {        // TODO 查问key        String key = "12345678954556";        Map<String, String> responseMap = null;        try {            responseMap = getFields(data);        } catch (IllegalAccessException e) {            return null;        }        String urlComponent = ApiUtil.concatSignString(responseMap);        String signature = urlComponent + "key=" + key;        String sign = MD5Util.encode(signature);        return sign;    }    /**     * @param data 反射的对象,获取对象的字段名和值     * @throws IllegalArgumentException     * @throws IllegalAccessException     */    public static Map<String, String> getFields(Object data) throws IllegalAccessException, IllegalArgumentException {        if (data == null) return null;        Map<String, String> map = new HashMap<>();        Field[] fields = data.getClass().getDeclaredFields();        for (int i = 0; i < fields.length; i++) {            Field field = fields[i];            field.setAccessible(true);            String name = field.getName();            Object value = field.get(data);            if (field.get(data) != null) {                map.put(name, value.toString());            }        }        return map;    }}7. ThreadLocalThreadLocal是线程内的全局上下文。就是在单个线程中,办法之间共享的内存,每个办法都能够从该上下文中获取值和批改值。 ...

August 24, 2021 · 1 min · jiezi

关于springboot:如何构建-Spring-Boot-12-因素应用

【注】本文译自:How to build a Spring Boot 12-Factor app (theserverside.com)在这里,咱们看看 Spring Boot 框架如何反对十二因素利用的办法,以及 GitHub、Docker 和 Kubernetes 等工具填补了哪些空白。    没有国际标准组织指定 Spring Boot 利用作为微服务必须满足的规范。Heroku 联结创始人 Adam Wiggins 向部署到 Heroku 平台的开发人员提供的 12 条倡议是开发人员最靠近的一套云原生开发指南。    这 12 条戒律被称为“十二因素利用方法论”,已成为创立古代的以 Docker 和 Kubernetes 为部署指标的古代云原生微服务的事实标准。    开发基于 Java 的微服务最风行的平台是 Spring Boot。以下是 Spring Boot 如何反对十二因素利用方法论的准则。 1. Spring Boot 代码库    并非每个 12 因素的准则都间接映射到 Spring Boot。代码库准则就是责任落在 Spring 框架之外的一个例子。    依据十二因素利用,每个 Spring Boot 微服务都应该有本人独立的代码库。这能够通过创立单个 Git 存储库来实现,开发人员能够在其中奉献代码、合并分支和修复谬误。如果您的 Spring Boot 利用托管在其本人的 Git 存储库中,那么您曾经正确地实现了 12 因素代码库要求。 2. 内部化依赖治理    如果您应用 Spring Boot 初始化器创立一个我的项目,则必须在 Gradle 或 Maven 之间进行抉择作为我的项目的构建工具。这两个工具都将治理依赖项内部化。    如果您将 JAR 文件放在我的项目的 lib 目录之外,并在 Maven POM 或 Gradle 构建文件中列出程序的所有内部依赖项,则您的 Spring Boot 应用程序将正确实现 12 因素 依赖项治理。 ...

August 23, 2021 · 2 min · jiezi

关于springboot:Conditional注解与SpringBoot组件扩展

明天,咱们还是来补一下SpringBoot主动拆卸原理留下的坑:如何查看组件的源码并进行自定义扩大。 在聊这个之前,咱们得先来学习一下@Conditional注解的应用,看过组件里一些主动配置类的小伙伴必定会发现这样的景象:外面充斥了大量的@ConditionalOnXxxxx的注解,那么这些注解的用途是什么呢? Conditional注解Conditional注解是个条件注解,将该注解加在Bean上,当满足注解中所须要的条件时,这个Bean才会被启用。 例子建设一个Spring我的项目,引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId></dependency>编写Conditional类 import org.springframework.context.annotation.Condition;import org.springframework.context.annotation.ConditionContext;import org.springframework.core.type.AnnotatedTypeMetadata;/** * @author Zijian Liao * @since 1.0.0 */public class FooConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return true; }}这里先不做任何操作,默认返回true编写一个Service用于测试 @Component@Conditional(FooConditional.class)public class FooService { public FooService(){ System.out.println("foo service init!!"); }}在下面加上@Conditional注解,并指定应用咱们本人的Conditional编写启动类 @ComponentScan@Configurationpublic class ConditionalApplication { public static void main(String[] args) { new AnnotationConfigApplicationContext(ConditionalApplication.class); }}这里采取了最原始的启动形式,不晓得还有没有小伙伴记得学习Spring入门时天天写这个类启动测试 将Conditonal类中返回ture,改为返回false,再次测试 日志外面不再呈现foo service init!!,阐明FooService没有注入到容器中,Conditonal失效了原理这里说一下大抵的过程:Spring在扫描到该Bean时,判断该Bean是否含有@Conditional注解,如果有,则应用反射实例化注解中的条件类,而后调用条件类的matchs办法,如果返回false,则跳过该Bean 感兴趣的小伙伴能够看下这块源码:ConditionEvaluator#shouldSkip,或者与我交换也是能够的哈进阶看完例子,有没有有种好鸡肋的感觉?因为单纯的应用@Conditional注解外面只能传入一个class,可操作性太小了,所以咱们能够将它革新一下,革新形式如下: 编写Conditional类 public class OnFooConditional implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // 取出自定义注解ConditionalOnFoo中的所有变量 final Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnFoo.class.getName()); if (attributes == null) { return false; } // 返回value的值 return (boolean) attributes.get("value"); }}编写自定义条件注解 ...

August 21, 2021 · 2 min · jiezi

关于springboot:spring单元测试MockMockBean踩坑及没有真实执行的理解

前言笔者在晚期就曾纠结过这个问题,过后只是简略的了解为Mock和MockBean注解的内部依赖并不会实在执行,然而这个没有实在执行又代表哪个意思呢?那Spy实在执行的了解是如何呢?Mock和MockBean又有啥区别呢? 非实在执行:引言:始终蛊惑执行的是mock的bean,然而不晓得为何并没有执行又进行测试呢?其中碰到一个实例很好: @Testpublic void mockitoTest(){ //生成一个mock对象 List<String> mockString = Mockito.mock(List.class); //打印mock对象的类名,看看mock对象为何物 System.out.println(mockString.getClass().getName()); //操作mock对象 mockString.add("0"); mockString.get(0); // 此时必定如果为实在执行,那么以后应该没有数据 mockString.clear(); //verify验证,mock对象是否产生过某些行为 //如果验证不通过,Mockito会抛出异样 Mockito.verify(mockString).add("one"); Mockito.verify(mockString).get(0); //指定mock办法,获取一个不存在的数据,如果实在执行,那么应该报错 Mockito.when(mockString.get(1)).thenReturn("执行胜利"); //这里打印出“13”(但咱们晓得mockedList理论是没有下标为1的元素,这就是mock的性能) System.out.println(mockString.get(1));}Mock和MockBean之前了解:二者用法简直雷同,只是初始化的时候有所不同。当初了解:二者初始化mock形式类似,然而二者适宜的场景却有很大区别 遇到问题:笔者将MockBean改为Mock后报错:@SpringBootTest// 用于进行模仿的 HTTP 申请@AutoConfigureMockMvcpublic class RepeaterControllerTest extends ControllerTest { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired RepeaterController repeaterController; @Autowired protected MockMvc mockMvc; @Mock RepeaterService repeaterService; private static final String baseUrl = "/repeater"; /** * 取得一个中继器. * * @return 中继器. */ public static Repeater getOneRepeater() { Repeater repeater = new Repeater(); repeater.setId(new Random().nextInt()); repeater.setType((byte) (new Random().nextInt() % 127)); repeater.setCreationTime(new Timestamp(System.currentTimeMillis())); repeater.setLastOnLineTime(new Timestamp(System.currentTimeMillis())); repeater.setMonitors(new ArrayList<>()); repeater.setSpec(new net.bytebuddy.utility.RandomString().nextString()); repeater.setPosition(new RandomString().nextString()); repeater.setReturnValue(new Random().nextLong()); repeater.setLatitude(BigDecimal.valueOf(Math.random())); repeater.setLongitude(BigDecimal.valueOf(Math.random())); return repeater; } @Test void getById() throws Exception { Repeater repeater = getOneRepeater(); Mockito.doReturn(repeater).when(this.repeaterService).getById(Mockito.eq(repeater.getId())); String url = baseUrl + "/" + repeater.getId().toString(); this.mockMvc.perform(MockMvcRequestBuilders.get(url)) .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(repeater.getId())) .andExpect(MockMvcResultMatchers.jsonPath("$.position").exists()) .andExpect(MockMvcResultMatchers.status().isOk()); }mock没有失效,执行的仍然是实在service: ...

August 21, 2021 · 1 min · jiezi

关于springboot:近期学习总结

前言首先我最次要的问题就是心急,对于我的项目任务分配不明确,不可能很好的将当前任务分工,导致每次提交pr代码量绝对比拟大,不仅减少本人的出错概率和测试难度,同时也给其余的合作成员带来很大的难度。 实例以用户登录后盾为例,在曾经跳过很多坑的状况下,第二次再去弄用户登录的后盾,本认为能够很好的解决,然而没有很好的将代码分块,导致代码量比拟大,同时在进行单元测试的时候,遇到了很多问题。 单元测试万物开头难,对于我来说,起初我是很恶感单元测试的,我不得不抵赖它对于团队合作的微小益处,然而因为锤炼比拟少,每次测试时候总是不违心用这种形式进行测试。在这里也感激潘老师的督促,可能让本人静下心来去理解单元测试每个代码的意思。上面我将用一个具体的单元测试进行举例: @Test void login() throws UnsupportedEncodingException { // 复制老我的项目的,用于增加正文 // 结构申请url String url = "http://localhost:" + port + "/user/login"; // 因为spring boot不提供主动配置的RestTemplate bean,他会提供一个restTemplateBuilder bean // 可用于在须要的时候创立RestTemplate实例。 // restTemplate用于发动申请,其中exchange能够指定申请办法 RestTemplate restTemplate = this.restTemplateBuilder.build(); // 结构一个申请的headers HttpHeaders headers = this.getChromeHeaders(); // 没有认证信息时401 // 预言发动申请抛出的异样为HttpClientErrorException。 Assertions.assertThrows(HttpClientErrorException.class, () -> restTemplate.getForObject(url, JSONObject.class)); try { // HttpEntity包含申请头和申请主体 HttpEntity entity = new HttpEntity(headers); // 调用exchange办法发动method为get,url为xxx,申请头和申请主体为entity,返回类型为User实体的 restTemplate.exchange(url, HttpMethod.GET, entity, User.class); } catch (HttpClientErrorException e) { // 捕捉异样为401,未认证 Assertions.assertEquals(e.getStatusCode().value(), HttpStatus.UNAUTHORIZED.value()); } // basic认证模式,结构前台传送信息, headers = this.getChromeHeaders(); // 增加认证信息 String auth = Base64.getEncoder().encodeToString( (appProperties.getUsername() + ":" + appProperties.getPassword()).getBytes("utf-8")); headers.add("Authorization", "Basic " + auth); // 结构一个申请信息,申请头为headers内容 HttpEntity entity = new HttpEntity(headers); // 获取响应实体,通过get申请 ResponseEntity<User> result = restTemplate.exchange(url, HttpMethod.GET, entity, User.class); // 从响应头中获取参数x-auth-token,并断言非空(后盾响应之后返回了token) String xAuthToken = result.getHeaders().get("x-auth-token").get(0); Assertions.assertNotNull(xAuthToken); // 获取响应主体,断言它的用户名等于xxx User body = result.getBody(); Assertions.assertEquals(appProperties.getUsername(), body.getUsername()); // x-auth-token认证 // 第一次申请调配了一个token,第二次申请间接应用调配的token headers = this.getChromeHeaders(); headers.add("x-auth-token", xAuthToken); // 获取完判断取得的user是之前的user。 User user = restTemplate.exchange(url, HttpMethod.GET, entity, User.class).getBody(); Assertions.assertEquals(UserInit.username, user.getUsername()); }总结:要应用麻利开发(将大的工作一直调配成各个小工作,保障每个小工作最晚当天能够实现)+ 单元测试的办法。 ...

August 20, 2021 · 1 min · jiezi

关于springboot:BPM敏捷Activiti开发平台工作流引擎springboot整合activiti

前言目前市场上有很多开源平台没有整合工作流,即便有,也是价格不菲的商业版,来看这篇文章的预计也理解了行情,必定不便宜。我这个疾速开发平台在零碎根底性能(用户治理,部门治理…)上整合了工作流,你能够间接用来开发ERP,OA,CRM等企业级利用,不必再放心如何再去花大量的工夫集成工作流进来。博主是集体开发者。钻研工作流有几年了,依稀记得第一次写工作流是用在江苏某省局的用车申请业务上,那时候年轻气盛,精力充沛可是能力无限,熬了几十个夜整进去了,即便进去了,也是代码很乱。前面也在好几个零碎参加了工作流的开发,目前是独自把这一套给抽取进去了,做成了可插拔的,能够十分不便的整合到你的程序中。上面咱们来摸索吧。 一、我的项目模式springboot+vue+activiti集成了activiti在线编辑器,疾速开发平台,可插拔工作流服务。 二、我的项目介绍本我的项目领有用户治理,部门治理,代码生成,零碎监管,报表,大屏展现,业务审批等性能。性能太强大,只能粗矿的介绍,所见即所得,体验一下吧。 三、工作流1.流程模型绘制进入流程模型菜单,创立流程模型,这里波及到网关流转,须要设置流转条件,咱们这里是三十岁以上的走上面分支,三十岁以下的走下面的分支。点击分支线,设置流转条件即可。${age<=30}。保留后咱们在列表中点击公布即可。 2.流程配置公布后,就到了已公布模型列表,在启用之前,咱们须要先对进行节点设置和关联具体单据。 审批人员能够依据角色,间接指定人,部门,部门负责人,发起人部门负责人来进行配置,基本上满足所有的流转需要,并且能够设置表单变量。 设置流程表单,目前就做了一个销假的测试表单,并且能够对相应角色受权,做到自定义权限。设置完后启动即可。 3.流程提交填写销假表单 提交单据,优先级分为一般,重要,紧急。音讯告诉能够抉择站内告诉,短信,邮件。 提交之后能够撤回单据。查看流程流转进度状况。 也能够挂起,删除流程。 4.流程审批办理人审批列表,能够解决单据(驳回或者通过),也能够委托别人待办。审批通过。委托别人待代。 审批通过后进入已办列表。 年龄大于30岁,进入上面分支流转。 审批通过。 5.待办信息推送站内音讯推送。 总结下面只是展现了平台的审批流性能,还有其余很多性能没展现进去,本人也写了一些十分好用的组件,做到零碎麻利疾速开发,大大减少开发工夫和老本,目前正在对接挪动端审批。之前因为没有工夫去部署线上测试环境,思考近期部署,目前能够独自找我,近程演示,有须要源码的分割我。q:2500564056。 鸣谢:jeecgboot开源版http://jeecg.com/咖啡兔activiti实战https://kafeitu.me/

August 20, 2021 · 1 min · jiezi

关于springboot:史上最全的SpringBoot学习教程

现在springboot越来越火,越来越多的公司抉择应用springboot作为我的项目的开发框架,其设计目标就是用来简化spring我的项目的搭建和开发过程,省略了传统spring、springmvc我的项目繁琐的配置,能够让开发人员疾速上手。它采纳的是建设生产就绪的应用程序观点,优先于配置的常规,缓缓的,你会爱上它,置信我。 Spring Boot简介Spring Boot 是Spring 家族中的一个全新的框架,它用来简化Spring 应用程序的创立和开发过程,也能够说Spring Boot 能简化咱们之前采纳SpringMVC + Spring + MyBatis 框架进行开发的过程。 采纳Spring Boot 能够非常容易和疾速地创立基于Spring 框架的应用程序,它让编码变简略了,配置变简略了,部署变简略了,监控变简略了。正因为Spring Boot 它化繁为简,让开发变得极其简略和疾速,所以在业界备受关注。 Spring Boot 的个性可能疾速创立基于Spring的应用程序 1)可能间接应用java main办法启动内嵌的 Tomcat 服务器运行 Spring Boot 程序,不需部署war包文件 2)提供约定的 starter POM 来简化 Maven 配置,让 Maven 的配置变得简略 3)自动化配置,依据我的项目的 Maven 依赖配置,Spring boot 主动配置 Spring、Spring mvc 等 4)提供了程序的健康检查等性能 5)根本能够齐全不应用 XML 配置文件,采纳注解配置 Spring Boot 四大外围主动配置、起步依赖、Actuator、命令行界面SpringBoot的次要指标是:为所有Spring开发提供基本上更疾速的入门开发体验(开发更快更不便);开箱即用,随着需要的变动,SpringBoot帮咱们解决了很多常见的问题,比方疾速集成其余组件;提供一系列大型项目通用的非性能性功能(例如嵌入式服务器,安全性,性能指标,运行状况查看,内部化配置)齐全没有代码生成,也不须要XML配置;总结一下就是:Spring家族推出springboot框架就是为了简化spring的配置与开发,使得在基于Spring框架的开发中开发起来更疾速更不便,简化或缩小相干的配置,进步开发效率; 第一个 SpringBoot 我的项目1、开发步骤项目名称:001-springboot-first (1)创立一个 Module,抉择类型为 Spring Initializr 疾速构建 (2) 设置 GAV 坐标及 pom 配置信息 (3) 抉择 Spring Boot 版本及依赖 ...

August 19, 2021 · 1 min · jiezi

关于springboot:后台登录遇到问题及总结

引言之前的登录和后盾初始化等都是凯强学长弄,在弄新零碎的登录堪称是遇到了很多的谬误,我想造成这样后果的起因是本人后盾学习不牢固,麻利开发思维没有把握。 H2数据库长久化开始并不知道采纳的是h2数据库,也看不懂,那时就在想老师建设了实体怎么测试呢,难道不须要配置数据库吗。就开始本人瞎琢磨,将workReview我的项目的数据库照搬过去,然而始终报错,问了老师才晓得曾经采纳h2数据库了。 应用办法:登录:初始化用户因为采纳起前台来测试后盾登录,那么就须要初始化一个用户,那么如何初始化一个用户呢:它能在起完后盾环境之前执行,我想是上面的作用:在初期为了让本人走的更远、更安稳,就不进行深刻理解了。 前后台链接因为后盾教程学习不牢固,加之太久没有写过后盾代码,在开始的时候居然为了前后台端口问题推敲了半天。起初问了凯强学长,后盾端口在起环境之后控制台找就行。Spring Security在前后台链接之后发现登录怎么都登录不上,就开始推敲是不是前后台办法写错了,最初发现没有自定义配置,导致spring security起环境之后会生成一个随机的明码和用户名user: @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = this.userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在")); // 设置用户角色 List<SimpleGrantedAuthority> authorities = new ArrayList<>(); return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities); }如果须要,能够去看喜硕学长的文章Spring Security and Angular 实现用户认证 X-auth-token认证正当我认为高枕无忧,开始测试的时候发现:登录能够登录胜利,然而登录之后申请就会报401,阐明登录之后再次申请后盾没有认证胜利,问了老师之后发现是没有启用header认证机制,也没有传token后盾启用header认证机制: /** * 应用header认证来替换默认的cookie认证 */ @Bean public HttpSessionStrategy httpSessionStrategy() { return new HeaderHttpSessionStrategy(); }前台减少x-auth-token拦截器: 登录流程: 总结:1.麻利开发思维(保障调配的工作可能当天实现)2.单元测试3.重复看教程 结语:本周是煎熬的一周,因为在用很多不相熟的,或者说不罕用的,然而提高真的很大。以应用单元测试进行开发为例,尽管开始很煎熬且遇到各种奇奇怪怪的问题,然而把握常识之后是很高兴的。

August 15, 2021 · 1 min · jiezi

关于springboot:看完就会的SpringBoot自动装配原理

前言我置信,只有你用过Spring Boot,就会对这样一个景象十分的好奇: 引入一个组件依赖,加个配置,这个组件就失效了。 举个例子来说,比方咱们罕用的Redis, 在Spring Boot中的应用形式是这样的: 1.引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>2.编写配置spring: redis: database: 0 timeout: 5000ms host: 127.0.0.1 port: 6379 password: 123456好了,接下来只须要应用时注入RedisTemplate就能应用了,像这样: @Autowiredprivate RedisTemplate redisTemplate;这期间,咱们做了什么嘛?咱们什么也没有做,那么,这个RedisTemplate对象是怎么注入到Spring容器中的呢? 接下来,就让咱们带着这样的疑难逐渐分析其中的原理,这个原理就叫做主动拆卸。 SPI先不焦急,在这之前,咱们先来理解理解上古大法:SPI机制。 SPI ,全称为 Service Provider Interface(服务提供者接口),是一种服务发现机制。它通过在classpath门路下的META-INF/services文件夹查找文件,主动加载文件中所定义的类。 栗子建一个工程,构造如下 provider 为服务提供方,能够了解为咱们的框架 zoo 为应用方,因为我的服务提供接口叫Animal,所以所有实现都是动物~ pom.xml外面啥都没有1. 定义一个接口在provider模块中定义接口Animal package cn.zijiancode.spi.provider;/** * 服务提供者 动物 */public interface Animal { // 叫 void call();}2. 应用该接口在zoo模块中引入provider <dependency> <groupId>cn.zijiancode</groupId> <artifactId>provider</artifactId> <version>1.0.0</version></dependency>写一个小猫咪实现Animal接口 public class Cat implements Animal { @Override public void call() { System.out.println("喵喵喵~~"); }}写一个狗子也实现Animal接口 ...

August 13, 2021 · 3 min · jiezi

关于springboot:SpringBoot常用注解整理

@SpringBootApplication定义在main办法入口类处,用于启动sping boot利用我的项目 @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Configuration@EnableAutoConfiguration@ComponentScanpublic @interface SpringBootApplication { /** * Exclude specific auto-configuration classes such that they will never be applied. * @return the classes to exclude */ Class<?>[] exclude() default {};}@EnableAutoConfiguration 让spring boot依据类门路中的jar包依赖以后我的项目进行主动配置 在src/main/resources的META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration若有多个主动配置,用“,”隔开@ImportResource加载XML文件,个别是放在启动main类上 @ImportResource("classpath*:/spring/*.xml") 单个@ImportResource({"classpath*:/spring/1.xml","classpath*:/spring/2.xml"}) 多个@Valueapplication.properties定义属性,间接应用@Value注入即可 public class A{ @Value("${push.start:0}") 如果缺失,默认值为0 private Long id;}@ConfigurationProperties(prefix="person")能够新建一个properties文件,ConfigurationProperties的属性prefix指定properties的配置的前缀,通过location指定properties文件的地位 @ConfigurationProperties(prefix="person")public class PersonProperties { private String name ; private int age;}@EnableConfigurationProperties用 @EnableConfigurationProperties注解使 @ConfigurationProperties失效,并从IOC容器中获取bean博客地址:https://blog.csdn.net/u010502...@RestController组合@Controller和@ResponseBody,当你开发一个和页面交互数据的管制时,比方bbs-web的api接口须要此注解@RequestMapping("/api2/copper")用来映射web申请(拜访门路和参数)、解决类和办法,能够注解在类或办法上。注解在办法上的门路会继承注解在类上的门路。produces属性: 定制返回的response的媒体类型和字符集,或需返回值是json对象 @RequestMapping(value="/api2/copper",produces="application/json;charset=UTF-8",method = RequestMethod.POST)@RequestParam获取request申请的参数值 public List<CopperVO> getOpList(HttpServletRequest request, @RequestParam(value = "pageIndex", required = false) Integer pageIndex, @RequestParam(value = "pageSize", required = false) Integer pageSize) { }@ResponseBody反对将返回值放在response体内,而不是返回一个页面。比方Ajax接口,能够用此注解返回数据而不是页面。此注解能够搁置在返回值前或办法前。 ...

August 13, 2021 · 2 min · jiezi

关于springboot:技术型产品体系

产品体系技术体系;微服务架构spring-boot 2.5.1、spring-cloud2020.0.31、已实现跨数据库同步Oracle、MySQL2、多数据源设置3、在线报表模板、数据获取绘制4、重要一步电子签章5、内置域设计(设计中)

August 11, 2021 · 1 min · jiezi

关于springboot:Spring-Boot参数校验以及分组校验的使用

简介: 做web开发基本上每个接口都要对参数进行校验,如果参数比拟少,还比拟容易解决,一但参数比拟多了的话代码中就会呈现大量的if-else语句。尽管这种形式简略间接,但会大大降低开发效率和代码可读性。所以咱们能够应用validator组件来代替咱们进行不必要的coding操作。本文将基于validator的介绍材料,同时联合作者本人在我的项目中的理论应用教训进行了总结。 作者 | 江岩起源 | 阿里技术公众号 一 前言做web开发有一点很烦人就是要对前端输出参数进行校验,基本上每个接口都要对参数进行校验,比方一些非空校验、格局校验等。如果参数比拟少的话还是容易解决的一但参数比拟多了的话代码中就会呈现大量的if-else语句。 应用这种形式尽管简略间接,然而也有不好的中央,一是升高了开发效率,因为咱们须要校验的参数会存在很多中央,并且不同中央会有反复校验,其次升高了代码可读性,因为在业务代码中掺杂了太多额定工作的代码。 所以咱们能够应用validator组件来代替咱们进行不必要的coding操作。本文基于validator的介绍材料,也联合本人在我的项目中的理论应用教训进行了总结,心愿能帮到大家。 1 什么是validatorBean Validation是Java定义的一套基于注解的数据校验标准,目前曾经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0实现于2017.08),曾经经验了三个版本 。须要留神的是,JSR只是一项规范,它规定了一些校验注解的标准,但没有实现,比方@Null、@NotNull、@Pattern等,它们位于 javax.validation.constraints这个包下。而hibernate validator是对这个标准的实现,并减少了一些其余校验注解,如 @NotBlank、@NotEmpty、@Length等,它们位于org.hibernate.validator.constraints这个包下。 如果咱们的我的项目应用了Spring Boot,hibernate validator框架曾经集成在 spring-boot-starter-web中,所以无需再增加其余依赖。如果不是Spring Boot我的项目,须要增加如下依赖。 二 注解介绍1 validator内置注解 hibernate validator中扩大定义了如下注解: 三 应用应用起来比较简单,都是应用注解形式应用。具体来说分为单参数校验、对象参数校验,单参数校验就是controller接口依照单参数接管前端传值,没有封装对象进行接管,如果有封装对象那就是对象参数校验。 1 单参数校验单参数校验只须要在参数前增加注解即可,如下所示: public Result deleteUser(@NotNull(message = "id不能为空") Long id) { // do something}但有一点须要留神,如果应用单参数校验,controller类上必须增加 @Validated注解,如下所示:@RestController@RequestMapping("/user")@Validated // 单参数校验须要加的注解public class UserController { // do something}2 对象参数校验对象参数校验应用时,须要先在对象的校验属性上增加注解,而后在Controller办法的对象参数前增加@Validated 注解,如下所示: public Result addUser(@Validated UserAO userAo) { // do something}public class UserAO { @NotBlank private String name; @NotNull private Integer age; ……}注解分组 ...

August 11, 2021 · 2 min · jiezi

关于springboot:SpringBoot系列-Mybatis-之自定义类型转换-TypeHandler

SpringBoot系列 Mybatis 之自定义类型转换 TypeHandler在应用 mybatis 进行 db 操作的时候,咱们常常会干的一件事件就是将 db 中字段映射到 java bean,通常咱们应用ResultMap来实现映射,通过这个标签能够指定两者的绑定关系,那么如果 java bean 中的字段类型与 db 中的不一样,应该怎么解决呢? 如 db 中为 timestamp, 而 java bean 中定义的却是 long 通过BaseTypeHandler来实现自定义的类型转换<!-- more --> I. 环境筹备1. 数据库筹备应用 mysql 作为本文的实例数据库,新增一张表 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创立工夫', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新工夫', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 我的项目环境本文借助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发 ...

August 9, 2021 · 4 min · jiezi

关于springboot:springboot-中-inputStream-神秘消失之谜

序言最近小明接手了前共事的代码,意料之外、情理之中的遇到了坑。 为了防止掉入同一个坑两次,小明决定把这个坑记下来,并在坑前立一个大牌子,防止其余小伙伴掉进去。 HTTPClient 模仿调用为了把这个问题阐明,咱们首先从最简略的 http 调用说起。 设置 body服务端服务端的代码如下: @Controller@RequestMapping("/")public class ReqController { @PostMapping(value = "/body") @ResponseBody public String body(HttpServletRequest httpServletRequest) { try { String body = StreamUtil.toString(httpServletRequest.getInputStream()); System.out.println("申请的 body: " + body); // 从参数中获取 return body; } catch (IOException e) { e.printStackTrace(); return e.getMessage(); } }}java 客户端要如何申请能力让服务端读取到传递的 body 呢? 客户端这个问题肯定难不到你,实现的形式有很多种。 咱们以 apache httpclient 为例: //post申请,带汇合参数public static String post(String url, String body) { try { // 通过HttpPost来发送post申请 HttpPost httpPost = new HttpPost(url); StringEntity stringEntity = new StringEntity(body); // 通过setEntity 将咱们的entity对象传递过来 httpPost.setEntity(stringEntity); return execute(httpPost); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); }}//执行申请返回响应数据private static String execute(HttpRequestBase http) { try { CloseableHttpClient client = HttpClients.createDefault(); // 通过client调用execute办法 CloseableHttpResponse Response = client.execute(http); //获取响应数据 HttpEntity entity = Response.getEntity(); //将数据转换成字符串 String str = EntityUtils.toString(entity, "UTF-8"); //敞开 Response.close(); return str; } catch (IOException e) { throw new RuntimeException(e); }}能够发现 httpclient 封装之后还是十分不便的。 ...

August 6, 2021 · 5 min · jiezi

关于springboot:Spring-Boot-简单整合-fluentmybatis-实现数据的增删改查

总是喜爱去关注更多的新框架,发现了一个基本上不必写mapper和xml的框架。让咱们来钻研一下这个框架吧。1. 新建Spring Boot我的项目1.1 pom.xml配置<properties> <java.version>1.8</java.version> <fluent-mybatis.version>1.6.13</fluent-mybatis.version></properties><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- 引入fluent-mybatis 运行依赖包, scope为compile --> <dependency> <groupId>com.github.atool</groupId> <artifactId>fluent-mybatis</artifactId> <version>${fluent-mybatis.version}</version> </dependency> <!-- 引入fluent-mybatis-processor, scope设置为provider 编译须要,运行时不须要 --> <dependency> <groupId>com.github.atool</groupId> <artifactId>fluent-mybatis-processor</artifactId> <version>${fluent-mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency></dependencies>咱们这里引入了fluent-mybatis-processor就是想主动生成代码,尽量减少咱们本人写代码。 1.2 application.yml配置server: port: 8080 # 端口号spring: datasource: # 数据库参数配置 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://IP:3306/数据库名称?useUnicode=true&characterEncoding=utf8 username: 用户名 password: 明码1.3 EntityGeneratorTests.java 主动代码生成这个我尝试过在非Test外面用main外面运行,后果总是失败,有能够运行的请通知我哈! ...

August 6, 2021 · 2 min · jiezi

关于springboot:micaauto-213-发布-idea-增量编译-bug-修复

一、简介mica-auto (Spring boot stater开发利器)用来生成 Spring boot starter 的一些根底配置,是 Spring cloud 微服务框架 Mica 中的一个根底组件, 二、性能生成 spring.factories。生成 spring-devtools.properties生成 FeignClient 到 spring.factories 中,供 mica-cloud 中实现 Feign 自动化配置。生成 java Spi 配置,须要增加 @AutoService 注解。注解spring.factories 或 Spi key@AutoContextInitializerApplicationContextInitializer@AutoListenerApplicationListener@AutoRunListenerSpringApplicationRunListener@AutoEnvPostProcessorEnvironmentPostProcessor@AutoFailureAnalyzerFailureAnalyzer@AutoConfigImportFilterAutoConfigurationImportFilter@AutoTemplateProviderTemplateAvailabilityProvider@AutoEnableCircuitBreakerEnableCircuitBreaker@AutoConfigDataLocationResolverConfigDataLocationResolver@AutoConfigDataLoaderConfigDataLoader@AutoDatabaseInitializerDetectorDatabaseInitializerDetector@AutoDependsOnDatabaseInitializationDetectorDependsOnDatabaseInitializationDetector@ComponentEnableAutoConfiguration@AutoIgnore疏忽,不生成到 spring.factories@AutoServicejava Spi 生成配置三、更新记录✨ 代码优化,优化流敞开。✨ 优化 github actions。 修复 spi,去除正文。 修复 gitee #I4193Q idea 增量编译 bug。 修复 spring-devtools.properties 匹配 bug。四、应用留神: 如果你我的项目中应用了 Lombok 请将 mica-auto 的依赖搁置到 Lombok 前面。 4.1 maven <dependency> <groupId>net.dreamlu</groupId> <artifactId>mica-auto</artifactId> <version>2.1.3</version> <scope>provided</scope></dependency>4.2 gradle >= 5.x annotationProcessor("net.dreamlu:mica-auto:2.1.3")4.3 gradle < 5.x ...

August 6, 2021 · 1 min · jiezi

关于springboot:Spring-Boot-中实现跨域的-5-种方式你一定要知道

送大家以下java学习材料,文末有支付形式 一、为什么会呈现跨域问题出于浏览器的同源策略限度。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最外围也最根本的平安性能,如果短少了同源策略,则浏览器的失常性能可能都会受到影响。能够说Web是构建在同源策略根底之上的,浏览器只是针对同源策略的一种实现。 同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具备雷同的协定(protocol),主机(host)和端口号(port) 二、什么是跨域当一个申请url的协定、域名、端口三者之间任意一个与以后页面url不同即为跨域 三、非同源限度【1】无奈读取非同源网页的 Cookie、LocalStorage 和 IndexedDB 【2】无奈接触非同源网页的 DOM 【3】无奈向非同源地址发送 AJAX 申请 四、java 后端 实现 CORS 跨域申请的形式对于 CORS的跨域申请,次要有以下几种形式可供选择: 返回新的CorsFilter重写 WebMvcConfigurer应用注解 @CrossOrigin手动设置响应头 (HttpServletResponse)自定web filter 实现跨域留神: CorFilter / WebMvConfigurer / @CrossOrigin 须要 SpringMVC 4.2以上版本才反对,对应springBoot 1.3版本以上上背后两种形式属于全局 CORS 配置,后两种属于部分 CORS配置。如果应用了部分跨域是会笼罩全局跨域的规定,所以能够通过 @CrossOrigin 注解来进行细粒度更高的跨域资源管制。其实无论哪种计划,最终目标都是批改响应头,向响应头中增加浏览器所要求的数据,进而实现跨域1.返回新的 CorsFilter(全局跨域)在任意配置类,返回一个 新的 CorsFIlter Bean ,并增加映射门路和具体的CORS配置门路。 `@Configuration``public class GlobalCorsConfig {` `@Bean` `public CorsFilter corsFilter() {` `//1. 增加 CORS配置信息` `CorsConfiguration config = new CorsConfiguration();` `//放行哪些原始域` `config.addAllowedOrigin("*");` `//是否发送 Cookie` `config.setAllowCredentials(true);` `//放行哪些申请形式` `config.addAllowedMethod("*");` `//放行哪些原始申请头部信息` `config.addAllowedHeader("*");` `//裸露哪些头部信息` `config.addExposedHeader("*");` `//2. 增加映射门路` `UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();` `corsConfigurationSource.registerCorsConfiguration("/**",config);` `//3. 返回新的CorsFilter` `return new CorsFilter(corsConfigurationSource);` `}``}`2. 重写 WebMvcConfigurer(全局跨域)`@Configuration``public class CorsConfig implements WebMvcConfigurer {` `@Override` `public void addCorsMappings(CorsRegistry registry) {` `registry.addMapping("/**")` `//是否发送Cookie` `.allowCredentials(true)` `//放行哪些原始域` `.allowedOrigins("*")` `.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})` `.allowedHeaders("*")` `.exposedHeaders("*");` `}``}`3. 应用注解 (部分跨域)在控制器(类上)上应用注解 @CrossOrigin:,示意该类的所有办法容许跨域。 ...

August 5, 2021 · 1 min · jiezi

关于springboot:SpringCloud升级之路20200x版5所有项目的parent与springframeworkcommon说明

本系列代码地址:https://github.com/HashZhang/... 源代码文件:https://github.com/HashZhang/...1. 应用 log4j2 异步日志所须要的依赖:须要排除默认的日志实现 logback,减少 log4j2 的依赖,并且增加 log4j2 异步日志须要的 disruptor 依赖。 <!--日志须要用log4j2--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId></dependency><!--log4j2异步日志须要的依赖,所有我的项目都必须用log4j2和异步日志配置--><dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>${disruptor.version}</version></dependency>2. javax.xml 的相干依赖。咱们的我的项目应用 JDK 11。JDK 9 之后的模块化个性导致 javax.xml 不主动加载,所以须要如下模块: <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>${jaxb.version}</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>${jaxb.version}</version></dependency><dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>${jaxb.version}</version></dependency><dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-xjc</artifactId> <version>${jaxb.version}</version></dependency><dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>${activation.version}</version></dependency>3. 应用 Junit 5 进行单元测试,Junit 5 应用能够参考:Junit5 user guide <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope></dependency>4. 应用 Spring Boot 单元测试,能够参考:features.testing <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope></dependency>5. mockito扩大,次要是须要mock final类:Spring Boot 单元测试曾经蕴含了 mockito 依赖了,然而咱们还须要 Mock final 类,所以增加以下依赖: ...

August 5, 2021 · 2 min · jiezi

关于springboot:springboot下的Excel到出

实体类注解@ContentRowHeight(int): 设置 row 高度,不蕴含表头 标记在 类上@HeadRowHeight(int): 设置 表头 高度(与 @ContentRowHeight 相同) 标记在 类上@ColumnWidth(int): 设置列宽 标记在属性上@ExcelProperty(value = String[], index = int): 设置表头信息 value: 表名称 index: 列号 ##### 少用 @DateTimeFormat: @NumberFormat: BrowsingExcelHistory browsingExcelHistory = new BrowsingExcelHistory(); @Overridepublic WGJsonResult userFeedBackExport(UserFeedBack param, HttpServletResponse response) throws IOException { List<UserFeedBack> userFeedBackLists = userFeedBackMapper.getList(param); response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); SimpleDateFormat sdf4 = new SimpleDateFormat("yyyy_MM_dd_hh_mm_ss"); String nowDate = sdf4.format(new Date()); String fileName = nowDate+ ".xlsx"; response.setHeader("Content-disposition", "attachment;filename=" + fileName ); EasyExcel.write(response.getOutputStream(), UserFeedBack.class).sheet("用户反馈记录").doWrite(userFeedBackLists); return success("下载胜利");

August 4, 2021 · 1 min · jiezi

关于springboot:记一次springbootmybaits配置遇到的问题

Spring+mybaits配置遇到的问题2021年,八月,在学习springboot+mybatis的时候遇到了一些问题: 1、spring启动谬误刚开始,在配置springboot+mybaits的时候,依据网络上的博客,配置application.yml,配置UserMapper等文件 application.ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/t_blog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456mybatis: type-aliases-package: com.mybatislearn.mockingjay.entity mapper-locations: classpath:mapper/*.xmlserver: port: 8080UserMapper.xml<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mybatislearn.mockingjay.mapper.UserMapper"> <resultMap id="UserMap" type="com.mybatislearn.mockingjay.entity.User" > <id column="id" property="id" /> <result column="avatar" property="avatar" /> <result column="createTime" property="createTime" /> <result column="email" property="email" /> <result column="nickname" property="nickname" /> <result column="password" property="password" /> <result column="type" property="type" /> <result column="updateTime" property="updateTime" /> <result column="username" property="username" /> </resultMap> <!-- 依据用户名查问用户--> <select id="loadUserByUsername" resultMap="UserMap"> select * from t_user where username=#{username} </select></mapper>配置一个mapper接口UserMapper.java package com.mybatislearn.mockingjay.mapper;import com.mybatislearn.mockingjay.entity.User;public interface UserMapper { User loadUserByUsername(String username);}同时我的项目内也是MVC构造的: serviceUserService.java package com.mybatislearn.mockingjay.service;import com.mybatislearn.mockingjay.entity.User;public interface UserService { User queryUserByname(String name);}UserServiceImpl.java ...

August 3, 2021 · 3 min · jiezi

关于springboot:分布式缓存redis看这一篇就够了

redis是什么?简略来说redis是一个开源的应用ANSI C语言编写的内存数据库,应用key-value的数据结构。反对数据长久化到硬盘,反对多种api语言的连贯。通常当做缓存解决。装置redisredis官网:https://redis.io 英语好的看官网,下载linux版本redisredis for windows: https://github.com/MicrosoftArchive/redis/releases ,windows版下载到本地间接装置即可应用。 windows版本下载安装实现之后,在services.msc外面能看到redis服务。 springboot集成redis咱们应用redisson框架集成,新建工程,pom中增加如下依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency><!-- https://mvnrepository.com/artifact/org.redisson/redisson --><dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.15.0</version></dependency>批改application.yml文件,增加redis的配置 spring: redisson: address: redis://127.0.0.1:6379 #redis的地址和端口,默认端口是6379 password: #默认没有交易 database: 0 #Redis默认状况下有16个分片,这里配置具体应用的分片,默认是0 timeout: 10000 #连贯超时工夫(毫秒)增加配置类,映射yml文件中的属性 @Getter@Setter@Configuration@ConfigurationProperties(prefix = "spring.redisson")//获取yml文件中以spring.redisson结尾的所有数据public class RedissonProperties { //映射yml文件外面的数据 private String address; private String password = null; private int database = 0; private int timeout = 3000;}减少RedissonClient初始化类: @Configuration@ConditionalOnClass(RedissonClient.class)//存在RedissonClient才创立该类@ConditionalOnProperty({"spring.redisson.address"})//存在spring.redisson.address配置才创立该类public class RedissonConfig { @Resource private RedissonProperties properties; @Bean(destroyMethod = "shutdown") RedissonClient redissonClient(RedissonProperties properties) throws Exception { Config config = new Config(); SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig .setAddress(properties.getAddress()) .setDatabase(properties.getDatabase()) .setTimeout(properties.getTimeout()); //明码不为空才设置,否则默认yml注入的空串会导致创立redisson不胜利 if(!StringUtils.isEmpty(properties.getPassword())){ singleServerConfig.setPassword(properties.getPassword()); } return Redisson.create(config); }}好了,自此,筹备工作都做好了,上面就能够编码操作redis的数据了。 ...

August 3, 2021 · 2 min · jiezi

关于springboot:SpringBoot整合DubboZookeeper

Zookeeper在linux下的装置去官网下载Zookeeper压缩包,解压到服务器的任意目录下。进入conf文件夹,有一个zoo_sample.cfg的文件,关上编辑进行编辑: # The number of milliseconds of each ticktickTime=2000# The number of ticks that the initial # synchronization phase can takeinitLimit=10# The number of ticks that can pass between # sending a request and getting an acknowledgementsyncLimit=5# the directory where the snapshot is stored.# do not use /tmp for storage, /tmp here is just # example sakes.dataDir=/tmp/zookeeper# the port at which the clients will connectclientPort=2181# the maximum number of client connections.# increase this if you need to handle more clients#maxClientCnxns=60## Be sure to read the maintenance section of the # administrator guide before turning on autopurge.## http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance## The number of snapshots to retain in dataDir#autopurge.snapRetainCount=3# Purge task interval in hours# Set to "0" to disable auto purge feature#autopurge.purgeInterval=1## Metrics Providers## https://prometheus.io Metrics Exporter#metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider#metricsProvider.httpPort=7000#metricsProvider.exportJvmInfo=true#开启四字命令4lw.commands.whitelist=*其中dataDir能够设置为本人制订的目录,用来寄存快照信息,同时能够增加dataLogDir来配置日志寄存的门路,默认与dataDir同一地位。最初记得开启四字命令配置,不便查看服务器状态。最初一步,将编辑好的文件改名或复制一份叫zoo.cfg的文件作为配置文件。(留神这是单机版的配置,非集群) ...

August 1, 2021 · 2 min · jiezi

关于springboot:微服务分布式协作框架tridenter使用介绍

tridenter是一款基于SpringBoot框架开发的微服务分布式合作框架,它能够使多个独立的SpringBoot利用轻松快捷地组成一个集群,而不依赖内部的注册核心(比方SpringCloud Eureka等)。tridenter框架首先提供了丰盛的分布式应用集群治理API和工具,同时也提供了一套残缺的微服务治理性能 tridenter的个性: 采纳去中心化的思维治理集群反对集群间的音讯多播和单播反对各种负载平衡策略反对多种Leader选举算法提供过程池/调度过程池的实现内置注册核心内置多种微服务间调用的限流降级策略内置微服务Rest客户端内置HTTP服务网关集群状态监控和告警集群中的音讯多播是tridenter十分重要的性能,tridenter底层是通过Redis(PubSub)实现多播性能从而实现利用的互相发现的,进而组成利用集群的。集群中的每个成员都反对音讯多播和单播的能力。利用tridenter反对音讯单播的能力,tridenter提供了Leader选举算法接口,内置了两种Leader选举算法,疾速Leader选举算法(基于Redis队列)和一致性选举算法(基于Paxos算法)同时,利用tridenter反对音讯单播的能力,tridenter提供了过程池,实现了跨过程的办法调用和办法分片的能力另一方面,tridenter自身也提供了微服务治理的基本功能:tridenter自带注册核心,而利用音讯多播的原理,利用是互相发现,互相注册的,所以集群中的每个成员都有一份全量的成员列表,即每个利用都是注册核心,体现了去中心化的设计思维。每个成员通过命名服务,实现了利用之间HTTP接口相互调用的能力,并提供了相干各种注解和Restful配置将服务公布方和生产方解耦tridenter自带网关性能,能够将利用独立公布成网关服务,可代理散发HTTP申请和下载工作(暂不反对上传)tridenter还内置了多种负载平衡算法和限流降级策略,用户也能够自定义负载平衡算法或降级策略tridenter实现了spring actuator的健康检查接口,除了监控集群状态,还自带接口的统计分析等性能,初步实现了接口的对立治理和监控所以,基于tridenter框架,咱们也能够搭建一套相似于Spring Cloud的微服务体系 tridenter采纳了去中心化的设计思维,即开发人员不须要晓得以后哪个是主节点,哪些节点是从节点,更不应该显式地定义某个利用为主节点,这是由tridenter采纳的Leader选举算法决定的,默认的选举算法是疾速选举算法。依据选举算法,集群内的任意一个利用节点都有可能成为主节点,默认第一个启动的利用就是主节点,然而如果采纳的是一致性选举算法,可能就会不一样。依据作者形容,一致性选举算法目前不稳固,举荐在利用中应用疾速选举算法。 Maven: <dependency> <groupId>com.github.paganini2008.atlantis</groupId> <artifactId>tridenter-spring-boot-starter</artifactId> <version>1.0-RC2</version> </dependency>tridenter的基本功能就是让不同的Spring Boot利用变成集群模式,所以配置的时候,咱们要在application.properties 定义两个零碎变量,否则会报错 spring.application.name=chaconne-management # 服务名称spring.application.cluster.name=chaconne-management-cluster #集群名称启动之后,如果在Console看到以下信息则示意集群配置失效 2021-06-05 18:20:11 [INFO ] io.undertow - starting server: Undertow - 2.0.29.Final2021-06-05 18:20:11 [INFO ] org.xnio - XNIO version 3.3.8.Final2021-06-05 18:20:11 [INFO ] org.xnio.nio - XNIO NIO Implementation Version 3.3.8.Final2021-06-05 18:20:11 [INFO ] i.a.f.t.m.ApplicationMulticastGroup - Registered candidate: {applicationContextPath: http://192.168.159.1:6543, applicationName: chaconne-management, clusterName: chaconne-management-cluster, id: fafdc9ada3a5d1de3836b1a0ba4ef174, leader: false, startTime: 1622888405582, weight: 1}, Proportion: 1/12021-06-05 18:20:11 [INFO ] i.a.f.t.m.ApplicationRegistryCenter - Register application: [{applicationContextPath: http://192.168.159.1:6543, applicationName: chaconne-management, clusterName: chaconne-management-cluster, id: fafdc9ada3a5d1de3836b1a0ba4ef174, leader: false, startTime: 1622888405582, weight: 1}] to ApplicationRegistryCenter2021-06-05 18:20:11 [INFO ] i.a.f.c.SerialDependencyListener - SerialDependencyHandler initialize successfully.2021-06-05 18:20:11 [INFO ] i.a.f.t.e.ApplicationLeaderElection - This is the leader of application cluster 'chaconne-management-cluster'. Current application event type is 'indi.atlantis.framework.tridenter.election.ApplicationClusterLeaderEvent'2021-06-05 18:20:11 [INFO ] i.a.f.t.e.ApplicationLeaderElection - Current leader: {applicationContextPath: http://192.168.159.1:6543, applicationName: chaconne-management, clusterName: chaconne-management-cluster, id: fafdc9ada3a5d1de3836b1a0ba4ef174, leader: true, startTime: 1622888405582, weight: 1}2021-06-05 18:20:11 [INFO ] o.s.b.w.e.u.UndertowServletWebServer - Undertow started on port(s) 6543 (http) with context path ''2021-06-05 18:20:12 [INFO ] i.a.f.c.m.ChaconneManagementMain - Started ChaconneManagementMain in 12.134 seconds (JVM running for 12.829)首先: ...

August 1, 2021 · 3 min · jiezi

关于springboot:Springboot整合mybatisPlus注解版

Springboot整合mybatis-Plus(注解版)

July 31, 2021 · 1 min · jiezi

关于springboot:分布式任务调度系统chaconne介绍

chaconne(音译夏空),是一款用Java写的,基于SpringBoot框架的轻量级的分布式任务调度框架。引入chaconne相干组件,能够十分快捷地帮忙你搭建一个分布式工作集群 chaconne 个性列表完满反对spring-boot框架(2.2.0+)反对多种形式设置的定时工作(Cron表达式,参数设置等)反对动静保留和删除工作反对注解配置定时工作反对两种集群模式(主备模式和负载平衡模式)内置多种负载平衡算法,反对自定义负载平衡算法反对失败重试和失败转移反对日志追踪反对工作参数分片解决反对工作依赖(串行依赖和并行依赖)反对DAG模仿工作流反对工作自定义终止策略反对工作超时冷却和重置反对邮件告警chaconne两种集群部署模式:去中心化部署模式 没有固定的调度核心节点,集群会选举其中一个利用作为Leader, 进行工作指挥调度中心化部署模式 分为调度核心和工作执行节点两个角色,且调度核心和工作执行节点都反对集群模式阐明:这里的集群是指参加工作执行的利用所组成的集群(chaconne集群),它和基于SpringCloud框架组成的集群是两个独立的概念如果chaconne集群规模较小,举荐应用去中心化部署模式,若该集群规模较大,根据理论状况,两者模式都能够应用。chaconne框架由两局部组成:chaconne-spring-boot-starter 外围jar包,蕴含了chaconne全副外围性能chaconne-console Chaconne Web治理界面,进行工作治理和查看工作运行状态Maven: <dependency> <groupId>com.github.paganini2008.atlantis</groupId> <artifactId>chaconne-spring-boot-starter</artifactId> <version>1.0-RC1</version></dependency>兼容性:Jdk 1.8+ chaconne实现原理简述chaconne的底层是依赖tridenter-spring-boot-starter组件来实现工作集群模式的(主备模式和负载平衡模式),利用音讯单播机制(通过Redis PubSub模仿)来实现工作散发和负载平衡,分片解决等高级个性。须要指出的是,chaconne框架中对于集群的定义和tridenter对于集群的定义是统一的,对于集群的概念,等同于用来区别不同的产品组或公司,同时chaconne也反对工作组的概念,它是可选配置,默认状况下,组名就是以后利用名称(${spring.application.name}),即当起了多个雷同利用名的利用,那这些利用就成为了一个工作组。chaconne不仅反对跨组的工作调用,更反对跨集群的工作调用。 如何定义工作?应用注解@ChacJob继承ManagedJob类实现Job接口实现NotManagedJob接口阐明:前3种定义工作的形式属于编程式定义工作,即通过代码形式定义一个工作,通过Spring框架启动并主动加载第4种定义的形式用来定义动静工作,即用户在Web界面上(Chaconne-Console)提交来创立工作或间接通过调用HTTP API创立工作,不同的是,该工作对象不属于Spring上下文托管的Bean。示例代码: 注解形式创立工作@ChacJob@ChacTrigger(cron = "*/5 * * * * ?")public class DemoCronJob { @Run public Object execute(JobKey jobKey, Object attachment, Logger log) throws Exception { log.info("DemoCronJob is running at: {}", DateUtils.format(System.currentTimeMillis())); return RandomUtils.randomLong(1000000L, 1000000000L); } @OnSuccess public void onSuccess(JobKey jobKey, Object result, Logger log) { log.info("DemoCronJob's return value is: {}", result); } @OnFailure public void onFailure(JobKey jobKey, Throwable e, Logger log) { log.error("DemoCronJob is failed by cause: {}", e.getMessage(), e); }}接口方式创立工作@Componentpublic class HealthCheckJob extends ManagedJob { @Override public Object execute(JobKey jobKey, Object arg, Logger log) { log.info(info()); return UUID.randomUUID().toString(); } @Override public Trigger getTrigger() { return GenericTrigger.Builder.newTrigger("*/5 * * * * ?").setStartDate(DateUtils.addSeconds(new Date(), 30)).build(); } private String info() { long totalMemory = Runtime.getRuntime().totalMemory(); long usedMemory = totalMemory - Runtime.getRuntime().freeMemory(); return FileUtils.formatSize(usedMemory) + "/" + FileUtils.formatSize(totalMemory); } @Override public long getTimeout() { return 60L * 1000; }}动静工作:public class EtlJob implements NotManagedJob { @Override public Object execute(JobKey jobKey, Object attachment, Logger log) { log.info("JobKey:{}, Parameter: {}", jobKey, attachment); return null; }}工作依赖工作依赖是chaconne框架的重要个性之一,工作依赖分为串行依赖和并行依赖,所谓串行依赖是指工作A做完接着执行工作B, 即工作B依赖工作A。并行依赖是指,比方有3个工作,别离为工作A, 工作B, 工作C, 工作A和工作B都做完能力执行工作C, 相似会签的业务场景。串行依赖和并行依赖都能够共享工作上下文参数和运行后果,并且反对自定义判断策略来决定要不要触发上游工作。 ...

July 24, 2021 · 5 min · jiezi

关于springboot:详解SptingBoot参数校验机制使用校验不再混乱

前言Spring Validation 验证框架提供了十分便当的参数验证性能,只须要@Validated或者@Valid以及一些规定注解即可校验参数。 自己看网上很多 SpringBoot 参数校验教程以 "单个参数校验" 和 "实体类参数校验" 这两个角度来分类(或者"Get 办法"和"Post 办法"分类,实际上也是一样的,甚至这种更容易让人产生误解)。这种分类很容易让人感觉凌乱:注解 @Validated一会要标在类下面,一会又要标在参数前;异样又要解决BindException,又要解决ConstraintViolationException。刚看的时候可能还记得住,过一段时间就容易记混了,特地是当两种形式同时在一个类里,就不记得到底怎么用,最初可能罗唆全副都加上@Validated注解了。 本文就从校验机制的角度进行分类,SpringBoot 的参数校验有两套机制,执行的时候会同时被两套机制管制。 两套机制除了管制各自的部份外,有局部是重叠的,这部分又会波及优先级之类的问题。 然而只有晓得了两个机制是什么,且理解Spring 流程,就再也不会搞混了。 校验机制这两套校验机制,第一种由 SpringMVC 管制。这种校验只能在"Controller"层应用,须要在被校验的对象前标注@Valid,@Validated,或者自定义的名称以'Valid'结尾的注解,如: @Slfj@RestController@RequestMappingpublic class ValidController { @GetMapping("get1") public void get1(@Validated ValidParam param) { log.info("param: {}", param); }}@Datapublic class ValidParam { @NotEmpty private String name; @Min(1) private int age;}另一种被 AOP 管制。这种只有是 Spring 治理的 Bean 就能失效,所以"Controller","Service","Dao"层等都能够用这种参数校验。 须要在被校验的类上标注@Validated注解,而后如果校验单个类型的参数,间接在参数前标注@NotEmpty之类的校验规定注解;如果校验对象,则在对象前标注@Valid注解(这里只能用@Valid,其余都无奈失效,起因前面阐明),如: import javax.validation.constraints.Max;@Slf4j@Validated@RestController@RequestMappingpublic class ValidController { /** * 校验对象 */ @GetMapping("get2") public void get2(@Valid ValidParam param) { log.info("param: {}", param); } /** * 校验参数 */ @GetMapping("get3") public void get3(@NotEmpty String name, @Max(1) int age) { log.info("name: {}, age: {}", name, age); }}@Datapublic class ValidParam { @NotEmpty private String name; @Min(1) private int age;}SpringMVC 校验机制详解首先大抵理解一下 SpringMVC 执行流程: ...

July 21, 2021 · 5 min · jiezi

关于springboot:SpringBoot-如何统一后端返回格式老鸟们都是这样玩的

大家好,我是飘渺。 明天咱们来聊一聊在基于SpringBoot前后端拆散开发模式下,如何敌对的返回对立的规范格局以及如何优雅的解决全局异样。 首先咱们来看看为什么要返回对立的规范格局? 为什么要对SpringBoot返回对立的规范格局在默认状况下,SpringBoot的返回格局常见的有三种: 第一种:返回 String @GetMapping("/hello")public String getStr(){ return "hello,javadaily";}此时调用接口获取到的返回值是这样: hello,javadaily第二种:返回自定义对象 @GetMapping("/aniaml")public Aniaml getAniaml(){ Aniaml aniaml = new Aniaml(1,"pig"); return aniaml;}此时调用接口获取到的返回值是这样: { "id": 1, "name": "pig"}第三种:接口异样 @GetMapping("/error")public int error(){ int i = 9/0; return i;}此时调用接口获取到的返回值是这样: { "timestamp": "2021-07-08T08:05:15.423+00:00", "status": 500, "error": "Internal Server Error", "path": "/wrong"}基于以上种种状况,如果你和前端开发人员联调接口她们就会很懵逼,因为咱们没有给他一个对立的格局,前端人员不晓得如何解决返回值。 还有甚者,有的同学比方小张喜爱对后果进行封装,他应用了Result对象,小王也喜爱对后果进行包装,然而他却应用的是Response对象,当呈现这种状况时我置信前端人员肯定会抓狂的。 所以咱们我的项目中是须要定义一个对立的规范返回格局的。 定义返回规范格局一个规范的返回格局至多蕴含3局部: status 状态值:由后端对立定义各种返回后果的状态码message 形容:本次接口调用的后果形容data 数据:本次返回的数据。{ "status":"100", "message":"操作胜利", "data":"hello,javadaily"}当然也能够按需退出其余扩大值,比方咱们就在返回对象中增加了接口调用工夫 timestamp: 接口调用工夫定义返回对象@Datapublic class ResultData<T> { /** 后果状态 ,具体状态码参见ResultData.java*/ private int status; private String message; private T data; private long timestamp ; public ResultData (){ this.timestamp = System.currentTimeMillis(); } public static <T> ResultData<T> success(T data) { ResultData<T> resultData = new ResultData<>(); resultData.setStatus(ReturnCode.RC100.getCode()); resultData.setMessage(ReturnCode.RC100.getMessage()); resultData.setData(data); return resultData; } public static <T> ResultData<T> fail(int code, String message) { ResultData<T> resultData = new ResultData<>(); resultData.setStatus(code); resultData.setMessage(message); return resultData; }}定义状态码public enum ReturnCode { /**操作胜利**/ RC100(100,"操作胜利"), /**操作失败**/ RC999(999,"操作失败"), /**服务限流**/ RC200(200,"服务开启限流爱护,请稍后再试!"), /**服务降级**/ RC201(201,"服务开启降级爱护,请稍后再试!"), /**热点参数限流**/ RC202(202,"热点参数限流,请稍后再试!"), /**零碎规定不满足**/ RC203(203,"零碎规定不满足要求,请稍后再试!"), /**受权规定不通过**/ RC204(204,"受权规定不通过,请稍后再试!"), /**access_denied**/ RC403(403,"无拜访权限,请分割管理员授予权限"), /**access_denied**/ RC401(401,"匿名用户拜访无权限资源时的异样"), /**服务异样**/ RC500(500,"零碎异样,请稍后重试"), INVALID_TOKEN(2001,"拜访令牌不非法"), ACCESS_DENIED(2003,"没有权限拜访该资源"), CLIENT_AUTHENTICATION_FAILED(1001,"客户端认证失败"), USERNAME_OR_PASSWORD_ERROR(1002,"用户名或明码谬误"), UNSUPPORTED_GRANT_TYPE(1003, "不反对的认证模式"); /**自定义状态码**/ private final int code; /**自定义形容**/ private final String message; ReturnCode(int code, String message){ this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; }}对立返回格局@GetMapping("/hello")public ResultData<String> getStr(){ return ResultData.success("hello,javadaily");}此时调用接口获取到的返回值是这样: ...

July 20, 2021 · 3 min · jiezi

关于springboot:springboot-实现拦截的-3-种方式介绍及异步执行的思考

springboot 拦挡形式理论我的项目中,咱们常常须要输入申请参数,响应后果,办法耗时,对立的权限校验等。 本文首先为大家介绍 HTTP 申请中三种常见的拦挡实现,并且比拟一下其中的差别。 (1)基于 Aspect 的拦挡形式 (2)基于 HandlerInterceptor 的拦挡形式 (3)基于 ResponseBodyAdvice 的拦挡形式 举荐浏览: 对立日志框架: https://github.com/houbb/auto-log springboot 入门案例为了便于大家学习,咱们首先从最根本的 springboot 例子讲起。 maven 引入引入必须的 jar 包。 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version></parent><dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.10</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.10</version> </dependency></dependencies><!-- Package as an executable jar --><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>启动类实现最简略的启动类。 @SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}定义 Controller为了演示不便,咱们首先实现一个简略的 controller。 ...

July 15, 2021 · 5 min · jiezi

关于springboot:数据处理分析平台一

一、简介RSCMP-HDP数据分析平台蕴含数据源适配、ETL数据处理、数据建模、数据分析、数据填报、工作流一站式数据处理剖析平台。数据获取形式:a、通过文件类型创立多数据源b、连接池多数据源c、接口 二、数据回填1、目前已撑持在线表格数据填报,减少对表格数据、格局的审核性能2、理论的业务流程在工作流中流转体现,在工作流编辑工具中,用图形化的形式将理论的业务过程或步骤形容进去,并转化成标准的工作流定义语言格局。3、数据权限、审核权限设定。 三、数据可视化1、报表统计2、内置域对立XML接口json格局四、门户、机构权限数据连贯设置:1、/** * 性能形容: 增加数据源 * 〈〉 * @Param: [diaSource] * @Return: com.rscmp.hdp.datrans.source.domain.DiaSource * @Author: Steven * @Date: 2021/7/12 18:08 */@Overridepublic void addDiaSource(DiaSource diaSource) { Map<String, String> params = new HashMap<String, String>(); params.put("connectorType",diaSource.getDriveType()); params.put("password",diaSource.getUserPass()); params.put("name",diaSource.getName()); params.put("driverClassName",diaSource.getDriverClassName()); params.put("url",diaSource.getUrl()); params.put("username",diaSource.getUserName()); ConfigModel model = connectorChecker.checkAddConfigModel(params); manager.addConnector(model);}/** * 性能形容: 新增配置 * 〈〉 * @Param: [diaSource] * @Return: com.rscmp.hdp.datrans.source.domain.DiaSource * @Author: Steven * @Date: 2021/7/12 18:08 */@Overridepublic ConfigModel checkAddConfigModel(Map<String, String> params) { String name = params.get(ConfigConstants.CONFIG_MODEL_NAME); String connectorType = params.get("connectorType"); Assert.hasText(name, "connector name is empty."); Assert.hasText(connectorType, "connector connectorType is empty."); Connector connector = new Connector(); connector.setName(name); connector.setType(ConfigConstants.CONNECTOR); setConfig(connector, connectorType); // 配置连接器配置 String type = StringUtils.toLowerCaseFirstOne(connectorType).concat("ConfigChecker"); ConnectorConfigChecker checker = map.get(type); Assert.notNull(checker, "Checker can not be null."); checker.modify(connector, params); // 获取表 setTable(connector); // 批改根本配置 this.modifyConfigModel(connector, params); return connector;}/** * 性能形容: 连贯配置 * 〈〉 * @Param: [diaSource] * @Return: com.rscmp.hdp.datrans.source.domain.DiaSource * @Author: Steven * @Date: 2021/7/12 18:08 */private void setConfig(Connector connector, String connectorType) { Class<ConnectorConfig> configClass = (Class<ConnectorConfig>) ConnectorEnum.getConfigClass(connectorType); Assert.notNull(configClass, String.format("不反对该连接器类型:%s", connectorType)); try { ConnectorConfig config = configClass.newInstance(); config.setConnectorType(connectorType); connector.setConfig(config); } catch (Exception e) { // logger.error(e.getMessage()); throw new BizException("获取连接器配置异样."); }}

July 15, 2021 · 1 min · jiezi

关于springboot:如何启用Spring-Boot-Actuator的所有Endpoints

概述本文将介绍如何启用Spring Boot Actuator的所有Endpoints。首先从maven依赖开始,而后解说如何通过配置文件来管制Endpoint(后称作端点)。最初再学习一下如何确保端点的平安。 其中Spring Boot 1.x和Spring Boot 2.x在Actuator的端点配置上会有肯定的区别。当呈现区别时,会进行提醒。 引入依赖要应用Spring Boot Actuator须要先在我的项目中引入对应的依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> <version>2.4.3</version></dependency>从Spring Boot 2.x开始,如果想通过HTTP的形式进行拜访,还须要引入web starter: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.4.3</version></dependency>启用并裸露端点从Spring Boot 2.x开始,咱们须要手动的去启用和裸露端点。默认状况下,除了/shutdown之外的所有端点都是可用的,同时只有/health和/info端点是对外裸露的。即使咱们为应用程序配置了多个根上下文,所有的端点都能够通过/actuator来进行查找。 这意味着,一旦咱们引入了适合的starter到maven配置中,咱们便能够通过http://localhost:8080/actuator/health和http://localhost:8080/actuator/info来进行两个端点的拜访。 拜访http://localhost:8080/actuator,返回可用的端点列表如下: {"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"health":{"href":"http://localhost:8080/actuator/health","templated":false},"info":{"href":"http://localhost:8080/actuator/info","templated":false}}}裸露所有端点上面通过配置来裸露除了/shutdown之外的所有端点,在application.properties中进行如下配置: management.endpoints.web.exposure.include=*重启,再次拜访/actuator,能够看到除了/shutdown之外的其余所有端点。 {"_links":{"self":{"href":"http://localhost:8080/actuator","templated":false},"beans":{"href":"http://localhost:8080/actuator/beans","templated":false},"caches":{"href":"http://localhost:8080/actuator/caches","templated":false},"health":{"href":"http://localhost:8080/actuator/health","templated":false},"info":{"href":"http://localhost:8080/actuator/info","templated":false},"conditions":{"href":"http://localhost:8080/actuator/conditions","templated":false},"configprops":{"href":"http://localhost:8080/actuator/configprops","templated":false},"env":{"href":"http://localhost:8080/actuator/env","templated":false},"loggers":{"href":"http://localhost:8080/actuator/loggers","templated":false},"heapdump":{"href":"http://localhost:8080/actuator/heapdump","templated":false},"threaddump":{"href":"http://localhost:8080/actuator/threaddump","templated":false},"metrics":{"href":"http://localhost:8080/actuator/metrics","templated":false},"scheduledtasks":{"href":"http://localhost:8080/actuator/scheduledtasks","templated":false},"mappings":{"href":"http://localhost:8080/actuator/mappings","templated":false}}}裸露指定端点一些端点可能会裸露敏感数据,所以咱们须要晓得如何细粒度去裸露端点。 management.endpoints.web.exposure.include属性能够通过逗号分隔符来配置多个端点,比方咱们裸露/beans和/loggers端点: management.endpoints.web.exposure.include=beans, loggers除了蕴含具体端点之外,咱们还能够排除一些端点。比方裸露除了/threaddump之外的所有端点: management.endpoints.web.exposure.include=*management.endpoints.web.exposure.exclude=threaddumpinclude和exclude属性均反对配置一个端点列表。exclude属性的优先级要高于include。 开启指定端点上面来看一下如何细粒度的开启指定的端点。首先,须要敞开默认开启的所有端点: management.endpoints.enabled-by-default=false开启并裸露/health端点: management.endpoint.health.enabled=truemanagement.endpoints.web.exposure.include=health通过上述配置,便能够拜访/health端点了。 开启Shutdown端点因为/shutdown端点比拟敏感的起因,该端点默认是不可用的。可在application.properties文件中通过如下配置进行启动: management.endpoint.shutdown.enabled=true此时,拜访/actuator端点,就能够在列表中看到它了。/shutdown端点仅接管POST申请,能够通过如下形式优雅的敞开服务: curl -X POST http://localhost:8080/actuator/shutdown端点的平安在实在的利用中,咱们通常须要对应用程序进行平安防护。基于此,咱们须要确保Actuator端点的平安。 首先,在应用程序中增加security的依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> <version>2.4.4</version></dependency>为了最根本的平安保障,这是咱们须要做的事。增加security starter依赖之后,程序会主动将身份验证利用于除/info和/health之外的所有公开端点。 上面,自定义security以将 /actuator 端点限度为 ADMIN 角色。 首先排除默认的平安配置: @SpringBootApplication(exclude = { SecurityAutoConfiguration.class, ManagementWebSecurityAutoConfiguration.class })注意下ManagementWebSecurityAutoConfiguration.class类,它会让平安配置利用到 /actuator。 在咱们的配置类中,配置几个用户和角色,同时有一个ADMIN的角色能够应用: @Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception { PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); auth .inMemoryAuthentication() .withUser("user") .password(encoder.encode("password")) .roles("USER") .and() .withUser("admin") .password(encoder.encode("admin")) .roles("USER", "ADMIN");}SpringBoot 为咱们提供了一个不便的申请匹配器,用于咱们的Actuator端点。利用它能够将/actuator锁定为ADMIN角色: ...

July 14, 2021 · 1 min · jiezi

关于springboot:springbootmybatis拦截器不生效问题分析

一、环境信息SpringBoot:2.3.6.RELEASEMybatis-plus:3.3.1二、碰到问题明天因业务零碎性能须要,应用mybatis的拦截器对ORACLE的数据库会话进行用户以后语言环境设置(NLS_LANGUAGE),碰到拦截器代码不失效的问题,特此记录下来,不便未来查阅。三、代码如下:1、在Mybatis的@Configuration相干代码增加如下代码(主动注入到拦截器链): /** * 更改会话状态 * * @return */@Beanpublic AlterSessionInterceptor alterSessionInterceptor() { return new AlterSessionInterceptor();}2、拦截器代码(拦挡StatementHandler的prepare办法,取到Connection执行ALTER SESSION的语句):/** <p>更改会话解析器</p> *@since 2021/7/14 */@Slf4j@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})public class AlterSessionInterceptor implements Interceptor { @Overridepublic Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); Connection connection = (Connection) args[0]; if ("Oracle".equalsIgnoreCase(connection.getMetaData().getDatabaseProductName())) { Statement statement = null; try { statement = connection.createStatement(); String locale = RequestHelper.getCurrentLocale(); if ("en_GB".equalsIgnoreCase(locale)) { statement.execute("ALTER SESSION SET NLS_LANGUAGE='AMERICAN'"); } else { statement.execute("ALTER SESSION SET NLS_LANGUAGE='SIMPLIFIED CHINESE'"); } } finally { statement.close(); } } return invocation.proceed();}}在运行调试时发现代码无奈执行到拦挡逻辑,通过参考分页插件代码,发现须要重载以下办法(代码默认是只有实现intercept这个接口,然而实际上须要多加以下插件的解决逻辑才能够,才会启用Plugin的Proxy): ...

July 14, 2021 · 1 min · jiezi

关于springboot:SpringBoot自动装配流程

SpringBoot重要的几个注解 SpringBootApplication外面包含 EnableAutoConfiguration、SpringBootConfiguration、ComponentScan EnableAutoConfiguration:启动主动拆卸AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class) 主动拆卸导入类ComponentScan:包扫描,默认是启动类以后包 SpringBoot启动 --SpringApplication.run(<以后启动类>,arg)--以后主动类(primarySource)--prepareContext()容器事后初始化工作--load 加载以后启动类--refreshContext(相熟的refresh办法,Spring容器创立以及初始化相干操作)-- invokeBeanFactoryPostProcessors(beanFactory);执行BeanFactoryPostProcessors办法执行invokeBeanFactoryPostProcessors中重要一步 BeanDefinitionRegistryPostProcessor bean定义信息处理器 实现类ConfigurationClassPostProcessor(重要)实现了 postProcessBeanDefinitionRegistryprocessConfigBeanDefinitions 配置Bean的定义信息doProcessConfigurationClass 解析Configuration注解 processImports(configClass, sourceClass, getImports(sourceClass), true);// 递归的解析配置类的注解 蕴含importprivate void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } ///执行this.deferredImportSelectorHandler.process(); public void process() { List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; try { if (deferredImports != null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); deferredImports.sort(DEFERRED_IMPORT_COMPARATOR); deferredImports.forEach(handler::register); //开始执行Import注解 handler.processGroupImports(); } } finally { this.deferredImportSelectors = new ArrayList<>(); } } }//执行办法 public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { //这步 grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get( entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass), asSourceClasses(entry.getImportClassName()), false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } }//下一步public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {//实现类 AutoConfigurationImportSelector this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); } //AutoConfigurationImportSelector 实现类@Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)//获取 AutoConfigurationEntry .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } }//下一步 这步就开始清晰了 protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); //主动拆卸开始 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } // getCandidateConfigurations办法protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) { List<String> configurations = //getSpringFactoriesLoaderFactoryClass 就是获取 EnableAutoConfiguration.class这个主动拆卸类//SpringFactoriesLoader 加载默认目录 META-INF/spring.factories 上面 加载主动拆卸EnableAutoConfiguration 这种类型的bean 此时加载的只是作为bean的定义信息 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you " + "are using a custom packaging, make sure that file is correct."); return configurations; }加载bean的定义信息到beanfactory 接下来就是 Beanfactory 执行bean的生命周期流程 总结:SpringBoot启动 SpringApplication.run办法传入以后启动类primarySources加载以后配置类 在refreshContext中invokeBeanFactoryPostProcessors 执行BeanFactoryPostProcessors(ConfigurationClassPostProcessor) 加载那些主动配置的bean的定义信息、加载实现 执行后续的beanfactory创立bean的流程 ...

July 14, 2021 · 2 min · jiezi

关于springboot:用Spingboot获得微信小程序的Code以及openid和sessionkey

这篇文章次要写的是怎么用spingboot来获取微信小程序的Code以及openid和sessionke,我感觉曾经很具体了 咱们要取得openid和sessionkey,就必须先要取得code,咱们间接调用wx.login 这样咱们就取得了微信code,接下来咱们关上微信公众平台找到开发治理的开发设置,取得咱们小程序的appid和密钥 接下来关上idea,找到pom.xml配置文件退出这个依赖 <!--Http Requset封装--><dependency> <groupId>com.github.kevinsawicki</groupId><artifactId>http-request</artifactId><version>6.0</version></dependency>接下来就是最初一步了,依照这个写就能够取得Code了,包含获取微信openid和sessionkey,留神:数据是返回了前端 更多精彩教程以及对于小程序教程请关注微信小程序:萝卜的博客 ,或扫描下方二维码

July 13, 2021 · 1 min · jiezi

关于springboot:SpringBoot基础篇

第一节SpringBoot的根本个性及概念约定优于配置Build Anything with Spring Boot:Spring Boot is the starting point forbuilding all Spring-based applications. Spring Boot is designed to get you upand running as quickly as possible, with minimal upfront configuration ofSpring.下面这段话的意思大略为:springboot是基于spring开发的终点,缩小了spring开发所须要的配置。约定优于配置又叫按约定编程,是一种软件设计范式。比方实体类是User时,咱们对应的数据库表就是user,这种遵循某种法则的约定就是约定优于配置。概念springboot是基于spring的,对spring的一些毛病进行的改善(spring的毛病:spring的配置文件为重量级的,每次启动都须要扫描大量xml文件)。长处一:起步依赖,springboot不再须要咱们在pom.xml中配置spring,springmvc等依赖,它是把这些依赖打包到一起进行的援用。长处二:主动配置,在springboot中,咱们不再须要大量的配置bean,能够主动将bean注入到ioc容器中,通过@AutoWired注解来获取bean。 第二节SpringBoot我的项目的构建应用Spring Initializr构建抉择Spring Initializr,在连网的状况下会显示出如图内容,抉择jdk开发版本。 springboot的版本号为2.5.2(默认抉择的是最新最稳固),勾选Spring Web退出springmvc依赖。 第三节SpringBoot的根本应用及热部署根本案例 springboot的主动类应该在Controller包的父级,因为启动类在启动后扫描的范畴为以后启动类所在包及其子包。比方和com.hellojava属于同一层级。 热部署首先在pom.xml文件中退出相干依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId></dependency> 而后在IDEA工具中关上File->Settings->Compiler 而后shift+ctrl+alt+/关上 点击registry,勾选下列选项 第四节SpringBoot的全局配置文件application.properties配置文件格局:person.id=1person.name=tomperson.hobby=study,playperson.map.k1 = v1person.map.k2 = v2person.pet.name = dogperson.pet.type = dog application.yml配置文件person: id: 2 name: lisi hobby: [play,study] map: {v1: k1,v2: k2} pet: {type: dog,name: tom} ...

July 13, 2021 · 1 min · jiezi

关于springboot:Springboot-常见bug及纠正意见

启动类谬误(IllegalStateException):一:起因:在单元测试启动时没有找到对应的Springboot工程启动类解决办法:Springboot工程中没有定义类,启动类没有应用@SpringBootApplication注解形容单元测试类没有放在启动类所在包或子包中 二:起因:可能Sprigboot工程中有多个启动类……解决办法:删除其启动类所在包其子包中多余的启动类保留一个…… Mybatis谬误:一:起因:拜访属性或者调用属性为null解决办法: 查看测试类是否交给了spring容器治理,是否有@SpringBootTest注解或者是否在测试包test下 查看属性是否由spring进行依赖注入(是否增加@Autowired注解) 单元测试办法上@Test注解引入是否正确 起因:个别是对象不存在解决办法:先运行,如果运行没有问题则是工具(idea)问题,不影响程序运作,反之在Mapper层加上@Mapper注解 SpringWeb谬误:一:404(申请页面不存在)起因:服务没有启动,端口不正确解决办法:查看申请的url是否正确,查看申请的资源是否交给了Spring治理,查看端口是否正确 二:500(服务器遇到谬误,无奈实现申请)起因:服务器谬误等解决办法:重启服务器等…… 三:400(服务器不了解申请的语法)起因:当客户端提交的申请参数个数,类型。格局不匹配是呈现404解决办法:查看类型,个数,比方向服务端提交的日期 四:405(禁用申请中指定的办法)起因:客户端提交形式与服务端定义的可解决办法不匹配解决办法:查看申请形式,比方服务只能用get申请解决,则不能用post申请

July 11, 2021 · 1 min · jiezi

关于springboot:spring-boot-使用-k8s-的-configMap-作为外部配置

一、☀️概述spring boot 利用以容器的形式运行在 k8s 集群下面是十分不便的,然而不同的环境须要不同的配置文件,咱们能够应用内部的配置核心,比方 nacos 、apollo。k8s 也提供了 configMap 用来将环境配置信息和容器镜像解耦,便于利用配置的批改。本文次要从以下几个方面介绍 spring boot 应用 k8s 的 configMap 作为内部配置的应用办法: spring boot 加载配置文件介绍k8s 的 configMap 介绍应用 k8s 的 configMap 作为内部配置文件二、☀️spring boot 加载配置文件介绍当应用程序启动时,Spring Boot 会主动从以下地位查找并加载 application.properties 和 application.yaml 文件。 配置文件优先级从高到底的程序如下: file:./config/ - 优先级最高(我的项目根门路下的/config子目录)file:./ - 优先级第二 (我的项目根门路下)classpath:/config/ - 优先级第三(我的项目resources/config下)classpath:/ - 优先级第四(我的项目resources根目录)高优先级配置会笼罩低优先级配置 在同级目录下同时存在 application.properties 和 application.yaml 文件,那么application.properties 会笼罩 application.yaml 文件如果咱们运行时想指定运行哪个环境的配置文件,能够有三种形式: 在我的项目 resources 文件夹下的 application.properties 文件中配置 spring.profiles.active=dev 指定加载的环境启动 jar 时,指定 --spring.profiles.active=prod 加载的环境启动 jar 时,指定 --spring.config.location=target/application.properties加载配置文件地位三、☀️k8s 的 configMap介绍ConfigMap 是一种 API 对象,用来将非机密性的数据保留到键值对中。应用时 pod 能够将其用作环境变量、命令行参数或者存储卷中的配置文件。 ...

July 9, 2021 · 2 min · jiezi

关于springboot:Spring-Boot整合Quartz实现定时任务

原文地址:Spring Boot整合Quartz实现定时工作 源码已上传至 Github:spring-boot-quartz 增加依赖编辑文件 pom.xml: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId></dependency>增加配置编辑配置文件 application.yml,增加以下配置: spring: quartz: # 采纳数据库存储形式 job-store-type: jdbc jdbc: # 不从新创立数据表 initialize-schema: never properties: org: quartz: scheduler: instanceId: AUTO jobStore: class: org.quartz.impl.jdbcjobstore.JobStoreTX driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 数据表前缀 tablePrefix: qrtz_ # 连接池 threadPool: class: org.quartz.simpl.SimpleThreadPool threadCount: 10 threadPriority: 5 threadsInheritContextClassLoaderOfInitializingThread: true创立数据表供 Quartz 应用sql 语句可在 jdbcjobstore 找到,Quartz 提供了 tables_mysql_innodb.sql: ## In your Quartz properties file, you'll need to set# org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate### By: Ron Cordell - roncordell# I didn't see this anywhere, so I thought I'd post it here. This is the script from Quartz to create the tables in a MySQL database, modified to use INNODB instead of MYISAM.DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;DROP TABLE IF EXISTS QRTZ_LOCKS;DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;DROP TABLE IF EXISTS QRTZ_TRIGGERS;DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;DROP TABLE IF EXISTS QRTZ_CALENDARS;CREATE TABLE QRTZ_JOB_DETAILS(SCHED_NAME VARCHAR(120) NOT NULL,JOB_NAME VARCHAR(190) NOT NULL,JOB_GROUP VARCHAR(190) NOT NULL,DESCRIPTION VARCHAR(250) NULL,JOB_CLASS_NAME VARCHAR(250) NOT NULL,IS_DURABLE VARCHAR(1) NOT NULL,IS_NONCONCURRENT VARCHAR(1) NOT NULL,IS_UPDATE_DATA VARCHAR(1) NOT NULL,REQUESTS_RECOVERY VARCHAR(1) NOT NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(190) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,JOB_NAME VARCHAR(190) NOT NULL,JOB_GROUP VARCHAR(190) NOT NULL,DESCRIPTION VARCHAR(250) NULL,NEXT_FIRE_TIME BIGINT(13) NULL,PREV_FIRE_TIME BIGINT(13) NULL,PRIORITY INTEGER NULL,TRIGGER_STATE VARCHAR(16) NOT NULL,TRIGGER_TYPE VARCHAR(8) NOT NULL,START_TIME BIGINT(13) NOT NULL,END_TIME BIGINT(13) NULL,CALENDAR_NAME VARCHAR(190) NULL,MISFIRE_INSTR SMALLINT(2) NULL,JOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_SIMPLE_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(190) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,REPEAT_COUNT BIGINT(7) NOT NULL,REPEAT_INTERVAL BIGINT(12) NOT NULL,TIMES_TRIGGERED BIGINT(10) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_CRON_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(190) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,CRON_EXPRESSION VARCHAR(120) NOT NULL,TIME_ZONE_ID VARCHAR(80),PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_SIMPROP_TRIGGERS ( SCHED_NAME VARCHAR(120) NOT NULL, TRIGGER_NAME VARCHAR(190) NOT NULL, TRIGGER_GROUP VARCHAR(190) NOT NULL, STR_PROP_1 VARCHAR(512) NULL, STR_PROP_2 VARCHAR(512) NULL, STR_PROP_3 VARCHAR(512) NULL, INT_PROP_1 INT NULL, INT_PROP_2 INT NULL, LONG_PROP_1 BIGINT NULL, LONG_PROP_2 BIGINT NULL, DEC_PROP_1 NUMERIC(13,4) NULL, DEC_PROP_2 NUMERIC(13,4) NULL, BOOL_PROP_1 VARCHAR(1) NULL, BOOL_PROP_2 VARCHAR(1) NULL, PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_BLOB_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_NAME VARCHAR(190) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,BLOB_DATA BLOB NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP),INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP),FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_CALENDARS (SCHED_NAME VARCHAR(120) NOT NULL,CALENDAR_NAME VARCHAR(190) NOT NULL,CALENDAR BLOB NOT NULL,PRIMARY KEY (SCHED_NAME,CALENDAR_NAME))ENGINE=InnoDB;CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (SCHED_NAME VARCHAR(120) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP))ENGINE=InnoDB;CREATE TABLE QRTZ_FIRED_TRIGGERS (SCHED_NAME VARCHAR(120) NOT NULL,ENTRY_ID VARCHAR(95) NOT NULL,TRIGGER_NAME VARCHAR(190) NOT NULL,TRIGGER_GROUP VARCHAR(190) NOT NULL,INSTANCE_NAME VARCHAR(190) NOT NULL,FIRED_TIME BIGINT(13) NOT NULL,SCHED_TIME BIGINT(13) NOT NULL,PRIORITY INTEGER NOT NULL,STATE VARCHAR(16) NOT NULL,JOB_NAME VARCHAR(190) NULL,JOB_GROUP VARCHAR(190) NULL,IS_NONCONCURRENT VARCHAR(1) NULL,REQUESTS_RECOVERY VARCHAR(1) NULL,PRIMARY KEY (SCHED_NAME,ENTRY_ID))ENGINE=InnoDB;CREATE TABLE QRTZ_SCHEDULER_STATE (SCHED_NAME VARCHAR(120) NOT NULL,INSTANCE_NAME VARCHAR(190) NOT NULL,LAST_CHECKIN_TIME BIGINT(13) NOT NULL,CHECKIN_INTERVAL BIGINT(13) NOT NULL,PRIMARY KEY (SCHED_NAME,INSTANCE_NAME))ENGINE=InnoDB;CREATE TABLE QRTZ_LOCKS (SCHED_NAME VARCHAR(120) NOT NULL,LOCK_NAME VARCHAR(40) NOT NULL,PRIMARY KEY (SCHED_NAME,LOCK_NAME))ENGINE=InnoDB;CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY);CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP);CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP);CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP);CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME);CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP);CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE);CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE);CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE);CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME);CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME);CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME);CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE);CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE);执行完 sql 语句能够在数据库中看到以下11个表: ...

July 9, 2021 · 7 min · jiezi

关于springboot:SpringCloud-微服务最佳开发实践

当初基于SpringCloud的微服务开发日益风行,网上各种开源我的项目层出不穷。咱们在理论工作中能够参考开源我的项目实现很多开箱即用的性能,然而必须要恪守肯定的约定和标准。 本文联合咱们理论的开发中遇到的一些问题整顿出了一份微服务开发的实际标准,欢送各位大佬拍砖指导。 Maven标准所有我的项目必须要有一个对立的parent模块 所有微服务工程都依赖这个parent,parent用于治理依赖版本,maven仓库,jar版本的对立降级保护 在parent上层能够有 core,starter,rate-limit 等自定义模块 core 外围包的作用:以POJO模式约定各种开发标准;如BaseEntity,对立入参,返参各种二方、三方组件开箱即用AutoConfig;各种进步开发效率的帮忙类等 XXXUtil留神:core包所有依赖的scope必须是provided,防止传递依赖,同时配合Condition注解按条件加载Bean 如 @ConditionalOnClass(Ribbon.class),@ConditionalOnBean(StringRedisTemplete.class) starter模块 如果你每个服务都须要依赖10几个starter,能够建一个对立的starter模块帮他们对立依赖进来,治理依赖集,简化依赖 rate-limit模块 用于搁置非通用的自开发组件 正确区分Release版本 和 Snapshot版本 阐明:如果是Snapshot版本,那么在mvn deploy时会主动公布到快照版本库中,而应用快照版本的模块,在不更改版本号的状况下,间接编译打包时,Maven会主动从镜像服务器上下载最新的快照版本。 如果是Release版本,那么在mvn deploy时会主动公布到正式版本库中,而应用正式版本的模块,在不更改版本号的状况下,编译打包时如果本地曾经存在该版本的模块则不会被动去镜像服务器上下载。 简而言之: Release : 正式版,有bug不能再持续应用这个版本号 Snapshot:快照版,有bug能够持续应用同一版本号,能够主动降级,举荐应用 服务调用标准服务间通过引入sdk调用,服务消费者须要依赖生产者提供的api,配合snapshot不便降级account account-api account-serviceaccount-api 模块中放生产方须要用到的货色,api接口,vo,入参等... public interface AccountApi { ...}account-service实现account-api提供的接口 @RestController@Log4j2@Api(tags = "用户接口")@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class AccountController implements AccountApi { ...}消费者通过feign调用生产者,间接集成生产者提供的接口并解决熔断@Component@FeignClient(name = "account-service",fallbackFactory = AccountClientFallbackFactory.class)public interface AccountClient extends AccountApi { ...}@Componentpublic class AccountClientFallbackFactory implements FallbackFactory<AccountClient> { @Override public AccountClient create(Throwable throwable) { AccountClientFallback accountClientFallback = new AccountClientFallback(); accountClientFallback.setCause(throwable); return accountClientFallback; }}@Slf4jpublic class AccountClientFallback implements AccountClient { @Setter private Throwable cause; @Override public ResultData<AccountDTO> getByCode(String accountCode) { log.error("查问失败,接口异样" ,cause); AccountDTO account = new AccountDTO(); account.setAccountCode("000"); account.setAccountName("测试Feign"); return ResultData.success(account); }}Restful设计规范一个 API 是一个开发者的 UI - 就像其余任何 UI 一样, 确保用户体验被认真的思考过是很重要的! ...

July 8, 2021 · 1 min · jiezi

关于springboot:在Spring-Boot中使用Druid连接池

原文地址:在Spring Boot中应用Druid连接池 简介Druid连接池是阿里巴巴开源的数据库连接池我的项目。Druid连接池为监控而生,内置弱小的监控性能,监控个性不影响性能。功能强大,能防SQL注入,内置Loging能诊断Hack利用行为。 Github我的项目地址:https://github.com/alibaba/druid 应用在 Spring Boot 中应用 Druid ,罕用的办法有两种,一是增加 druid 依赖,而后配置相干参数;二是应用 Druid Spring Boot Starter,这种办法比较简单,本文以这种办法为例。 1. 增加依赖在 pom.xml 文件中增加 druid 的依赖包: <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version></dependency>2. 增加配置以下配置属性解释可查看 DruidDataSource配置属性列表。 JDBC 配置spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8spring.datasource.druid.username=xxxspring.datasource.druid.password=xxxspring.datasource.druid.driver-class-name=com.mysql.cj.jdbc.Driver连接池配置spring.datasource.druid.initial-size=# 最大连接池数量,默认为 8spring.datasource.druid.max-active=spring.datasource.druid.min-idle=spring.datasource.druid.max-wait=spring.datasource.druid.pool-prepared-statements=spring.datasource.druid.max-pool-prepared-statement-per-connection-size= spring.datasource.druid.max-open-prepared-statements=# 验证连贯是否可用,应用的SQL语句spring.datasource.druid.validation-query=spring.datasource.druid.validation-query-timeout=spring.datasource.druid.test-on-borrow=spring.datasource.druid.test-on-return=# testWhileIdle = "true" 指明连贯是否被闲暇连贯回收器(如果有)进行测验.如果检测失败,则连贯将被从池中去除.spring.datasource.druid.test-while-idle=spring.datasource.druid.time-between-eviction-runs-millis=spring.datasource.druid.min-evictable-idle-time-millis=spring.datasource.druid.max-evictable-idle-time-millis=spring.datasource.druid.filters= #配置多个英文逗号分隔监控配置# 登录用户名spring.datasource.druid.stat-view-servlet.login-username=xxx# 登录明码spring.datasource.druid.stat-view-servlet.login-password=xxx3. 运行测试增加配置实现后,启动利用,应用浏览器拜访: http://127.0.0.1:8080/druid/l... 在登录页面输出已配置好的用户名 spring.datasource.druid.stat-view-servlet.login-username 及明码 spring.datasource.druid.stat-view-servlet.login-password,登录胜利后进入到druid的监控页面: 至此,Spring Boot 整合 Druid 配置实现,更多的性能应用与配置请查看 官网文档。 参考文档Druid Spring Boot Starter

July 8, 2021 · 1 min · jiezi

关于springboot:Springboot-读取修改json配置文件

读取或更新resources下的json文件; { "links": [ { "name": "MYSQL_HOST", "title": "MYSQL_HOST", "content": "127.0.0.1" }, { "name": "MYSQL_PORT", "title": "端口", "content": "3306" }, { "name": "MYSQL_DBNAME", "title": "数据库", "content": "tableau" }, { "name": "MYSQL_USER", "title": "用户名", "content": "root" }, { "name": "MYSQL_PASSWORD", "title": "明码", "content": "123456" } ]}import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONArray;import com.alibaba.fastjson.JSONObject;import org.springframework.context.annotation.Profile;import java.io.*;import java.util.HashMap;public class JsonUtils { public static String readJSON(String fileName) throws Exception { String filePath = Profile.class.getClassLoader().getResource(fileName).toURI().getPath(); BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(filePath))); StringBuilder sb = new StringBuilder(); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } String result = sb.toString().replaceAll("\r\n", "").replaceAll(" +", ""); return result; } public static void updateJSON(String fileName, HashMap<String, String> map) throws Exception { JSONObject jsonObject = JSON.parseObject(readJSON(fileName)); JSONArray links = jsonObject.getJSONArray("links"); for (int i = 0; i < links.size(); i++) { JSONObject item1 = links.getJSONObject(i); if (item1.getString("name").equalsIgnoreCase(map.get("name"))) { item1.put("content", map.get("content")); } } JSONObject nItem = new JSONObject(); nItem.put("links", links); String nContent = JSON.toJSONString(nItem); String filePath = Profile.class.getClassLoader().getResource(fileName).toURI().getPath(); BufferedWriter bw = null; try { bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath))); bw.write(""); bw.write(nContent); bw.close(); } catch (IOException e) { e.printStackTrace(); } finally { try { bw.close(); } catch (IOException e) { e.printStackTrace(); } } }} @PostMapping() public ResponseEntity<Object> editConfig(@RequestBody HashMap<String, String> map) throws Exception { JsonUtils.updateJSON("user.json", map); return new ResponseEntity<>(HttpStatus.OK); }

July 8, 2021 · 1 min · jiezi

关于springboot:SpringBoot-优雅整合Swagger-Api-自动生成文档

前言一个好的可继续交付的我的项目,我的项目阐明,和接口文档是必不可少的,swagger api 就能够帮咱们很容易主动生成api 文档,不须要独自额定的去写,无侵入式,方便快捷大大减少前后端的沟通不便查找和测试接口进步团队的开发效率不便新人理解我的项目,残余的工夫就能够去约妹子啦 整合swagger api这里咱们本人去整合swagger api比拟麻烦,要导入好几个包,有大神帮咱们写好了轮子kinfe4j间接对应SpringBoot的启动项,而且在不影响原来应用性能上界面ui做了丑化性能做了加强 咱们间接整合这个就好了 <!--api 文档--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.1</version> </dependency>正如官网所说 kinfe4j官网文档点击这里 自定义配置信息为咱们为swagger配置更多的接口阐明 package cn.soboys.core.config;import cn.hutool.core.collection.CollUtil;import cn.soboys.core.ret.ResultCode;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.HttpMethod;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.builders.ResponseBuilder;import springfox.documentation.service.ApiInfo;import springfox.documentation.service.Contact;import springfox.documentation.service.Response;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import javax.annotation.Resource;import java.util.Arrays;import java.util.List;/** * @author kenx * @version 1.0 * @date 2021/6/21 16:02 * api 配置类 */@Configurationpublic class SwaggerConfigure { @Resource private SwaggerProperty swaggerProperty; /** * 结构api 文档 * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.OAS_30).globalResponses(HttpMethod.POST, this.responseList()) //全局respons信息 .apiInfo(apiInfo(swaggerProperty)) //文档信息 .select() //文档扫描 //.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class)) //有ApiOperation注解的controller都退出api文档 .apis(RequestHandlerSelectors.basePackage(swaggerProperty.getBasePackage())) //包模式 .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo(SwaggerProperty swagger) { return new ApiInfoBuilder() //题目 .title(swagger.getTitle()) //形容 .description(swagger.getDescription()) //创立联系人信息 (作者,邮箱,网站) .contact(new Contact(swagger.getAuthor(), swagger.getUrl(), swagger.getEmail())) //版本 .version(swagger.getVersion()) //认证 .license(swagger.getLicense()) //认证协定 .licenseUrl(swagger.getLicenseUrl()) .build(); } /** * 全局response 返回信息 * @return */ private List<Response> responseList() { List<Response> responseList = CollUtil.newArrayList(); Arrays.stream(ResultCode.values()).forEach(errorEnum -> { responseList.add( new ResponseBuilder().code(errorEnum.getCode().toString()).description(errorEnum.getMessage()).build() ); }); return responseList; }}抽出api文档配置模版信息为属性文件不便复用 ...

July 8, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-demo系列十四ShardingSphere-MyBatisPlus-分库分表-读写分离

1 概述之前笔者写过两篇文章: ShardingSphere 读写拆散ShardingSphere 分库分表这里将两者联合起来,实现读写拆散+分库分表的性能。对于环境的配置本文将进行简化叙述,具体能够参考前两篇文章。 2 环境MySQL 8.0.25(Docker)MyBatis Plus 3.4.3.1MyBatis Plus Generator 3.5.0Druid 1.2.6ShardingSphere 4.1.1Yitter 1.0.6(一个雪花id生成器)3 数据库环境筹备因为环境筹备不是本文的重点,一主一从的主从复制环境能够参考此处搭建。 筹备好环境,本地启动两个MySQL,主节点环境: 名字:master端口:3306数据库:两个库(test0、test1)数据表:六个表,每个库三个(test0.user0、test0.user1、test0.user2、test1.user0、test1.user1、test1.user2)从节点环境: 名字:slave端口:3307数据库:两个库(test0、test1)数据表:六个表,每个库三个(test0.user0、test0.user1、test0.user2、test1.user0、test1.user1、test1.user2)主库配置文件: [mysqld]server-id=1binlog-do-db=test0binlog-do-db=test1从库配置文件: [mysqld]server-id=2replicate-do-db=test0replicate-do-db=test1残缺的数据库脚本和MySQL配置文件放在文末的源码链接中。 4 新建我的项目新建我的项目并引入如下依赖: DruidMyBatis Plus starterMyBaits Plus GeneratorVelocity coreShardingSphereYitterMaven如下: <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.0</version></dependency><dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version></dependency><dependency> <groupId>org.realityforge.org.jetbrains.annotations</groupId> <artifactId>org.jetbrains.annotations</artifactId> <version>1.7.0</version></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version></dependency><dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>4.1.1</version></dependency><dependency> <groupId>com.github.yitter</groupId> <artifactId>yitter-idgenerator</artifactId> <version>1.0.6</version></dependency>Gradle如下: implementation 'com.baomidou:mybatis-plus-boot-starter:3.4.3.1'implementation 'org.apache.velocity:velocity-engine-core:2.3'implementation 'org.realityforge.org.jetbrains.annotations:org.jetbrains.annotations:1.7.0'implementation 'com.alibaba:druid:1.2.6'implementation 'org.apache.shardingsphere:sharding-jdbc-spring-boot-starter:4.1.1'implementation 'com.github.yitter:yitter-idgenerator:1.0.6'5 配置文件spring: shardingsphere: datasource: names: master-test0,master-test1,slave-test0,slave-test1 # 数据源节点名字 # master-test0示意主节点的test0库,master-test1示意主节点的test1库 # slave-test0示意从节点的test0库,slave-test1示意从节点的test1库 master-test0: type: com.alibaba.druid.pool.DruidDataSource # 连接池 url: jdbc:mysql://127.0.0.1:3306/test0 # 主节点的test0库 username: root password: 123456 master-test1: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/test1 # 主节点的test1库 username: root password: 123456 slave-test0: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3307/test0 # 从节点的test0库,端口3307 username: root password: 123456 slave-test1: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3307/test1 # 从节点的test1库,端口3307 username: root password: 123456 sharding: default-database-strategy: inline: sharding-column: age # 依照哪一列分库 algorithm-expression: master-test$->{age % 2} # 分库规定为对年龄取模 tables: user: actual-data-nodes: master-test$->{0..1}.user$->{0..2} # 分表的节点,格局为 [数据源.表名] table-strategy: inline: sharding-column: id # 依照哪一列分表 algorithm-expression: user$->{id%3} # 分表规定,对id取模 master-slave-rules: # 读写拆散的规定 master-test0: # 哪一个主节点 master-datasource-name: master-test0 # 指定主节点名字 slave-data-source-names: slave-test0 # 指定从节点名字 master-test1: master-datasource-name: master-test1 slave-data-source-names: slave-test1 props: sql: show: true # 打印SQL6 筹备测试代码应用MyBatis Plus Generator生成器类生成代码: ...

July 7, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-搭配MyBatis及Druid实现多数据源可自动切换

原文地址:https://blog.lanweihong.com/p... Github: spring-boot-mybatis-multiple-data-source 增加相干依赖包编辑 pom.xml 文件,增加相干依赖: <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.8</version> <relativePath/> </parent> <properties> <mysql.driver.version>8.0.16</mysql.driver.version> <mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version> <druid.version>1.1.10</druid.version> <tk.mybatis.version>2.1.5</tk.mybatis.version> <lombok.version>1.16.18</lombok.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <!-- mysql driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.driver.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>${tk.mybatis.version}</version> </dependency> </dependencies>配置 application.yml 文件在配置文件 application.yml 中配置数据源: spring: datasource: type: com.alibaba.druid.pool.DruidDataSource username: root password: Aa123456. druid: driver-class-name: com.mysql.cj.jdbc.Driver initial-size: 5 max-active: 50 max-wait: 60000 min-idle: 5 # 配置 book 数据源,可自定义 book: # type: com.alibaba.druid.pool.DruidDataSource # driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai # username: root # password: 1 # 配置 user 数据源 user: url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai # 重新配置明码,未配置则默认应用以上配置的 username: root password: 1手动配置数据源主数据源配置创立配置类 BookDataSourceConfig ,配置类须要对 DataSource、DataSourceTransactionManager、SqlSessionFactory 、SqlSessionTemplate 四个数据项进行配置。当零碎中存在多个数据源时,必须有一个数据源为主数据源,应用 @Primary 注解润饰。 ...

July 7, 2021 · 3 min · jiezi

关于springboot:SpringBoot-Validation优雅的全局参数校验

前言咱们都晓得在平时写controller时候,都须要对申请参数进行后端校验,个别咱们可能会这样写 public String add(UserVO userVO) { if(userVO.getAge() == null){ return "年龄不能为空"; } if(userVO.getAge() > 120){ return "年龄不能超过120"; } if(userVO.getName().isEmpty()){ return "用户名不能为空"; } // 省略一堆参数校验... return "OK";}业务代码还没开始写呢,光参数校验就写了一堆判断。这样写尽管没什么错,然而给人的感觉就是:不优雅,不业余,代码可读性也很差,一看就是新手写的代码 作为久经和平的老司机怎么能这样呢,大神是不容许这样代码呈现的,其实SpringBoot提供整合了参数校验解决方案spring-boot-starter-validation 整合应用在SpringBootv2.3之前的版本只须要引入 web 依赖就能够了他蕴含了validation校验包在此之后SpringBoot版本就独立进去了须要独自引入依赖 <!--参数校验--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>内置的校验注解有很多,列举如下: 注解校验性能@AssertFalse必须是false@AssertTrue必须是true@DecimalMax小于等于给定的值@DecimalMin大于等于给定的值@Digits可设定最大整数位数和最大小数位数@Email校验是否合乎Email格局@Future必须是未来的工夫@FutureOrPresent以后或未来工夫@Max最大值@Min最小值@Negative正数(不包含0)@NegativeOrZero正数或0@NotBlank不为null并且蕴含至多一个非空白字符@NotEmpty不为null并且不为空@NotNull不为null@Null为null@Past必须是过来的工夫@PastOrPresent必须是过来的工夫,蕴含当初@Pattern必须满足正则表达式@PositiveOrZero负数或0@Size校验容器的元素个数单个参数校验应用很简略只须要在须要校验controller上加上@Validated注解在需校验参数上加上@NotNull,@NotEmpty之类参数校验注解就行了, @Validated@GetMapping("/home")public class ProductController { public Result index(@NotBlank String name, @Email @NotBlank String email) { return ResultResponse.success(); }}对象参数校验在下面的根底上只须要在对象参数后面加上@Validated注解,而后在须要校验的对象参数的属性下面加上@NotNull,@NotEmpty之类参数校验注解就行了, @PostMapping("/user")public Result index1(@Validated @RequestBody UserParams userParams) { log.info("info test######"); log.error("error test #####"); return ResultResponse.success(userParams); }@Datapublic class UserParams { @NotBlank private String username; private int age; @NotBlank private String addr; @Email private String email;}参数校验异样信息处理下面咱们进行了参数校验,默认当参数校验没通过后会通过异样形式来抛出错误信息MethodArgumentNotValidException是在校验时抛出的还包含很多其余异样信息,这时咱们能够通过全局异样捕捉信息来解决这些参数校验异样 ...

July 6, 2021 · 2 min · jiezi

关于springboot:Spring-Boot-无侵入式-实现RESTful-API接口统一JSON格式返回

前言当初咱们做我的项目基本上中大型项目都是抉择前后端拆散,前后端拆散曾经成了一个趋势了,所以总这样·咱们就要和前端约定对立的api 接口返回json 格局, 这样咱们须要封装一个对立通用全局 模版api返回格局,下次再写我的项目时候间接拿来用就能够了 约定JSON格局个别咱们和前端约定json格局是这样的 { "code": 200, "message": "胜利", "data": { }}code: 返回状态码message: 返回信息的形容data: 返回值 封装java bean定义状态枚举package cn.soboys.core.ret;import lombok.Data;import lombok.Getter;/** * @author kenx * @version 1.0 * @date 2021/6/17 15:35 * 响应码枚举,对应HTTP状态码 */@Getterpublic enum ResultCode { SUCCESS(200, "胜利"),//胜利 //FAIL(400, "失败"),//失败 BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, "认证失败"),//未认证 NOT_FOUND(404, "接口不存在"),//接口不存在 INTERNAL_SERVER_ERROR(500, "零碎忙碌"),//服务器外部谬误 METHOD_NOT_ALLOWED(405,"办法不被容许"), /*参数谬误:1001-1999*/ PARAMS_IS_INVALID(1001, "参数有效"), PARAMS_IS_BLANK(1002, "参数为空"); /*用户谬误2001-2999*/ private Integer code; private String message; ResultCode(int code, String message) { this.code = code; this.message = message; }}定义返回状态码,和信息一一对应,咱们能够约定xxx~xxx 为什么错误码,避免前期错误码反复,应用凌乱不分明, ...

July 5, 2021 · 3 min · jiezi

关于springboot:SpringBoot优雅的全局异常处理

前言在日常我的项目开发中,异样是常见的,然而如何更高效的解决好异样信息,让咱们能疾速定位到BUG,是很重要的,不仅可能进步咱们的开发效率,还能让你代码看上去更难受,SpringBoot的我的项目曾经有肯定的异样解决了,然而对于咱们开发者而言可能就不太适合了,因而咱们须要对这些异样进行对立的捕捉并解决。 SpringBoot默认的错误处理机制返回谬误页面默认返回 Whitelabel Error Page页面的款式太枯燥,用户体验不好。如 果 我 们 需 要 将 所 有 的 异 常 同 一 跳 转 到 自 定 义 的 错 误 页 面 , 需 要 再 src/main/resources/templates 目录下创立 error.html 页面。 留神:名称必须叫 error <!DOCTYPE html><html><head><meta charset="UTF-8"><title>Insert title here</title></head><body> <!--SpringBoot默认存储异样信息的key为exception--> <span th:text="${exception}" /></body></html>返回json格局apiJson格局的后果字符串不对立,与前端人员约定对立格局不统一 源码剖析SpringBoot在页面 产生异样的时候会主动把申请转到/error,SpringBoot内置了一个BasicErrorController对异样进行对立的解决,当然也能够自定义这个门路 @RequestMapping( produces = {"text/html"} ) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity(body, status); } }咱们能够看到刚好对照两个办法一个返回谬误页面,一个返回谬误字符,默认谬误门路是/error如果有自定义就用自定义的 ...

July 4, 2021 · 2 min · jiezi

关于springboot:SpringBoot整合Elasticsearch

微信搜一搜:科技猫,分享编程,软件,科技。 Spring Data Elasticsearch我的项目提供了与Elasticsearch搜索引擎的集成。Spring Data Elasticsearch的要害性能区域是一个以POJO为核心的模型,该模型用于与Elastichsearch文档进行交互并轻松编写存储库款式的数据拜访层。 Maven依赖<!--https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> <version>2.3.0.RELEASE</version> </dependency>配置application.propertiesspring.elasticsearch.rest.uris=http://localhost:9200# spring.elasticsearch.rest.username= // 用户名# spring.elasticsearch.rest.password=// 明码# spring.elasticsearch.rest.connection-timeout= // 连贯超时工夫# spring.elasticsearch.rest.read-timeout= // 读取超时工夫CRUD操作实体类package com.example.entity;import lombok.Data;import org.springframework.data.annotation.Id;import org.springframework.data.elasticsearch.annotations.Document;import org.springframework.data.elasticsearch.annotations.Field;import org.springframework.data.elasticsearch.annotations.FieldType;import java.io.Serializable;@Data@Document(indexName = "account", type = "_doc")public class Message implements Serializable { private static final long serialVersionUID = 5710293639676035958L; @Id private String id; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String username; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String email; @Field(type = FieldType.Long) private String age; @Field(type = FieldType.Long) // 意思自定义属性格局 工夫格局,咱们在java程序中能够传入这些格局的工夫 private Long createTime;}Daopackage com.example.dao;import com.example.entity.Message;import org.apache.ibatis.annotations.Mapper;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;import org.springframework.stereotype.Repository;import java.util.List;@Mapper@Repositorypublic interface MessageDao extends ElasticsearchRepository<Message, String> { Page<Message> findAll(Pageable pageable); Iterable<Message> findAll(Sort sort); List<Message> findByUsername(String username); List<Message> findByAgeBetween(Long mix, Long max); Long countByAgeBetween(Long mix, Long max);}Test查问package com.example;import com.example.dao.MessageDao;import com.example.entity.Message;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Sort;import java.util.List;@SpringBootTestpublic class SpringDataElasticsearchFindTest { @Autowired private MessageDao messageDao; @Test public void findAll() { Iterable<Message> all = messageDao.findAll(); all.forEach(System.out::println); } @Test public void findAllPageRequest() { Iterable<Message> all = messageDao.findAll(PageRequest.of(0, 1)); all.forEach(System.out::println); } @Test public void findAllSort() { Iterable<Message> all = messageDao.findAll(Sort.by("age").descending()); all.forEach(System.out::println); } @Test public void findByUsername() { List<Message> messages = messageDao.findByUsername("JonssonYan"); messages.forEach(System.out::println); } @Test public void findByAgeBetween() { List<Message> messages = messageDao.findByAgeBetween(10L, 20L); messages.forEach(System.out::println); } @Test public void countByAgeBetween() { Long count = messageDao.countByAgeBetween(10L, 20L); System.out.println(count); }}减少package com.example;import com.example.dao.MessageDao;import com.example.entity.Message;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import java.util.Date;@SpringBootTestpublic class SpringDataElasticsearchInsertTest { @Autowired private MessageDao messageDao; @Test public void insert() { Message message = new Message(); message.setId("3"); message.setAge("18"); message.setEmail("yz808@qq.com"); message.setCreateTime(new Date().getTime()); message.setUsername("JonssonYan"); messageDao.save(message); }}删除package com.example;import com.example.dao.MessageDao;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;@SpringBootTestpublic class SpringDataElasticsearchDeleteTest { @Autowired private MessageDao messageDao; @Test public void deleteById(){ messageDao.deleteById("2"); }}自定义JSON查问Daopackage com.example.dao;import com.example.entity.Message;import org.apache.ibatis.annotations.Mapper;import org.springframework.data.domain.Page;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;import org.springframework.data.elasticsearch.annotations.Query;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;import org.springframework.stereotype.Repository;import java.util.List;@Mapper@Repositorypublic interface MessageDao extends ElasticsearchRepository<Message, String> { @Query("{\"match\": {\"username\": {\"query\": \"?0\"}}}") List<Message> findByUsername(String username);}Test@Test public void findByUsername() { List<Message> messages = messageDao.findByUsername("JonssonYan"); messages.forEach(System.out::println); }References[1] Spring Data Elasticsearch: https://spring.io/projects/sp... ...

June 30, 2021 · 2 min · jiezi

关于springboot:SpringBootSpringCloudSeata配置

记录SpringBoot+SpringCloud+Seata的配置。这里用的是AT模式。注册核心用的Nacos。本文不蕴含nacos配置部署。如有须要请自行百度。(吐槽,官网文档几乎一言难尽。。) 我这里创立了两个我的项目,demo3和demo4.POM配置是一样的,这里间接贴出。 <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo3</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo3</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR11</version> <type>pom</type> <scope>import</scope> </dependency> <!--<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>--> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> <version>2.2.5.RELEASE</version> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2.2.1.RELEASE</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build></project>SpringBoot,SpringCloud版本能够间接查看以上pom。我这里用的Seata最新版本,1.4.2.配置seata的时候大家留神,客户端和服务端版本必须统一。服务端下载地址:https://github-releases.githu... ...

June 29, 2021 · 3 min · jiezi

关于springboot:Fullset-一套基于微服务的定制化平台

传送门:一套基于微服务的定制化平台

June 29, 2021 · 1 min · jiezi

关于springboot:SpringBoot常用注解个人整理

【Spring Web MVC】Request@RequestMapping提供路由信息,负责URL到Controller中具体函数的映射。 @GetMapping GET申请:从服务器获取特定资源。@GetMapping("/blog")相当于@RequestMapping(value="/blog",method=RequestMethod.GET) @PostMapping POST申请:在服务器上创立一个新的资源。@PostMapping("/blogs/save")等价于@RequestMapping(value="/blog/save",method=RequestMethod.POST) consumes属性用于指定申请输出,而produces用于指定申请输入。 @PostMapping(consumes="application/json") @ResponseStatus(HttpStatus.CREATED) public Taco postTaco(@RequestBody Taco taco) { return tacoRepo.save(taco); }@PutMappingPUT申请:更新服务器上的资源,客户端提供更新后的整个资源。 @DeleteMappingDELETE申请:从服务器删除特定的资源 @PatchMappingPATCH申请:更新服务器上的资源,客户端提供更改的属性,能够看作为局部更新 params@PathVariable 获取门路参数,从url模板里取值,接管申请门路中占位符的值。 比方: @GetMapping("/blog/{id}") public Result detail(@PathVariable(name = "id") Long id){ Blog blog = blogService.getById(id); return Result.succ200(blog);}@RequestParam 获取查问参数,从request里拿数据。 @GetMapping("/blog") //分页解决public Result list(@RequestParam(defaultValue = "1") Integer currentPage){ Page page = new Page(currentPage,5); IPage pageData = …… return Result.succ200(pageData);}这里前端的代码是这样的: _this.$axios.get("/blog?currentPage=" + currentPage)@RequestParam 反对上面四种参数 defaultValue 如果本次申请没有携带这个参数,或者参数为空,那么就会启用默认值name 绑定本次参数的名称,要跟URL下面的一样required 这个参数是不是必须的value 跟name一样的作用,是name属性的一个别名@RequestBody 用于读取Request申请的body局部,并且ContentType为application/json格局的数据,接管到数据后会主动将数据绑定到Java对象上。零碎会应用HttpMessageConverter(或者本人定义)将申请的body中的json字符串转换为java对象。 ...

June 28, 2021 · 4 min · jiezi

关于springboot:Spring-Boot-demo系列十二ShardingSphere-MyBatisPlus-读写分离-主从复制

1 概述本文讲述了如何应用MyBatisPlus+ShardingSphere进行读写拆散,以及利用MySQL进行一主一从的主从复制。 具体步骤包含: MySQL主从复制环境筹备(Docker)搭建ShardingShpere+MyBatisPlus+Druid环境测试2 环境OpenJDK 11.0.11Spring Boot 2.5.1MyBatis Plus 3.4.3.1MyBatis Plus Generator 3.5.0Druid 1.2.6ShardingSphere 4.1.1MySQL 8.0.253 一些基础理论3.1 读写拆散读写拆散,顾名思义就是读和写离开,更具体来说,就是: 写操作在主数据库进行读操作在从数据库进行应用读写拆散的基本目标就是为了进步并发性能,如果读写都在同一台MySQL上实现,置信会不如一台MySQL写,另外两台MySQL读这样的配置性能高。另一方面,在很多时候都是读操作的申请要远远高于写操作,这样就显得读写拆散十分有必要了。 3.2 主从复制主从复制,顾名思义就是把主库的数据复制到从库中,因为读写拆散之后,写操作都在主库进行,然而读操作是在从库进行的,也就是说,主库上的数据如果不能复制到从库中,那么从库就不会读到主库中的数据。严格意义上说,读写拆散并不要求主从复制,只须要在主库写从库读即可,然而如果没有了主从复制,读写拆散将失去了它的意义。因而读写拆散通常与主从复制配合应用。 因为本示例应用的是MySQL,这里就说一下MySQL主从复制的原理,如下图所示: 工作流程如下: 主库批改数据后,将批改日志写入binlog从库的I/O线程读取主库的binlog,并拷贝到从库本地的binlog中从库本地的binlog被SQL线程读取,执行其中的内容并同步到从库中3.3 数据库中间件简介数据库中间件能够简化对读写拆散以及分库分表的操作,并暗藏底层实现细节,能够像操作单库单表那样操作多库多表,支流的设计方案次要有两种: 服务端代理:须要独立部署一个代理服务,该代理服务前面治理多个数据库实例,在利用中通过一个数据源与该代理服务器建设连贯,由该代理去操作底层数据库,并返回相应后果。长处是反对多语言,对业务通明,毛病是实现简单,实现难度大,同时代理须要确保本身高可用客户端代理:在连接池或数据库驱动上进行一层封装,外部与不同的数据库建设连贯,并对SQL进行必要的操作,比方读写拆散抉择走主库还是从库,分库分表select后如何聚合后果。长处是实现简略,人造去中心化,毛病是反对语言较少,版本升级艰难一些常见的数据库中间件如下: Cobar:阿里开源的关系型数据库分布式服务中间件,已停更DRDS:脱胎于Cobar,全称分布式关系型数据库服务MyCat:开源数据库中间件,目前更新了MyCat2版本Atlas:Qihoo 360公司Web平台部基础架构团队开发保护的一个基于MySQL协定的数据中间层我的项目,同时还有一个NoSQL的版本,叫Pikatddl:阿里巴巴自主研发的分布式数据库服务Sharding-JDBC:ShardingShpere的一个子产品,一个轻量级Java框架4 MySQL主从复制环境筹备看完了一些基础理论就能够进行入手了,本大节先筹备好MySQL主从复制的环境,基于Docker+MySQL官网文档搭建。 4.1 主库操作4.1.1 拉取镜像并创立容器运行docker pull mysqldocker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql-master mysqldocker exec -it mysql-master /bin/bash在主库中进行更新镜像源,装置vim以及net-tools的操作: cd /etc/aptecho deb http://mirrors.aliyun.com/debian/ buster main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib deb http://mirrors.aliyun.com/debian-security buster/updates main deb-src http://mirrors.aliyun.com/debian-security buster/updates main deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib > sources.listapt update && apt upgradeapt install vim net-tools4.1.2 批改配置文件vim /etc/mysql/my.cnf增加上面两行数据: ...

June 25, 2021 · 3 min · jiezi

关于springboot:SpringBoot-集成-Prometheus

目录一、增加依赖二、Prometheus 装置与配置三、Grafana 装置和配置四、自定义监控指标一、增加依赖Maven pom.xml<!-- 第一条必须加,否则会导致 Could not autowire. No beans of 'xxxx' type found 的谬误 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId></dependency><dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId></dependency>Gradle build.gradleimplementation 'org.springframework.boot:spring-boot-starter-actuator'compile 'io.micrometer:micrometer-registry-prometheus'compile 'io.micrometer:micrometer-core'关上 Prometheus 监控接口 application.propertiesserver.port=8088spring.application.name=springboot2-prometheusmanagement.endpoints.web.exposure.include=*management.metrics.tags.application=${spring.application.name}能够间接运行程序,拜访http://localhost:8088/actuator/prometheus能够看到上面的内容: # HELP jvm_buffer_total_capacity_bytes An estimate of the total capacity of the buffers in this pool# TYPE jvm_buffer_total_capacity_bytes gaugejvm_buffer_total_capacity_bytes{id="direct",} 90112.0jvm_buffer_total_capacity_bytes{id="mapped",} 0.0# HELP tomcat_sessions_expired_sessions_total # TYPE tomcat_sessions_expired_sessions_total countertomcat_sessions_expired_sessions_total 0.0# HELP jvm_classes_unloaded_classes_total The total number of classes unloaded since the Java virtual machine has started execution# TYPE jvm_classes_unloaded_classes_total counterjvm_classes_unloaded_classes_total 1.0# HELP jvm_buffer_count_buffers An estimate of the number of buffers in the pool# TYPE jvm_buffer_count_buffers gaugejvm_buffer_count_buffers{id="direct",} 11.0jvm_buffer_count_buffers{id="mapped",} 0.0# HELP system_cpu_usage The "recent cpu usage" for the whole system# TYPE system_cpu_usage gaugesystem_cpu_usage 0.0939447637893599# HELP jvm_gc_max_data_size_bytes Max size of old generation memory pool# TYPE jvm_gc_max_data_size_bytes gaugejvm_gc_max_data_size_bytes 2.841116672E9# 此处省略超多字...二、Prometheus 装置与配置应用 docker 运行 Prometheus(仅初始测试) ...

June 25, 2021 · 3 min · jiezi

关于springboot:Spring-Boot-demo系列十一MyBatis-Plus-Generator代码生成

1 前言MyBatis Plus starter最近更新了3.4.3.1版本,而MyBatis Plug Generator更新到了3.5.0版本,然而官网文档还没有更新生成器的代码,另外在之前的文章里介绍过MyBatis Plus的应用,因而这里补上一篇文章联合Spring Boot介绍代码生成器的应用。 2 为什么须要代码生成器应用代码生成器能够生成一些固定模板的代码,比方: Controller层代码Service层代码mapper实体类比方一个User类能够生成如下代码: 3 环境本次示例应用的环境如下: Spring Boot 2.5.1MyBaits Plus 3.4.3.1MyBatis Plus Generator 3.5.04 筹备数据表这里为了方便使用Workbench创立一个用户表User: 5 创立我的项目并导入依赖创立一个新的Spring Boot我的项目,而后导入如下依赖: implementation 'com.baomidou:mybatis-plus-boot-starter:3.4.3.1' implementation 'com.baomidou:mybatis-plus-generator:3.5.0'implementation 'org.apache.velocity:velocity-engine-core:2.3'implementation 'org.realityforge.org.jetbrains.annotations:org.jetbrains.annotations:1.7.0'除了starter和generator后,还须要一个模板引擎(可选Velocity、Freemarker、Beetl,默认Velocity)和一个注解依赖(jetbrains.annotations)。 Maven版本如下: <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version></dependency><dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.0</version></dependency><dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version></dependency><dependency> <groupId>org.realityforge.org.jetbrains.annotations</groupId> <artifactId>org.jetbrains.annotations</artifactId> <version>1.7.0</version></dependency>6 新建一个生成器类该类用于进行代码生成的配置: import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.config.*;public class MyBatisPlusGenerator { public static void main(String[] args) { DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://localhost:3306/test","root","123456").build(); String projectPath = System.getProperty("user.dir"); GlobalConfig globalConfig = new GlobalConfig.Builder().outputDir(projectPath+"/src/main/java").openDir(false).build(); PackageConfig packageConfig = new PackageConfig.Builder().moduleName("test").parent("com.example.test").build(); AutoGenerator autoGenerator = new AutoGenerator(dataSourceConfig); autoGenerator.global(globalConfig).packageInfo(packageConfig); autoGenerator.execute(); }}代码生成器配置相比起之前的版本,应用了Builder模式代替了原来的setter模式: ...

June 25, 2021 · 1 min · jiezi

关于springboot:Springboot集成ElasticSearch

Springboot我的项目集成ElasticSearch7.6.1

June 24, 2021 · 1 min · jiezi

关于springboot:spring-boot-分层构建镜像

一、概述spring boot 2.3 开始spring-boot-maven-plugin 插件默认开启了分层构建 jar包 或者 war包,本文次要依据官网文档记录一下如何应用分层jar构建镜像。 二、spring-boot-maven-plugin 分层构建 jar 包配置 spring boot 我的项目继承自 spring-boot-starter-parent,设置 <parent> 如下: <!-- Inherit defaults from Spring Boot --><parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version></parent>继承自 spring-boot-starter-parent 的我的项目有如下的默认配置: 默认编译jdk:JDK 1.8默认编码:UTF-8默认依赖项治理局部继承自 spring-boot-dependencies POM,治理公共依赖项的版本,这种依赖治理容许您在本人的 POM 中应用时省略那些依赖项的<version>标记默认配置的 excution goal为:repackage当在我的项目中引入 spring-boot-maven-plugin 插件默认就开启了分层构建 jar包,咱们能够应用如下形式进行验证: 在我的项目的 pom.xml 文件中增加如下插件配置: <project> <modelVersion>4.0.0</modelVersion> <artifactId>getting-started</artifactId> <!-- ... --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>执行如下命令进行打包: mvn clean package 在 target 目录上面生成对应的 jar 包,解压 jar 包目录构造如下: ...

June 21, 2021 · 2 min · jiezi

关于springboot:mica-251-发布添加-jfinal-activerecord-stater

一、前言做为一个 JFinal 老玩家,身边还有一部分敌人在混用 JFinal + Spring boot,其次要是在应用 jfinal activerecord 性能。故在 mica 中增加了基于 jfinal activerecord 的 mica-activerecord 模块,替大家排坑,方便使用。 二、mica(云母)mica是一个微服务组件集,但不仅仅是组件,咱们关注的是微服务生态并继续演进,尽量做到开箱即用,简化应用和排坑。总共已有 40+ 组件,并且很多组件曾经买通。 三、版本阐明最新版本mica 版本spring boot 版本spring cloud 版本2.5.1mica 2.5.x2.5.x20202.4.7mica 2.4.x2.4.x20202.1.1-GAmica 2.0.x~2.1.x2.2.x ~ 2.3.xHoxton阐明:mica 对 Spring cloud 为非强制依赖,除了 mica-jobs 其余组件一般 Spring boot 我的项目也能够应用。 四、更新记录v2.5.1✨ 整体代码优化缩小 codacy 上的问题。✨ mica-http 代码优化,反对自定义 Logger。✨ 增加 mica-activerecord 模块。✨ mica-core 优化 DisableValidationTrustManager 方便使用。✨ mica-redis 可自定义 RedisTemplate。✨ mica-logging loki sender 默认值优化,依据依赖判断,gitee #I3T68Y。 修复 mica-redis RedisTemplate 初始化。 update README.md 更新文档地址。⬆️ 降级到 Spring cloud 2020.0.3。⬆️ 降级到 Spring boot 到 2.5.1。v2.4.7✨ 整体代码优化缩小 codacy 上的问题。✨ mica-http 代码优化,反对自定义 Logger。✨ 增加 mica-activerecord 模块。✨ mica-core 优化 DisableValidationTrustManager 方便使用。✨ mica-redis 可自定义 RedisTemplate。✨ mica-logging loki sender 默认值优化,依据依赖判断,gitee #I3T68Y。 修复 mica-redis RedisTemplate 初始化。 update README.md 更新文档地址。⬆️ 降级到 Spring cloud 2020.0.3。⬆️ 降级到 Spring boot 到 2.4.7。五、重点阐明5.1 增加 mica-activerecord 模块扩大 jfinal activerecord,不便 Spring boot 中应用。性能如下: ...

June 21, 2021 · 1 min · jiezi

关于springboot:spring-boot-应用在-k8s-中的健康检查三

一、概述在 spring boot 2.3 中引入了容器探针,也就是减少了 /actuator/health/liveness 和 /actuator/health/readiness 这两个健康检查门路,对于部署在 k8s 中的利用,spring-boot-actuator 将通过这两个门路主动进行健康检查。本文次要依据官网文档的形容实际并记录应用流程,从如下几个方面进行介绍: k8s 中的健康检查spring-boot-actuator 中的 k8s 探针spring boot 健康检查在 k8s 中的实际二、spring boot 健康检查在 k8s 中的实际本次实际的思路来自下文的参考文章,这里应用 spring boot 2.5.1 进行实际1. 实际环境开发工具:IntelliJ IDEA 2021.1.1 (Ultimate Edition)jdk 1.8Apache Maven 3.6.3docker 20.10.5minikube v1.18.1spring boot 2.5.1 2. 创立一个 spring boot 我的项目1. 应用 idea 创立一个 spring boot 我的项目: 2. pom.xml 的依赖配置如下:<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.1</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>probedemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>probedemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 用来做健康检查的 starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>3. 创立一个监听类,能够监听存活和就绪状态的变动:package com.example.probedemo.listener;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.availability.AvailabilityChangeEvent;import org.springframework.boot.availability.AvailabilityState;import org.springframework.context.event.EventListener;import org.springframework.stereotype.Component;/** * 监听系统事件的类 * * @className: AvailabilityListener * @date: 2021/6/15 10:44 */@Slf4j@Componentpublic class AvailabilityListener { /** * 基于 spring 的事件监听机制,监听系统的音讯 * 当监听到 AvailabilityChangeEvent 事件会触发此办法的调用 * 这里应用日志记录事件的状态 * @param event */ @EventListener public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) { log.info(event.getState().getClass().getSimpleName() + ": " + event.getState()); }}@EventListener 注解阐明: ...

June 20, 2021 · 3 min · jiezi

关于springboot:spring-boot-应用在-k8s-中的健康检查二

一、概述在 spring boot 2.3 中引入了容器探针,也就是减少了 /actuator/health/liveness 和 /actuator/health/readiness 这两个健康检查门路,对于部署在 k8s 中的利用,spring-boot-actuator 将通过这两个门路主动进行健康检查。本文次要依据官网文档的形容实际并记录应用流程,从如下几个方面进行介绍: k8s 中的健康检查spring-boot-actuator 中的 k8s 探针spring boot 健康检查在 k8s 中的实际二、spring-boot-actuator 中的 k8s 探针1、spring boot 对 k8s 环境的判断官网文档如下图所示,SpringBoot 判断是否是 kubernetes 环境的逻辑很简略,查看是否有 *_SERVICE_HOST 和*_SERVICE_PORT 这两个环境变量: 对于相熟 k8s 的小伙伴应该会想起 KUBERNETES_SERVICE_HOST 和 KUBERNETES_SERVICE_PORT,这是 k8s 给 pod 中配置的环境变量,所以咱们能够晓得 SpringBoot 是针对 k8s 的这个规定来断定是否是容器环境的。通过上面的操作查看一下 pod 中的这两个环境变量: 2、验证上述规定创立一个 spring boot 利用,其中 pom.xml 的 parent 信息如下(我这里应用的 2.4.5,须要 >= 2.3.x 版本): <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.4.5</version> <relativePath/> <!-- lookup parent from repository --></parent>并且增加 spring-boot-starter-actuator 的依赖: ...

June 20, 2021 · 1 min · jiezi

关于springboot:spring-boot-应用在-k8s-中的健康检查一

一、概述在 spring boot 2.3 中引入了容器探针,也就是减少了 /actuator/health/liveness 和 /actuator/health/readiness 这两个健康检查门路,对于部署在 k8s 中的利用,spring-boot-actuator 将通过这两个门路主动进行健康检查。本文次要依据官网文档的形容实际并记录应用流程,从如下几个方面进行介绍: k8s 中的健康检查spring-boot-actuator 中的 k8s 探针spring boot 健康检查在 k8s 中的实际二、K8s 容器健康检查1、k8s 中的探针kubernetes 提供了三种探针(反对exec、tcp和http形式)来探测容器的状态: LivenessProbe:容器存活性查看,用于判断容器是否衰弱,通知 kubelet 一个容器什么时候处于不衰弱的状态。如果 LivenessProbe 探针探测到容器不衰弱,则 kubelet 将删除该容器,并依据容器的重启策略做相应的解决。如果一个容器不蕴含 LivenessProbe 探针,那么 kubelet 认为该容器的 LivenessProbe 探针返回的值永远是 Success;ReadinessProbe:容器就绪性查看,用于判断容器是否启动实现且筹备接管申请。如果ReadinessProbe探针探测到失败,Endpoint Controller 将从 Service 的 Endpoint 中删除蕴含该容器所在 Pod 的 IP 地址的Endpoint条目。如果容器不提供就绪态探针,则默认状态为 Success。startupProbe: 容器启动查看,批示容器中的利用是否曾经启动。如果提供了启动探针,则所有其余探针都会被禁用,直到此探针胜利为止。如果启动探测失败,kubelet 将杀死容器,而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为 Success。startupProbe 是在k8s v1.16退出了alpha版,官网对其作用的解释是:Indicates whether the application within the Container is started. All other probes are disabled if a startup probe is provided, until it succeeds. If the startup probe fails, the kubelet kills the Container, and the Container is subjected to its restart policy. If a Container does not provide a startup probe, the default state is Success2、k8s 的探针处理程序Probe 探针查看是由 kubelet 对容器执行的定期诊断,kubelet 调用由容器实现的 Handler (处理程序),每一个探针都能够配置如下三种类型的处理程序: ...

June 20, 2021 · 2 min · jiezi

关于springboot:个人学习系列-Spring-Boot-实现线程池

当初因为零碎越来越简单,导致很多接口速度变慢,这时候就会想到能够利用线程池来解决一些耗时并不影响零碎的操作。新建Spring Boot我的项目1. ExecutorConfig.xml新建线程池配置文件。 @Configuration@EnableAsyncpublic class ExecutorConfig { private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class); @Value("${async.executor.thread.core_pool_size}") private int corePoolSize; @Value("${async.executor.thread.max_pool_size}") private int maxPoolSize; @Value("${async.executor.thread.queue_capacity}") private int queueCapacity; @Value("${async.executor.thread.name.prefix}") private String namePrefix; @Bean(name = "asyncServiceExecutor") public Executor asyncServiceExecutor() { logger.info("start asyncServiceExecutor"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置外围线程数 executor.setCorePoolSize(corePoolSize); //配置最大线程数 executor.setMaxPoolSize(maxPoolSize); //配置队列大小 executor.setQueueCapacity(queueCapacity); //配置线程池中的线程的名称前缀 executor.setThreadNamePrefix(namePrefix); // rejection-policy:当pool曾经达到max size的时候,如何解决新工作 // CALLER_RUNS:不在新线程中执行工作,而是有调用者所在的线程来执行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //执行初始化 executor.initialize(); return executor; }}2. application.yml@Value配置在application.yml,能够参考配置 # 异步线程配置async: executor: thread: # 配置外围线程数 core_pool_size: 10 # 配置最大线程数 max_pool_size: 20 # 配置队列大小 queue_capacity: 99999 # 配置线程池中的线程的名称前缀 name: prefix: async-service-3. AsyncService.java创立一个 Service 接口,是异步线程的接口,将办法写入其实现类即可 ...

June 17, 2021 · 3 min · jiezi

关于springboot:分布式事务Saga模式

1 Saga相干概念1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何解决long lived transaction(长活事务)。Saga是一个长活事务可被分解成能够交织运行的子事务汇合。其中每个子事务都是一个放弃数据库一致性的实在事务。论文地址:sagas 1.1 Saga的组成每个Saga由一系列sub-transaction Ti 组成每个Ti 都有对应的弥补动作Ci,弥补动作用于撤销Ti造成的后果能够看到,和TCC相比,Saga没有“预留”动作,它的Ti就是间接提交到库。 Saga的执行程序有两种: T1, T2, T3, ..., TnT1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < nSaga定义了两种复原策略: backward recovery,向后复原,弥补所有已实现的事务,如果任一子事务失败。即下面提到的第二种执行程序,其中j是产生谬误的sub-transaction,这种做法的成果是撤销掉之前所有胜利的sub-transation,使得整个Saga的执行后果撤销。forward recovery,向前复原,重试失败的事务,假如每个子事务最终都会胜利。实用于必须要胜利的场景,执行程序是相似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中j是产生谬误的sub-transaction。该状况下不须要Ci。显然,向前复原没有必要提供弥补事务,如果你的业务中,子事务(最终)总会胜利,或弥补事务难以定义或不可能,向前复原更合乎你的需要。 实践上弥补事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种状况下咱们能做些什么? 最初的伎俩是提供回退措施,比方人工干预。 1.2 Saga的应用条件Saga看起来很有心愿满足咱们的需要。所有长活事务都能够这样做吗?这里有一些限度: Saga只容许两个档次的嵌套,顶级的Saga和简略子事务在外层,全原子性不能失去满足。也就是说,sagas可能会看到其余sagas的局部后果每个子事务应该是独立的原子行为在咱们的业务场景下,各个业务环境(如:航班预订、租车、酒店预订和付款)是天然独立的行为,而且每个事务都能够用对应服务的数据库保障原子操作。弥补也有需思考的事项: 弥补事务从语义角度吊销了事务Ti的行为,但未必能将数据库返回到执行Ti时的状态。(例如,如果事务触发导弹发射, 则可能无奈吊销此操作)但这对咱们的业务来说不是问题。其实难以吊销的行为也有可能被弥补。例如,发送电邮的事务能够通过发送解释问题的另一封电邮来弥补。 对于ACID的保障: Saga对于ACID的保障和TCC一样: 原子性(Atomicity):失常状况下保障。一致性(Consistency),在某个工夫点,会呈现A库和B库的数据违反一致性要求的状况,然而最终是统一的。隔离性(Isolation),在某个工夫点,A事务可能读到B事务局部提交的后果。持久性(Durability),和本地事务一样,只有commit则数据被长久。Saga不提供ACID保障,因为原子性和隔离性不能失去满足。原论文形容如下: full atomicity is not provided. That is, sagas may view the partial results of other sagas 通过saga log,saga能够保障一致性和持久性。 和TCC比照 Saga相比TCC的毛病是短少预留动作,导致弥补动作的实现比拟麻烦:Ti就是commit,比方一个业务是发送邮件,在TCC模式下,先保留草稿(Try)再发送(Confirm),撤销的话间接删除草稿(Cancel)就行了。而Saga则就间接发送邮件了(Ti),如果要撤销则得再发送一份邮件阐明撤销(Ci),实现起来有一些麻烦。 如果把下面的发邮件的例子换成:A服务在实现Ti后立刻发送Event到ESB(企业服务总线,能够认为是一个消息中间件),上游服务监听到这个Event做本人的一些工作而后再发送Event到ESB,如果A服务执行弥补动作Ci,那么整个弥补动作的层级就很深。 不过没有预留动作也能够认为是长处: 有些业务很简略,套用TCC须要批改原来的业务逻辑,而Saga只须要增加一个弥补动作就行了。TCC起码通信次数为2n,而Saga为n(n=sub-transaction的数量)。有些第三方服务没有Try接口,TCC模式实现起来就比拟tricky了,而Saga则很简略。没有预留动作就意味着不用放心资源开释的问题,异样解决起来也更简略(请比照Saga的复原策略和TCC的异样解决)。2 Saga相干实现Saga Log ...

June 17, 2021 · 1 min · jiezi

关于springboot:SpringBoot基础系列Value-之字面量及-SpEL使用知识点介绍篇

【SpringBoot 根底系列】@Value 之字面量及 SpEL 知识点介绍篇承接上一篇博文【SpringBoot 根底系列】@Value 中哪些你不晓得的知识点 中提及到但没有细说的知识点,这一篇博文将来看一下@Value除了绑定配置文件中的属性配置之外,另外反对的两种姿态 字面量表达式反对SpEL 语法反对<!-- more --> I. 我的项目环境1. 我的项目依赖本我的项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发 开一个 web 服务用于测试 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency></dependencies>II. @Value 知识点上一篇的博文晓得通过${}能够获取配置文件中对应的配置值,接下来咱们看一下另外两种常见的姿态 1. 字面量字面量的应用比较简单,间接在@Value注解中写常量 一个 demo 如下 @Value("1 + 2")private String common;下面这种初始化之后,common 的值会是 1 + 2;如果只是这种用法,这个货色就有些鸡肋了,我间接赋值不香嘛,为啥还有这样多此一举呢? 当然事实中(至多我无限的代码接触中),纯下面这种写法的不多,更常见的是上面这种 @Value("demo_${auth.jwt.token}")private String prefixConf;字面量 + 配置联结应用,如咱们的配置文件值为 auth: jwt: token: TOKEN.123下面的 prefixConf 的取值,理论为 demo_TOKEN.123 2. SpEL 表达式@Value 另外一个很强的应用姿态是反对 SpEL 表达式,至于 SpEL 是什么鬼,举荐查看【SpringBoot 根底系列】SpEL 语法扫盲与查问手册 ...

June 16, 2021 · 2 min · jiezi

关于springboot:三springBoot对接rabbitMq

前情提要:rabbitmq 治理界面查看姿态 一、疾速搭建/根本信息发送和生产 1、引入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId></dependency>2、application.yml spring: rabbitmq: host: ipXXX port: 5672 username: 账户XXX password: 明码XXX virtual-host: /wen # 交换器名称以 direct模式为例 1、配置文件import org.springframework.amqp.core.Binding;import org.springframework.amqp.core.BindingBuilder;import org.springframework.amqp.core.DirectExchange;import org.springframework.amqp.core.Queue;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.HashMap;import java.util.Map;/** * @Author : JCccc * @CreateTime : 2019/9/3 * @Description : **/@Configurationpublic class RabbitConfig { //队列 起名:TestDirectQueue @Bean public Queue emailQueue() { // durable:是否长久化,默认是false,长久化队列:会被存储在磁盘上,当音讯代理重启时依然存在,暂存队列:以后连贯无效 // exclusive:默认也是false,只能被以后创立的连贯应用,而且当连贯敞开后队列即被删除。此参考优先级高于durable // autoDelete:是否主动删除,当没有生产者或者消费者应用此队列,该队列会主动删除。 // return new Queue("TestDirectQueue",true,true,false); //个别设置一下队列的长久化就好,其余两个就是默认false return new Queue("email.fanout.queue", true); } @Bean public Queue smsQueue() { // durable:是否长久化,默认是false,长久化队列:会被存储在磁盘上,当音讯代理重启时依然存在,暂存队列:以后连贯无效 // exclusive:默认也是false,只能被以后创立的连贯应用,而且当连贯敞开后队列即被删除。此参考优先级高于durable // autoDelete:是否主动删除,当没有生产者或者消费者应用此队列,该队列会主动删除。 // return new Queue("TestDirectQueue",true,true,false); //个别设置一下队列的长久化就好,其余两个就是默认false return new Queue("sms.fanout.queue", true); } @Bean public Queue weixinQueue() { // durable:是否长久化,默认是false,长久化队列:会被存储在磁盘上,当音讯代理重启时依然存在,暂存队列:以后连贯无效 // exclusive:默认也是false,只能被以后创立的连贯应用,而且当连贯敞开后队列即被删除。此参考优先级高于durable // autoDelete:是否主动删除,当没有生产者或者消费者应用此队列,该队列会主动删除。 // return new Queue("TestDirectQueue",true,true,false); //个别设置一下队列的长久化就好,其余两个就是默认false return new Queue("weixin.fanout.queue", true); } @Bean public Queue TTLQueue() { Map<String, Object> map = new HashMap<>(16); map.put("x-message-ttl", 30000); // 队列中的音讯未被生产则30秒后过期 return new Queue("TTL_QUEUE", true, false, false, map); } @Bean public DirectExchange TTLExchange() { return new DirectExchange("TTL_EXCHANGE", true, false); } //Direct交换机 起名:TestDirectExchange @Bean public DirectExchange fanoutOrderExchange() { // return new DirectExchange("TestDirectExchange",true,true); return new DirectExchange("fanout_exchange", true, false); } //绑定 将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting @Bean public Binding bindingDirect() { return BindingBuilder.bind(TTLQueue()).to(TTLExchange()).with("TTL"); } @Bean public Binding bindingDirect1() { return BindingBuilder.bind(weixinQueue()).to(fanoutOrderExchange()).with(""); } @Bean public Binding bindingDirect2() { return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange()).with(""); } @Bean public Binding bindingDirect3() { return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange()).with(""); }}2、生产者package com.pit.barberShop.common.MQ.Rabbit.fanout;import org.springframework.amqp.core.Message;import org.springframework.amqp.core.MessageProperties;import org.springframework.amqp.rabbit.core.RabbitTemplate;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author :wenye * @date :Created in 2021/6/15 21:41 * @description:播送模式 * @version: $ */@RestController@RequestMapping("/rabbitmq")public class ProducerFanout { @Autowired private RabbitTemplate rabbitTemplate; // 1: 定义交换机 private String exchangeName = "fanout_exchange"; // 2: 路由key private String routeKey = ""; @RequestMapping("/fanout") public void markerFanout() { String message ="shua"; // 发送音讯 rabbitTemplate.convertAndSend(exchangeName, routeKey, message); } @RequestMapping("/ttl") public String testTTL() { MessageProperties messageProperties = new MessageProperties(); messageProperties.setExpiration("20000"); // 设置过期工夫,单位:毫秒 byte[] msgBytes = "测试音讯主动过期".getBytes(); Message message = new Message(msgBytes, messageProperties); rabbitTemplate.convertAndSend("TTL_EXCHANGE", "TTL", message); return "ok"; }}3、消费者package com.pit.barberShop.common.MQ.Rabbit.fanout;import org.springframework.amqp.rabbit.annotation.RabbitHandler;import org.springframework.amqp.core.ExchangeTypes;import org.springframework.amqp.rabbit.annotation.*;import org.springframework.stereotype.Component;/** * @author :wenye * @date :Created in 2021/6/15 22:07 * @description:fanout消费者 * @version: $ */@Componentpublic class ConsumerFanout { @RabbitListener(bindings =@QueueBinding( // email.fanout.queue 是队列名字,这个名字你能够自定轻易定义。 value = @Queue(value = "sms.fanout.queue",autoDelete = "false"), // order.fanout 交换机的名字 必须和生产者保持一致 exchange = @Exchange(value = "fanout_exchange", // 这里是确定的rabbitmq模式是:fanout 是以播送模式 、 公布订阅模式 type = ExchangeTypes.DIRECT) )) public void messagerevice(String message){ // 此处省略发邮件的逻辑 System.out.println("sms-two111------------->" + message); } @RabbitListener(bindings =@QueueBinding( // email.fanout.queue 是队列名字,这个名字你能够自定轻易定义。 value = @Queue(value = "weixin.fanout.queue",autoDelete = "false"), // order.fanout 交换机的名字 必须和生产者保持一致 exchange = @Exchange(value = "fanout_exchange", // 这里是确定的rabbitmq模式是:fanout 是以播送模式 、 公布订阅模式 type = ExchangeTypes.DIRECT) )) public void messageWXrevice(String message){ // 此处省略发邮件的逻辑 System.out.println("weixin----two---------->" + message); }}二、过期工夫 ...

June 16, 2021 · 6 min · jiezi

关于springboot:SpringBoot2-参数管理实践入参出参与校验

一、参数治理在编程零碎中,为了能写出良好的代码,会依据是各种设计模式、准则、束缚等去标准代码,从而进步代码的可读性、复用性、可批改,实际上集体感觉,如果写出的代码很好,即他人批改也无奈毁坏原作者的思路和封装,这应该是十分高水准。 然而在日常开发中,碍于很多客观因素,很少有工夫去一直思考和优化代码,所以只能从理论状况的角度去思考如何构建零碎代码,保障当前本人还能读懂本人的代码,在本人的几年编程中,理论会思考如下几个方面:代码层级治理,命名和正文对立,正当的设计业务数据库,明确参数格调。 这里就来聊一下参数治理,围绕:入参、校验、返参三个方面内容。 如何了解代码标准这个概念:即大多数开发认同,违心恪守的束缚,例如Spring框架和Mvc模式对于工程的治理,《Java开发手册》中对于业务开发的规定,其基本目标都是想防止随着业务倒退,代码演变到无奈保护的境界。 二、接管参数接管参数形式有很多种,List,Map,Object等等,然而为了明确参数的语义,通常都须要设计参数对象的构造并且恪守肯定的标准,例如明确禁止Map接管参数: Rest格调接管单个ID参数: @GetMapping("/param/single/{id}")public String paramSingle (@PathVariable Integer id){ return "Resp:"+id ;}接管多个指定的参数: @GetMapping("/param/multi")public String paramMulti (@RequestParam("key") String key, @RequestParam("var") String var){ return "Resp:"+key+var ;}基于Java包装对象入参: @PostMapping("/param/wrap")public ParamIn paramWrap (@RequestBody ParamIn paramIn){ return paramIn ;}-- 参数对象实体public class ParamIn { private Integer id ; private String key ; private String var ; private String name ;}以上是在开发中罕用的几种接参形式,这里通常会恪守上面几个习惯: 参数语义:明确接管参数的作用;个数限度:参数超过三个应用包装对象;防止多个接口应用单个包装对象入参;防止包装对象主体过于简单;参数接管并没有很简单的束缚,整体上也比拟容易恪守,通常的问题在于解决较大主体对象时,容易产生一个包装对象被多处复用,进而导致对象字段属性很多,这种状况在简单业务中尤其容易呈现,这种对象并不利于web层接口应用,或者很多时候都会在业务层和接口层混用对象; 在业务层封装简单的BO对象来升高业务管理的复杂度,这是正当常见的操作,能够在web接口层面依据接口性能各自治理入参主体,在业务实现的过程中,再传入BO对象中。 防止简单的业务包装对象在各个层乱飘,如果多个接口入参都是同一个简单的对象,很容易让开发人员迷茫。 三、响应参数与参数接管绝对应的就是参数响应,参数响应通常具备明确的束缚标准:响应主体数据,响应码,形容信息。通常来说就是这样三个外围因素。 响应参数主体: 这里泛型的应用通常用来做主体数据的接管。 public class Resp<T> { private int code ; private String msg ; private T data ; public static <T> Resp<T> ok (T data) { Resp<T> result = new Resp<>(HttpStatus.OK); result.setData(data); return result ; } public Resp (HttpStatus httpStatus) { this.code = httpStatus.value(); this.msg = httpStatus.getReasonPhrase(); } public Resp(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; }}Code状态码 ...

June 16, 2021 · 3 min · jiezi

关于springboot:SpringCloud入门6Config

前言曾经4天没有更新了,怎么说呢,就很忙,很忙,很忙。次要还是毕业以及从一个城市到另外一个城市的事件,这段时间尽最大可能放弃2,3天一更,最晚不超过4,5天一更。 随着模块的增多,会呈现配置文件繁冗的通病,每次都要关上好多层目录能力找到配置文件,SpringCloud中的Config组件就是为了解决这个问题,通过简略的配置就能实现配置文件的对立治理。 Config服务端引入Config服务端创立Config空父模块,在上面建设一个config-server子模块,批改子模块的pom文件 留神是子模块的pom文件,不像以前一样批改的是空父模块的pom文件<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency></dependencies>配置文件因为临时没必要注册进Eureka中,所以配置文件的编写还是比较简单的 server: port: 8101spring: cloud: config: server: git: uri: https://gitee.com/cutey_none/springcloud-study-config username: password: label: masterspring.cloud.config.server.git.uri :寄存文件的地址,到时候客户端就从这里获取配置文件,能够本地,也能够是git 如果是公开仓库,那么username 和password 不必写因为创立的springcloud-study-config 仓库设置的权限凋谢的,所以间接用我的也行,本人创立也行,就失常github或者gitee创立一个仓库就好。 仓库搁置的是各个微服务的配置文件例子是治理config-client微服务(前面会创立)的配置文件,所以须要在仓库中创立一个config-client-dev.properties(-dev示意是开发环境下的配置文件) config-client-dev.properties 文件的内容如下,能够看作是config-client服务的某些配置 主启动类减少@EnableConfigServer 注解以提供config服务反对 @SpringBootApplication@EnableConfigServerpublic class ConfigServer8101 { public static void main(String[] args) { SpringApplication.run(ConfigServer8101.class, args); }}测试SpringCloud Config有本人http服务拜访资源的模式 /{application}/{profile}[/{label}] >> /config-client/dev/{application}-{profile}.yml >> /config-client-dev.yml/{label}/{application}-{profile}.yml >> /master/config-client-dev.yml/{application}-{profile}.properties >> /config-client-dev.properties/{label}/{application}-{profile}.properties >> /master/config-client-profile.properties单个运行ConfigServer8101我的项目即可,用上述5种形式拜访资源,能够看到均能胜利从服务端拜访资源 config客户端引入config客户端后面始终说的config-client 就是接下来要创立的模块,最终的我的项目构造目录如下 接着批改config-client9501 模块的pom文件,留神服务端和客户端引入的依赖是不一样的 <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency></dependencies>配置文件配置文件留神命名是bootstrap.yml因为客户端要设置服务端的uri,所以应该优先加载客户端配置文件 ...

June 14, 2021 · 1 min · jiezi

关于springboot:翻译使用Spring-Boot进行单元测试

原文地址:https://reflectoring.io/unit-... 编写好的单元测试能够被看成一个很难把握的艺术。但好消息是反对单元测试的机制很容易学习。 本文给你提供在Spring Boot 应用程序中编写好的单元测试的机制,并且深刻技术细节。 咱们将带你学习如何以可测试的形式创立Spring Bean实例,而后探讨如何应用Mockito和AssertJ,这两个包在Spring Boot中都为了测试默认援用了。 本文只探讨单元测试。至于集成测试,测试web层和测试长久层将会在接下来的系列文章中进行探讨。 代码示例本文附带的代码示例地址:spring-boot-testing 应用 Spring Boot 进行测试系列文章这个教程是一个系列: 应用 Spring Boot 进行单元测试(本文)应用 Spring Boot 和 @WebMvcTest 测试SpringMVC controller层应用 Spring Boot 和 @DataJpaTest 测试JPA长久层查问通过 @SpringBootTest 进行集成测试如果你喜爱看视频教程,能够看看Philip的课程:测试Spring Boot应用程序课程 依赖项本文中,为了进行单元测试,咱们会应用JUnit Jupiter(Junit 5),Mockito和AssertJ。此外,咱们会援用Lombok来缩小一些模板代码: dependencies{ compileOnly('org.projectlombok:lombok') testCompile('org.springframework.boot:spring-boot-starter-test') testCompile 'org.junit.jupiter:junit-jupiter-engine:5.2.0' testCompile('org.mockito:mockito-junit-jupiter:2.23.0')}Mockito和AssertJ会在spring-boot-test依赖中主动援用,然而咱们须要本人援用Lombok。 不要在单元测试中应用Spring如果你以前应用Spring或者Spring Boot写过单元测试,你可能会说咱们不要在写单元测试的时候用Spring。然而为什么呢? 思考上面的单元测试类,这个类测试了RegisterUseCase类的单个办法: @ExtendWith(SpringExtension.class)@SpringBootTestclass RegisterUseCaseTest { @Autowired private RegisterUseCase registerUseCase; @Test void savedUserHasRegistrationDate() { User user = new User("zaphod", "zaphod@mail.com"); User savedUser = registerUseCase.registerUser(user); assertThat(savedUser.getRegistrationDate()).isNotNull(); }}这个测试类在我的电脑上须要大略4.5秒来执行一个空的Spring我的项目。 然而一个好的单元测试仅仅须要几毫秒。否则就会妨碍TDD(测试驱动开发)流程,这个流程提倡“测试/开发/测试”。 然而就算咱们不应用TDD,期待一个单元测试太久也会毁坏咱们的注意力。 ...

June 10, 2021 · 2 min · jiezi

关于springboot:springboot整合activiti前端vue

前言activiti是目前比拟风行的工作流框架,然而activiti学起来还是吃力,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和零碎权限绑定,这些问题都是要思考到的,不是说纯正的把activiti整合到零碎中就完事了。现在国内比拟风行的是前后端拆散模式,之前都是前后端放一个工程外面,接口格调很乱,并且不好保护,前后端开发不是很不便。目前前端都做成了工程化的模式,比方国产的Vue,国外的React等等。为了顺应潮流,我就做了一套springboot-vue-activiti的疾速开发小脚手架,对于大型项目来说还是不太适宜,毕竟来说,一个人的开发能力无限,然而对于中小微型我的项目,并且带有审批业务的我的项目来说,那几乎就是一个福音了。 一、成果展现1.模型设计器 2.流程节点设置审批人员能够依据角色、部门、部门负责人、间接抉择人员等。 3.审批进度查问 二、操作过程1.引入activiti依赖<!-- Activiti 启动器 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter-basic</artifactId> <version>${activiti.version}</version> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </exclusion> </exclusions> </dependency> <!-- Activiti 流程图 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-diagram-rest</artifactId> <version>${activiti.version}</version> </dependency> <!-- Activiti 在线设计 --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-modeler</artifactId> <version>${activiti.version}</version> </dependency>2.编辑器代码及汉化 3.application.yml配置activiti: check-process-definitions: false #启用作业执行器 async-executor-activate: false #启用异步执行器 job-executor-activate: false 4.数据库表 三、业务表单和零碎权限绑定1.思路1.新建一张流程定义扩大表(用来存储流程部署的根本信息、关联业务表名、前端路由信息、业务表单类型等),流程部署完后往流程定义扩大表插入部署根本信息,而后编辑已公布的流程,往扩大表中插入本流程的分类,关联表单信息。2.对于自定义节点设置,次要是用来定义每个节点由哪个角色,或者具体哪个人来审批。建设一张节点扩大表(存储节点流转信息,指定人ID、角色ID,等)。 2.局部代码// 获取模型 Model modelData = repositoryService.getModel(id); byte[] bytes = repositoryService.getModelEditorSource(modelData.getId()); if (bytes == null) { return Result.error("模型数据为空,请先胜利设计流程并保留"); } try { JsonNode modelNode = new ObjectMapper().readTree(bytes); BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode); if(model.getProcesses().size()==0){ return Result.error("模型不符要求,请至多设计一条主线流程"); } byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model); // 部署公布模型流程 String processName = modelData.getName() + ".bpmn20.xml"; Deployment deployment = repositoryService.createDeployment() .name(modelData.getName()) .addString(processName, new String(bpmnBytes, "UTF-8")) .deploy(); String metaInfo = modelData.getMetaInfo(); JSONObject metaInfoMap = JSON.parseObject(metaInfo); // 设置流程分类,保留扩大流程至数据库 List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list(); for (ProcessDefinition pd : list) { ActZprocess actZprocess = new ActZprocess(); actZprocess.setId(pd.getId()); actZprocess.setName(modelData.getName()); actZprocess.setProcessKey(modelData.getKey()); actZprocess.setDeploymentId(deployment.getId()); actZprocess.setDescription(metaInfoMap.getString(ModelDataJsonConstants.MODEL_DESCRIPTION)); actZprocess.setVersion(pd.getVersion()); actZprocess.setDiagramName(pd.getDiagramResourceName()); actZprocessService.setAllOldByProcessKey(modelData.getKey()); actZprocess.setLatest(true); actZprocessService.save(actZprocess); } }catch (Exception e){ String err = e.toString(); log.error(e.getMessage(),e); if (err.indexOf("NCName")>-1){ return Result.error("部署失败:流程设计中的流程名称不能为空,不能为数字以及特殊字符结尾!"); } if (err.indexOf("PRIMARY")>-1){ return Result.error("部署失败:该模型已公布,key惟一!"); } return Result.error("部署失败!"); } return Result.ok("部署胜利");/*获取高亮实时流程图*/ @RequestMapping(value = "/getHighlightImg/{id}", method = RequestMethod.GET) public void getHighlightImg(@PathVariable String id, HttpServletResponse response){ InputStream inputStream = null; ProcessInstance pi = null; String picName = ""; // 查问历史 HistoricProcessInstance hpi = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult(); if (hpi.getEndTime() != null) { // 曾经完结流程获取原图 ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().processDefinitionId(hpi.getProcessDefinitionId()).singleResult(); picName = pd.getDiagramResourceName(); inputStream = repositoryService.getResourceAsStream(pd.getDeploymentId(), pd.getDiagramResourceName()); } else { pi = runtimeService.createProcessInstanceQuery().processInstanceId(id).singleResult(); BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId()); List<String> highLightedActivities = new ArrayList<String>(); // 高亮工作节点 List<Task> tasks = taskService.createTaskQuery().processInstanceId(id).list(); for (Task task : tasks) { highLightedActivities.add(task.getTaskDefinitionKey()); } List<String> highLightedFlows = new ArrayList<String>(); ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator(); //"宋体" inputStream = diagramGenerator.generateDiagram(bpmnModel, "png", highLightedActivities, highLightedFlows, "宋体", "宋体", "宋体",null, 1.0); picName = pi.getName()+".png"; } try { response.setContentType("application/octet-stream;charset=UTF-8"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(picName, "UTF-8")); byte[] b = new byte[1024]; int len = -1; while ((len = inputStream.read(b, 0, 1024)) != -1) { response.getOutputStream().write(b, 0, len); } response.flushBuffer(); } catch (IOException e) { log.error(e.toString()); throw new JeecgBootException("读取流程图片失败"); } }总结下面只是展现了局部代码和局部效果图,对于一个失常的审批流转业务来说,提交审批,申请人撤销,从新申请,审批人驳回,委托别人审批,会签,流程挂起,流程实时跟踪,并行网关,排他网关,监听,待办,已办音讯告诉等都是必须要的,我这就不一一展示了,如有须要,能够+q:2500564056。 ...

June 9, 2021 · 2 min · jiezi

关于springboot:使用SpringBoot-WebSocket实现单人聊天

前言最近在做一个聊天性能,具体需要:相似微信,在一个好友列表中,点击某个好友就能够建设与该好友的聊天连贯,向该好友发送音讯,对方可能实时显示进去,进行真正意义上的聊天。在做之前,不论在界面布局,还是性能实现方面都下了一点功夫,最终还是一点点实现了,当初就记录一下。在编码之前得先理解一下WebSocket 什么是WebSocket?WebSocket,即Web浏览器与Web服务器之间全双工通信规范;是HTML5中的协定,反对长久间断,http协定不反对持久性连贯。Http1.0和HTTP1.1都不反对持久性的链接,HTTP1.1中的keep-alive,将多个http申请合并为1个一旦确立WebSocket通信连贯,不管服务器还是客户端,任意一方都可间接向对方发送报文WebSocket特点?推送性能:反对由服务器向客户端推送数据的推送性能,这样,服务器可间接发送数据,而不用期待客户端的申请缩小通信量:只有建设起WebSocket,就能够始终放弃连贯状态头部字段多了上面2个属性: Upgrade:webSocketConnection:Upgrade 1、实现成果 点击左侧好友列表时,会建设websocket连贯,把以后发消息的用户发送给websocket服务器 输出音讯 2、前端实现<!-- Chat.vue页面 --><template> <div id="chat"> <!-- 聊天音讯治理 --> <el-container style="height: 620px; border: 1px solid #eee"> <el-aside width="250px"> <user-list :friendList="this.friendList" @set-contact="getcontact" ref="friends" :activeIndex="activeIndex" ></user-list> </el-aside> <el-container> <el-header style="text-align: right; font-size: 12px"> <span> <h2>{{this.username}}</h2> </span> </el-header> <el-main style="height:400px;" class="msg-main"> <chat-msg ref="msg" :user="this.username" id="msg-box"></chat-msg> </el-main> <div class="m-text"> <textarea placeholder="按 Ctrl + Enter 发送" ref="sendMsg" v-model="contentText" @keyup.enter="sendText()" ></textarea> <div class="btn" :class="{['btn-active']:contentText}" @click="sendText()">发送</div> </div> </el-container> </el-container> </div></template><script>import UserList from "../../components/chat/friendList";import ChatMsg from "../../components/chat/message";import InputText from "../../components/chat/text";export default { data() { return { //好友列表 friendList: [], activeIndex: null, //以后聊天好友 activeFriend: [], friends: "", ws: null, count: 0, userId: this.$store.state.userInfo.uid, // 以后用户ID username: this.$store.state.userInfo.username, // 以后用户昵称 avatar: this.$store.state.userInfo.uavatar, // 以后用户头像 msgList: [], // 聊天记录的数组 contentText: "" // input输出的值 }; }, components: { UserList, ChatMsg, InputText }, mounted() { this.getFridends(this.userId); }, destroyed() { // 来到页面时敞开websocket连贯 this.ws.onclose(undefined); }, methods: { select(value) { this.activeIndex = this.friendList.indexOf(value); }, getcontact(list) { console.log(this.$store.state.userInfo); this.activeFriend = list; //保留以后聊天好友 this.friends = list.ffriendName; this.activeIndex = this.friendList.indexOf(this.friends); this.getFridendInfo(this.friends); this.initWebSocket(this.username); this.getFridendMsg(this.friends); }, // 发送聊天信息 sendText() { let _this = this; _this.$refs["sendMsg"].focus(); if (!_this.contentText) { return; } let params = { mfromuser: _this.username, mtouser: _this.activeFriend[0].uusername, mfromavatar: _this.activeFriend[0].uavatar, mtoavatar: _this.avatar, mmsg: _this.contentText, mtype: 1 }; _this.ws.send(JSON.stringify(params)); //调用WebSocket send()发送信息的办法 _this.contentText = ""; setTimeout(() => { _this.scrollToBottom(); }, 200); }, // 进入页面创立websocket连贯 initWebSocket(id) { let _this = this; // 判断页面有没有存在websocket连贯 if (window.WebSocket) { var serverHot = window.location.hostname; // 填写本地IP地址,此处的 :8021端口号要与后端配置的统一! var url = "ws://" + serverHot + ":8021" + "/websocket/" + id; // `ws://127.0.0.1/9101/websocket/10086/老王` let ws = new WebSocket(url); _this.ws = ws; ws.onopen = function(e) { console.log("服务器连贯胜利: " + url); }; ws.onclose = function(e) { console.log("服务器连贯敞开: " + url); }; ws.onerror = function() { console.log("服务器连贯出错: " + url); }; ws.onmessage = function(e) { //接管服务器返回的数据 console.log(e); let resData = e.data.split("|"); console.log(resData); //更新音讯 if (resData[0] == _this.username) { _this.getFridendMsg(_this.friends); } else { _this.$message.error("发送音讯失败,以后用户不存在"); } }; } }, //获取好友列表 async getFridends(id) { console.log(id); const { data: res } = await this.$http.get( "api/business/chat/getfriends/" + id ); if (res.success) { // console.log(res); this.friendList = res.data; } else { this.$message.error("获取好友列表失败:" + res.data.errorMsg); } }, //获取好友音讯列表 async getFridendMsg(name) { const { data: res } = await this.$http.get( "api/business/chat/getFriendMsg/" + name ); if (res.success) { // console.log(res); this.msgList = res.data; //把好友聊天记录传给音讯组件 this.$refs.msg.getMsgList(this.msgList); } else { this.$message.error("获取好友音讯失败:" + res.data.errorMsg); } }, //获取好友信息 async getFridendInfo(name) { const { data: res } = await this.$http.get( "api//system/user/getFriendInfo/" + name ); if (res.success) { console.log(res); //好友信息 this.activeFriend = res.data; } else { this.$message.error("获取好友信息失败:" + res.data.errorMsg); } }, // 滚动条到底部 scrollToBottom() { this.$nextTick(() => { var container = this.$el.querySelector(".msg-main"); // console.log(container); // console.log(container.scrollTop); container.scrollTop = container.scrollHeight; }); } }};</script><style scoped lang="less">#chat { overflow: hidden;}.el-header { background-color: #b3c0d1; color: #333; line-height: 60px;}.el-aside { color: #fff; background-color: #2e3238;}.m-list { li { padding: 12px 15px; border-bottom: 1px solid #292c33; cursor: pointer; transition: background-color 0.1s; &:hover { background-color: rgba(255, 255, 255, 0.03); } &.active { background-color: rgba(255, 255, 255, 0.1); } } .avatar, .name { vertical-align: middle; } .avatar { border-radius: 2px; } .name { display: inline-block; margin: 0 0 0 15px; }}.m-message { padding: 10px 15px; li { margin-bottom: 15px; } .time { margin: 7px 0; text-align: center; > span { display: inline-block; padding: 3px 20px; font-size: 12px; color: #fff; border-radius: 2px; background-color: #dcdcdc; } } .avatar { float: left; margin: 0 15px 0 0; border-radius: 3px; } .text { display: inline-block; position: relative; padding: 3px 17px; max-width: ~"calc(100% - 40px)"; min-height: 30px; line-height: 2.5; font-size: 12px; text-align: left; word-break: break-all; background-color: #fafafa; border-radius: 4px; &:before { content: " "; position: absolute; top: 9px; right: 100%; border: 6px solid transparent; border-right-color: #fafafa; } } .self { text-align: right; .avatar { float: right; margin: 0 0 0 10px; } .text { background-color: #b2e281; &:before { right: inherit; left: 100%; border-right-color: transparent; border-left-color: #b2e281; } } }}.m-text { display: flex; height: 90px; border-top: solid 1px #ddd; textarea { padding-left: 10px; height: 100%; width: 100%; border: none; outline: none; font-family: "Micrsofot Yahei"; resize: none; } .btn { height: 2.3rem; min-width: 4rem; background: #e0e0e0; padding: 0.5rem; font-size: 0.88rem; color: white; text-align: center; border-radius: 0.2rem; margin-left: 0.5rem; transition: 0.5s; } .btn-active { background: #409eff; }}</style><!-- friendList.vue --><template> <div class="m-list"> <ul v-for="(friend, i) in friendList"> <li @click="setContact(i)" :class="{'active':currentIndex===i}"> <img class="avatar" width="50" height="50" /> <p class="name">{{friend.ffriendName}}</p> </li> </ul> </div></template><script>export default { data() { return { currentIndex: this.activeIndex }; }, props: ["friendList", "activeIndex"], methods: { setContact(index) { this.currentIndex = index; console.log(this.currentIndex); //与父组件进行通信。把以后点击好友的用户信息传给父组件 this.$emit("set-contact", this.friendList[index]); } }};</script><style lang="less" scoped>.m-list { li { padding: 12px 15px; border-bottom: 1px solid #292c33; cursor: pointer; transition: background-color 0.1s; &:hover { background-color: rgba(255, 255, 255, 0.03); } &.active { background-color: rgba(255, 255, 255, 0.1); } } .avatar, .name { vertical-align: middle; } .avatar { border-radius: 2px; } .name { display: inline-block; margin: 0 0 0 15px; }}</style><!-- message.vue --><template> <div class="m-message"> <ul v-for="item in msgList"> <li> <p class="time"> <span>{{item.mcreateTime | time}}</span> </p> <div class="main" :class="{self:item.mfromUser===username}"> <div class="title">{{item.mfromUser}}</div> <img class="avatar" width="40" height="40" /> <div class="text">{{item.mmsg}}</div> </div> </li> </ul> </div></template><script>export default { data() { return { msgList: this.userList, username: this.user }; }, props: ["user", "userList"], methods: { getMsgList(list) { console.log(list); this.msgList = list; }, }, filters: { // 将日期过滤为 yy-mm-dd hh:mm:ss time(date) { if (typeof date === "string") { date = new Date(date); //把定义的工夫赋值进来进行上面的转换 let year = date.getFullYear(); let month = date.getMonth() + 1; let day = date.getDate(); let hour = date.getHours(); let minute = date.getMinutes(); let second = date.getSeconds(); return ( year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second ); } } }};</script><style lang="less" scoped>.m-message { padding: 10px 15px; // overflow-y: scroll; li { margin-bottom: 15px; } .time { margin: 7px 0; text-align: center; > span { display: inline-block; padding: 3px 20px; font-size: 12px; color: #fff; border-radius: 2px; background-color: #dcdcdc; } } .avatar { float: left; margin: 0 15px 0 0; border-radius: 3px; } .text { display: inline-block; position: relative; padding: 3px 17px; max-width: ~"calc(100% - 40px)"; min-height: 30px; line-height: 2.5; font-size: 12px; text-align: left; word-break: break-all; background-color: #fafafa; border-radius: 4px; &:before { content: " "; position: absolute; top: 9px; right: 100%; border: 6px solid transparent; border-right-color: #fafafa; } } .self { text-align: right; .avatar { float: right; margin: 0 0 0 10px; } .text { background-color: #b2e281; &:before { right: inherit; left: 100%; border-right-color: transparent; border-left-color: #b2e281; } } }}</style>3、后端实现增加依赖 ...

June 8, 2021 · 7 min · jiezi