关于springboot:用户角色菜单查询

多对多,依据用户id,查问用户的多个角色,同时查问每个角色下对应的菜单, 同时过滤出雷同的菜单 用户<-->角色, 角色<-->菜单, 多对多的关系 个别状况下, 菜单不过滤, 前端框架主动反复的数据会笼罩 办法一 @RequestMapping("getMenuByUserid") public List<MenuRoleView> getMenuByUserid(HttpServletRequest request){ int userid = (int) request.getSession().getAttribute("userid"); //依据userid查问登录用户的角色信息 UserRoleViewExample userRoleViewExample=new UserRoleViewExample(); Criteria criteria2 = userRoleViewExample.createCriteria(); userRoleViewMapper.selectByExample(userRoleViewExample); criteria2.andUseridEqualTo(userid); List<UserRoleView> roleList = userRoleViewMapper.selectByExample(userRoleViewExample); List<Integer> roleIds=new ArrayList<Integer>();//角色id的汇合 for (UserRoleView userRoleView : roleList) { roleIds.add(userRoleView.getRoleid()); } //依据userid的汇合,查问 MenuRoleViewExample menuRoleViewExample=new MenuRoleViewExample(); com.bw.entity.MenuRoleViewExample.Criteria criteria = menuRoleViewExample.createCriteria(); criteria.andRoleidIn(roleIds); List<MenuRoleView> menuList = menuRoleViewMapper.selectByExample(menuRoleViewExample); return menuList; }办法二 @RequestMapping("getMenuByUserid2") public List<MenuRoleView> getMenuByUserid2(HttpServletRequest request){ Integer userid = (Integer) request.getSession().getAttribute("userid"); if(userid==null){ return new ArrayList(); } List<MenuRoleView> menuList = menuRoleViewMapper.getMenuByUserid2(userid); //工具类 HashMap<Integer, MenuRoleView> map=new HashMap<Integer, MenuRoleView>(); for (MenuRoleView menuRoleView : menuList) { map.put(menuRoleView.getMenuid(), menuRoleView); } Collection<MenuRoleView> values = map.values(); menuList=new ArrayList<MenuRoleView>(values); return menuList; }

March 22, 2023 · 1 min · jiezi

关于springboot:笑小枫的SpringBoot系列十六SpringBoot生成PDF

本文简介本文次要介绍了在SpringBoot我的项目下,通过代码和操作步骤,具体的介绍了如何操作PDF。心愿能够帮忙到筹备通过JAVA操作PDF的你。 我的项目框架用的SpringBoot,但在JAVA中代码都是通用的。本文波及pdf操作,如下: PDF模板制作基于PDF模板生成,并反对下载自定义中文字体齐全基于代码生成,并保留到指定目录合并PDF,并保留到指定目录合并PDF,并反对下载基于PDF模板生成:实用于固定格局的PDF模板,基于内容进行填空,例如:合同信息生成、固定格局表格等等 齐全基于代码生成:实用于不固定的PDF,例如:动静表格、动静增加某块内容、不确定的内容大小等不确定的场景 PDF文件简介PDF是可移植文档格局,是一种电子文件格式,具备许多其余电子文档格局无奈相比的长处。PDF文件格式能够将文字、字型、格局、色彩及独立于设施和分辨率的图形图像等封装在一个文件中。该格式文件还能够蕴含超文本链接、声音和动静影像等电子信息,反对专长文件,集成度和平安可靠性都较高。在零碎开发中通常用来生成比拟正式的报告或者合同类的电子文档。 代码实现PDF操作首先须要引入咱们的依赖,这里通过maven治理依赖 在pom.xml文件增加以下依赖 <!--pdf操作--><!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --><dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>${itextpdf-version}</version></dependency>基于PDF模板生成,并下载PDF模板制作首先在word或者其他软件外面制作模板,筛选你相熟的软件即可,前提是可生成pdf。 将word文件转为pdf文件。 应用Adobe Acrobat软件操作pdf,这里用的是这个软件,只有能实现这个性能,其余的软件也可~ 抉择表单编辑哈,咱们要在对应的坑上增加表单占位 在表单上增加文本域即可,所有的格局都用文本域即可,这里只是占坑。 对应的域名称要与程序的名称对应,不便前面数据填充,不然前面须要手动解决赋值。 创立个简略的模板吧,要留神填充的空间要短缺,不然会呈现数据展现不全呦~ 成果如下: 好了,到这里模板就生成好了,咱们保留一下,而后放在咱们的/resources/templates目录下 PDF生成代码编写在util包下创立PdfUtil.java工具类,代码如下: package com.maple.demo.util;import com.itextpdf.text.Document;import com.itextpdf.text.DocumentException;import com.itextpdf.text.Image;import com.itextpdf.text.Rectangle;import com.itextpdf.text.pdf.*;import javax.servlet.ServletOutputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.util.Map;import java.util.Objects;/** * @author 笑小枫 * @date 2022/8/15 * @see <a href="https://www.xiaoxiaofeng.com">https://www.xiaoxiaofeng.com</a> */public class PdfUtil { private PdfUtil() { } /** * 利用模板生成pdf * * @param data 写入的数据 * @param photoMap 图片信息 * @param out 自定义保留pdf的文件流 * @param templatePath pdf模板门路 */ public static void fillTemplate(Map<String, Object> data, Map<String, String> photoMap, ServletOutputStream out, String templatePath) { PdfReader reader; ByteArrayOutputStream bos; PdfStamper stamper; try { // 读取pdf模板 reader = new PdfReader(templatePath); bos = new ByteArrayOutputStream(); stamper = new PdfStamper(reader, bos); AcroFields acroFields = stamper.getAcroFields(); // 赋值 for (String name : acroFields.getFields().keySet()) { String value = data.get(name) != null ? data.get(name).toString() : null; acroFields.setField(name, value); } // 图片赋值 for (Map.Entry<String, String> entry : photoMap.entrySet()) { if (Objects.isNull(entry.getKey())) { continue; } String key = entry.getKey(); String url = entry.getValue(); // 依据地址读取须要放入pdf中的图片 Image image = Image.getInstance(url); // 设置图片在哪一页 PdfContentByte overContent = stamper.getOverContent(acroFields.getFieldPositions(key).get(0).page); // 获取模板中图片域的大小 Rectangle signRect = acroFields.getFieldPositions(key).get(0).position; float x = signRect.getLeft(); float y = signRect.getBottom(); // 图片等比缩放 image.scaleAbsolute(signRect.getWidth(), signRect.getHeight()); // 图片地位 image.setAbsolutePosition(x, y); // 在该页退出图片 overContent.addImage(image); } // 如果为false那么生成的PDF文件还能编辑,肯定要设为true stamper.setFormFlattening(true); stamper.close(); Document doc = new Document(); PdfCopy copy = new PdfCopy(doc, out); doc.open(); PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1); copy.addPage(importPage); doc.close(); bos.close(); } catch (IOException | DocumentException e) { e.printStackTrace(); } }}在controller包下创立TestPdfController.java类,并i代码如下: ...

March 21, 2023 · 10 min · jiezi

关于springboot:Spring-Boot配置HTTPS解决微信小程序上线问题

前言因为微信小程序在体验版和上线版本,须要用https连贯,所以你须要申请一个域名,并为这个域名申请证书。怎么利用acme.sh收费申请证书在上篇文章有提到利用acme.sh收费建设https连贯,这里就记录一下Spring Boot中配置HTTPS,再利用Docker进行部署。实现步骤1.生成PKCS12格局的证书文件上一篇中acme.sh收费申请证书后会生成两个文件example.com.key和fullchain.cer example.com.key是私钥文件fullchain.cer是蕴含公钥证书和两头证书链的证书文件 把这两个文件放在同一目录下,并执行一下命令,合并成一个 PKCS12 格局的证书文件:openssl pkcs12 -export -in fullchain.cer -inkey example.com.key -out your_keystore.p12 -name your_alias复制代码 your_keystore.p12 是你要生成的 PKCS12 格局的证书文件名your_alias 是你的证书别名 而后会让你设置一个明码来爱护生成的 PKCS12 格局的证书文件,这个明码要记下来!!!这时候当前目录下就会生成 your_keystore.p12文件2.配置application.yml文件先把证书文件放到application.yml同一目录下server: port: 9898 ssl: key-store-type: pkcs12key-store: classpath:your_keystore.p12key-store-password: xxxxxxxkey-alias: your_alias复制代码3.Docker部署把打包好的jar包上传到服务器,并把证书文件也放在你服务器上在jar包目录生成一个Dockerfile文件,内容如下:FROM java:8-alpineARG JAR_FILECOPY 你jar包的名称.jar app.jarENTRYPOINT ["java","-jar","/app.jar"]复制代码在当前目录下执行构建,并部署sudo docker build -t <镜像名称> . #记得前面有个点 . 我将镜像映射到我服务器的9898端口sudo docker run -d -p 9898:9898 -v /root/your_keystore.p12:/app/your_keystore.p12 -e "SERVER_SSL_KEY_STORE_TYPE=PKCS12" -e "SERVER_SSL_KEY_STORE=classpath:your_keystore.p12" -e "SERVER_SSL_KEY_STORE_PASSWORD=xxxxxx" -e "SERVER_SSL_KEY_ALIAS=your_alias" <镜像名称>复制代码 /root/your_keystore.p12要替换成你证书所在服务器的地址SERVER_SSL_KEY_STORE,SERVER_SSL_KEY_STORE_PASSWORD,SERVER_SSL_KEY_ALIAS都要改成你本人的配置 到这里曾经实现所有的部署啦4.测试在postman或在网页中测试,输出https://example.com:9898就能看到数据啦然而对于微信小程序来说还没有能够失常发动连贯5.服务器域名配置须要到官网的微信小程序后盾的 开发治理 -> 开发设置-> 服务器域名配置 将本人的域名配置下来,就完结撒花啦END ...

March 20, 2023 · 1 min · jiezi

关于springboot:笑小枫的SpringBoot系列十四SpringBoot发送邮件

本文简介本文次要介绍了应用SpringBoot发送邮件,次要蕴含如何获取发送邮件的受权码,这里以QQ邮箱为例,而后介绍了性能如何实现,包含通过模板发送邮件,发送带图片的邮件,发送带附件的邮件,发送带有多个附件的邮件。 获取SMTP受权码应用SPringBoot发送邮件呢,首先须要发送邮件的地址开明SMTP服务,并获取到对应的受权码,接下来就以QQ邮箱为例,简略的介绍一下怎么可通SMTP,并且获取到受权码值。具体操作如下图所示: 首先登录QQ邮箱网页版,而后在设置外面找到账户 能够看到下图中的一堆服务,咱们只有开明SMTP服务即可。 QQ邮箱开明SMTP服务须要应用密保手机发送短信码验证,我在发送的时候,提醒1069070069这个号码已敞开服务,能够发送上面的10690329021269。 验证胜利后,能够看到对应的受权码,咱们复制进去,留着备用。 发送邮件性能实现下面咱们曾经获取到QQ邮箱的STMP服务的受权码,接下来咱们看看怎么实现性能。 pom.xml增加援用 <!-- 配置thymeleaf模板依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- 反对发送邮件依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId></dependency>配置发送邮件的application springmail: default-encoding: UTF-8 host: smtp.qq.com username: 你的邮箱地址 password: 你的受权码 port: 22 protocol: smtp properties: mail: smtp: ssl: enable: true socketFactory: port: 465 class: javax.net.ssl.SSLSocketFactory这里的配置文件是以QQ邮箱的为例,如果是其余的邮箱,能够参考表格里的SMTP服务器地址和对应的端口号。 邮箱类型SMTP服务器地址端口号QQ邮箱smtp.qq.com465或587sina邮箱smtp.sina.cn465或587126邮箱smtp.126.com465或994aliyun邮箱smtp.aliyun.com465或994163邮箱smtp.163.com465或994yeah邮箱smtp.yeah.net465或994在config.bean包下编写发送邮件类EmailBean.javapackage com.maple.demo.config.bean;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.RequiredArgsConstructor;import org.thymeleaf.context.Context;import java.util.List;/** * @author 笑小枫 * @date 2022/7/22 */@Data@Builder@AllArgsConstructor@RequiredArgsConstructorpublic class EmailBean { /** * 填充内容 */ private Context context; /** * 应用模板,和text互斥,优先应用模板,模板不存在发送text内容 */ private String templateName; /** * 发送给谁 */ private String toUser; /** * 抄送给谁 */ private String[] ccUser; /** * 邮件主体 */ private String subject; /** * 邮件内容,和templateName互斥,优先应用模板,模板不存在发送text内容 */ private String text; /** * 附件列表 */ private List<String> attachmentList;}在util包下编写邮件的工具类EmailUtil.javapackage com.maple.demo.util;import com.maple.demo.config.bean.EmailBean;import lombok.RequiredArgsConstructor;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.factory.annotation.Value;import org.springframework.core.io.FileSystemResource;import org.springframework.mail.javamail.JavaMailSender;import org.springframework.mail.javamail.MimeMessageHelper;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;import org.thymeleaf.TemplateEngine;import javax.mail.MessagingException;import javax.mail.internet.MimeMessage;/** * @author 笑小枫 * @date 2022/7/22 */@Service@RequiredArgsConstructorpublic class EmailUtil { private final JavaMailSender mailSender; private final TemplateEngine templateEngine; @Value("${spring.mail.username}") private String from; @Async public void sendEmail(EmailBean emailBean) { try { // 解决附件名称过长导致的附件名称乱码问题 System.setProperty("mail.mime.splitlongparameters", "false"); // 定义邮件信息 MimeMessage message = mailSender.createMimeMessage(); MimeMessageHelper helper; helper = new MimeMessageHelper(message, true); helper.setFrom(from); helper.setTo(emailBean.getToUser()); helper.setSubject(emailBean.getSubject()); if (emailBean.getCcUser() != null && emailBean.getCcUser().length > 0) { helper.setCc(emailBean.getCcUser()); } // 如果存在模板,定义邮件模板中的内容,context的内容对应email.html的${project}占位的内容 if (emailBean.getContext() != null && StringUtils.isNotBlank(emailBean.getTemplateName())) { String emailContent = templateEngine.process(emailBean.getTemplateName(), emailBean.getContext()); helper.setText(emailContent, true); } else { helper.setText(emailBean.getText()); } // 如果存在附件,定义邮件的附件 if (emailBean.getAttachmentList() != null && !emailBean.getAttachmentList().isEmpty()) { for (String attachment : emailBean.getAttachmentList()) { FileSystemResource file = new FileSystemResource(attachment); if (StringUtils.isNotBlank(file.getFilename())) { helper.addAttachment(file.getFilename(), file); } } } mailSender.send(message); } catch (MessagingException e) { e.printStackTrace(); } }}配置发送邮件的模板在resources目录下创立templates目录。创立email.html模板。 ...

March 16, 2023 · 2 min · jiezi

关于springboot:Spring-cloud-和-Spring-boot-之间应该如何技术选型

1. Spring cloud 相比Spring boot有什么益处?Spring Boot 是一个基于 Spring Framework 的开发框架,能够帮忙开发者疾速搭建基于 Spring 的应用程序,简化了配置和部署的过程。而 Spring Cloud 则是一个基于 Spring Boot 的分布式系统开发工具,它提供了许多组件和工具,帮忙开发者疾速构建分布式系统。 上面是 Spring Cloud 相比 Spring Boot 的一些益处: 微服务反对:Spring Cloud 提供了丰盛的微服务组件和工具,如服务注册与发现、负载平衡、断路器、分布式配置等,帮忙开发者构建高可用、可伸缩的微服务架构。分布式配置:Spring Cloud Config 提供了分布式配置管理性能,能够将配置文件集中管理,不便在多个服务之间共享配置,同时反对 Git 和 SVN 等版本控制工具。负载平衡:Spring Cloud 提供了多种负载平衡算法,如轮询、随机等,能够帮忙开发者实现负载平衡策略。服务网关:Spring Cloud Gateway 是一个高效、可扩大的 API 网关,能够帮忙开发者实现 API 认证、路由、限流等性能。监控和追踪:Spring Cloud Sleuth 和 Zipkin 能够帮忙开发者实现分布式系统的监控和追踪性能,不便疾速定位问题和优化性能。总之,Spring Boot 是一个疾速构建基于 Spring 的应用程序的框架,而 Spring Cloud 则是在 Spring Boot 的根底上提供了一些分布式系统开发工具,能够帮忙开发者构建高可用、可伸缩的微服务架构。 2. 那有什么害处吗?尽管 Spring Cloud 带来了许多益处,然而也存在一些害处: 复杂性:分布式系统的开发和部署绝对于单体利用来说更加简单,须要思考分布式环境下的容错、高可用等问题,而 Spring Cloud 提供的组件和工具也绝对比较复杂,须要投入更多的学习和应用老本。技术选型:Spring Cloud 提供了多种组件和工具,开发者须要在这些组件和工具中进行抉择,例如服务注册核心能够抉择 Eureka 或者 Consul,负载平衡能够抉择 Ribbon 或者 LoadBalancer 等,须要在技术选型上投入更多的工夫和精力。性能问题:因为分布式系统的复杂性和组件的多样性,可能会带来性能问题,例如服务调用的提早、网络带宽等问题,须要进行性能优化和调优。依赖性:应用 Spring Cloud 还须要依赖其余的组件和工具,例如应用 Spring Cloud Config 须要依赖 Git 或者 SVN,应用 Spring Cloud Gateway 须要依赖 Netty 或者 Undertow 等,可能会减少我的项目的依赖性和复杂性。版本问题:Spring Cloud 组件和工具的版本更新频繁,可能会导致我的项目须要进行版本升级,减少了我的项目保护的复杂性。综上所述,只管 Spring Cloud 带来了许多益处,然而也须要在技术选型、复杂性、性能、依赖性和版本问题等方面进行思考和衡量。 ...

March 15, 2023 · 1 min · jiezi

关于springboot:如何将-Spring-Boot-项目集成到-Spring-Cloud-Alibaba-项目中

将 Spring Boot 我的项目集成到 Spring Cloud Alibaba 我的项目中须要实现以下步骤: 1. 配置核心集成增加依赖在 Spring Boot 我的项目的 pom.xml 文件中,增加如下依赖: <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>${latest.version}</version></dependency>这个依赖将帮忙你集成 Nacos 配置核心。 增加配置在 Spring Boot 我的项目的 application.yml 文件中,增加如下配置: spring: cloud: nacos: config: server-addr: ${nacos.server.addr} namespace: ${nacos.namespace} username: ${nacos.username} password: ${nacos.password}其中,${nacos.server.addr} 是 Nacos 服务器地址,${nacos.namespace} 是命名空间名称,${nacos.username} 和 ${nacos.password} 是 Nacos 的登录账号和明码。 配置 Nacos在 Nacos 控制台中,创立一个新的配置,将 Spring Boot 我的项目的配置复制到 Nacos 中。 启动 Spring Boot 我的项目启动 Spring Boot 我的项目后,它将会从 Nacos 中获取配置信息,并依照配置信息来运行。 增加其余依赖依据你的须要,你可能还须要增加其余的 Spring Cloud Alibaba 依赖,例如 spring-cloud-starter-alibaba-sentinel 来集成 Sentinel ...

March 15, 2023 · 1 min · jiezi

关于springboot:深入学习-Spring-Web-开发-BeanDefinition上

本文次要探讨什么是 BeanDefinition,下一篇文章,咱们将探讨 BeanDefinition 是如何起作用的。 BeanDefinition 是什么上面,咱们先看一下 BeanDefinition 的源码: public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; int ROLE_APPLICATION = 0; int ROLE_SUPPORT = 1; int ROLE_INFRASTRUCTURE = 2; void setParentName(@Nullable String parentName); @Nullable String getParentName(); void setBeanClassName(@Nullable String beanClassName); @Nullable String getBeanClassName(); void setScope(@Nullable String scope); @Nullable String getScope(); void setLazyInit(boolean lazyInit); boolean isLazyInit(); void setDependsOn(@Nullable String... dependsOn); @Nullable String[] getDependsOn(); void setAutowireCandidate(boolean autowireCandidate); boolean isAutowireCandidate(); void setPrimary(boolean primary); boolean isPrimary(); void setFactoryBeanName(@Nullable String factoryBeanName); @Nullable String getFactoryBeanName(); void setFactoryMethodName(@Nullable String factoryMethodName); @Nullable String getFactoryMethodName(); ConstructorArgumentValues getConstructorArgumentValues(); default boolean hasConstructorArgumentValues() { return !getConstructorArgumentValues().isEmpty(); } MutablePropertyValues getPropertyValues(); default boolean hasPropertyValues() { return !getPropertyValues().isEmpty(); } void setInitMethodName(@Nullable String initMethodName); @Nullable String getInitMethodName(); void setDestroyMethodName(@Nullable String destroyMethodName); @Nullable String getDestroyMethodName(); void setRole(int role); int getRole(); void setDescription(@Nullable String description); @Nullable String getDescription(); ResolvableType getResolvableType(); boolean isSingleton(); boolean isPrototype(); boolean isAbstract(); @Nullable String getResourceDescription(); @Nullable BeanDefinition getOriginatingBeanDefinition();}能够看到 BeanDefinition 是一个接口,它继承了 AttributeAccessor 和 BeanMetadataElement,其中 AttributeAccessor 的源码如下: ...

March 15, 2023 · 2 min · jiezi

关于springboot:笑小枫的SpringBoot系列十二JAVA使用EasyExcel导入excel

性能背景简略的说下这个性能的背景需要吧,有相似需要的能够复用 实现excel导入(废话...)多个sheet页一起导入第一个sheet页数据表头信息有两行,但只需依据第二行导入如果报错,依据不同的sheet页返回多个List记录报错起因数据量略微有些大(多个sheet页总量50w左右)我的项目引入依赖gradle: compile "com.alibaba:easyexcel:3.1.0"maven: <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.0</version></dependency>留神: 3+版本的的easyexcel,应用poi 5+版本时,须要手动排除:poi-ooxml-schemas,例如: <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.0</version> <exclusions> <exclusion> <artifactId>poi-ooxml-schemas</artifactId> <groupId>org.apache.poi</groupId> </exclusion> </exclusions></dependency>Excel模板这里演示一下两个sheet页,第一个sheet页取第二行题目,第二个sheet页取第一行题目的excel操作,只为演示,非凡的能够依据这个理论状况进行拓展。 点击下载模板(http://file.xiaoxiaofeng.site/blog/image/笑小枫测试导入.xls) 我的项目编码在config.bean包下新建excel包,用于寄存excel解决相干的代码 在excel包下定义通用的CommonExcel.java对象,只有用于记录行号package com.maple.demo.config.bean.excel;import com.alibaba.excel.annotation.ExcelIgnore;import lombok.Data;/** * @author 笑小枫 * @date 2022/7/22 */@Datapublic class CommonExcel { /** * 行号 */ @ExcelIgnore private Integer rowIndex;}在excel包下定义经销商信息对象ImportCompany.java,代码如下:@ExcelProperty 对用的是excel的题目名称,如果不加@ExcelProperty,默认对应列号 package com.maple.demo.config.bean.excel;import com.alibaba.excel.annotation.ExcelProperty;import lombok.Data;import java.util.Date;/** * @author 笑小枫 * @date 2022/7/22 */@Datapublic class ImportCompany { // -------------------- 根本信息 start ------------- @ExcelProperty("公司名称") private String companyName; @ExcelProperty("省份") private String province; @ExcelProperty("成立工夫") private Date startDate; @ExcelProperty("企业状态") private String entStatus; @ExcelProperty("注册地址") private String registerAddress; // ---------------- 根本信息 end --------------------- // ---------------- 经营信息 start --------------------- @ExcelProperty("员工数") private String employeeMaxCount; @ExcelProperty("经营规模") private String newManageScaleName; @ExcelProperty("所属区域省") private String businessProvinceName; @ExcelProperty("所属区域市") private String businessCityName; @ExcelProperty("所属区域区县") private String businessAreaName; // ---------------- 经营信息 end ---------------------}在excel包下定义联系人信息对象ImportContact.java,代码如下:package com.maple.demo.config.bean.excel;import com.alibaba.excel.annotation.ExcelProperty;import lombok.Data;/** * @author 笑小枫 * @date 2022/7/22 */@Datapublic class ImportContact { @ExcelProperty("公司名称") private String companyName; @ExcelProperty("姓名") private String name; @ExcelProperty("身份证号码") private String idCard; @ExcelProperty("电话号码") private String mobile; @ExcelProperty("职位") private String contactPostName;}在listener包下定义excel解决的监听器ImportExcelListener.java,代码如下:package com.maple.demo.listener;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.exception.ExcelDataConvertException;import com.alibaba.excel.read.listener.ReadListener;import com.alibaba.excel.read.metadata.holder.ReadRowHolder;import com.alibaba.excel.util.ListUtils;import com.maple.demo.config.bean.excel.CommonExcel;import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections4.CollectionUtils;import java.util.List;import java.util.function.Consumer;/** * @author 笑小枫 * @date 2022/7/22 */@Slf4jpublic class ImportExcelListener<T> implements ReadListener<T> { /** * 默认一次读取1000条,可依据理论业务和服务器调整 */ private static final int BATCH_COUNT = 1000; /** * Temporary storage of data */ private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); private final List<String> errorMsgList; /** * consumer */ private final Consumer<List<T>> consumer; public ImportExcelListener(Consumer<List<T>> consumer, List<String> errorMsgList) { this.consumer = consumer; this.errorMsgList = errorMsgList; } @Override public void invoke(T data, AnalysisContext context) { // 记录行号 if (data instanceof CommonExcel) { ReadRowHolder readRowHolder = context.readRowHolder(); ((CommonExcel) data).setRowIndex(readRowHolder.getRowIndex() + 1); } cachedDataList.add(data); if (cachedDataList.size() >= BATCH_COUNT) { consumer.accept(cachedDataList); cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { if (CollectionUtils.isNotEmpty(cachedDataList)) { consumer.accept(cachedDataList); } } /** * 在转换异样 获取其余异样下会调用本接口。抛出异样则进行读取。如果这里不抛出异样则 持续读取下一行。 */ @Override public void onException(Exception exception, AnalysisContext context) { // 如果是某一个单元格的转换异样 能获取到具体行号 String errorMsg = String.format("%s, 第%d行解析异样", context.readSheetHolder().getReadSheet().getSheetName(), context.readRowHolder().getRowIndex() + 1); if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception; errorMsg = String.format("第%d行,第%d列数据解析异样", excelDataConvertException.getRowIndex() + 1, excelDataConvertException.getColumnIndex() + 1); log.error("{}, 第{}行,第{}列解析异样,数据为:{}", context.readSheetHolder().getReadSheet().getSheetName(), excelDataConvertException.getRowIndex() + 1, excelDataConvertException.getColumnIndex() + 1, excelDataConvertException.getCause().getMessage()); } else { log.error(errorMsg + exception.getMessage()); } errorMsgList.add(errorMsg); }}编写controller进行测试,代码如下:.readSheet(0) 读取哪个sheet页,默认从0开始.head(ExcelCompany.class) 对应定义的sheet页对象,不同的sheet页应用对应的对象.registerReadListener 应用的监听器,这里定义的时通用的,依据不同的业务逻辑,能够定义不同的监听器解决,如需非凡的返回解决,能够定义多个参数的结构器,在监听器外面解决返回.headRowNumber(2) 题目行在第几行package com.maple.demo.controller;import com.alibaba.excel.EasyExcelFactory;import com.alibaba.excel.ExcelReader;import com.alibaba.excel.read.metadata.ReadSheet;import com.alibaba.fastjson.JSON;import com.maple.demo.config.bean.excel.ImportCompany;import com.maple.demo.config.bean.excel.ImportContact;import com.maple.demo.listener.ImportExcelListener;import io.swagger.annotations.Api;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import java.io.IOException;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;/** * @author 笑小枫 * @date 2022/7/22 */@Slf4j@RestController@RequestMapping("/example")@Api(tags = "实例演示-导入Excel")public class TestImportExcelController { @PostMapping("/importExcel") public Map<String, List<String>> importExcel(@RequestParam(value = "file") MultipartFile file) { List<String> companyErrorList = new ArrayList<>(); List<String> contactErrorList = new ArrayList<>(); try (ExcelReader excelReader = EasyExcelFactory.read(file.getInputStream()).build()) { // 公司信息结构器 ReadSheet dealerSheet = EasyExcelFactory .readSheet(0) .head(ImportCompany.class) .registerReadListener(new ImportExcelListener<ImportCompany>(data -> { // 解决你的业务逻辑,最好抽出一个办法独自解决逻辑 log.info("公司信息数据----------------------------------------------"); log.info("公司信息数据:" + JSON.toJSONString(data)); log.info("公司信息数据----------------------------------------------"); }, companyErrorList)) .headRowNumber(2) .build(); // 联系人信息结构器 ReadSheet contactSheet = EasyExcelFactory .readSheet(1) .head(ImportContact.class) .registerReadListener(new ImportExcelListener<ImportContact>(data -> { // 解决你的业务逻辑,最好抽出一个办法独自解决逻辑 log.info("联系人信息数据------------------------------------------"); log.info("联系人信息数据:" + JSON.toJSONString(data)); log.info("联系人信息数据------------------------------------------"); }, contactErrorList)) .build(); // 这里留神 肯定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取屡次,节约性能 excelReader.read(dealerSheet, contactSheet); } catch (IOException e) { log.error("解决excel失败," + e.getMessage()); } Map<String, List<String>> result = new HashMap<>(16); result.put("company", companyErrorList); result.put("contact", contactErrorList); log.info("导入excel实现,返回后果如下:" + JSON.toJSONString(result)); return result; }}测试后果因为须要上传excel文件,这里通过postman进行调用,idea控制台打印后果如下: ...

March 14, 2023 · 3 min · jiezi

关于springboot:笑小枫的SpringBoot系列十一SpringBoot接口日志信息统一记录

为什么要记录接口日志?至于为什么,具体看到这里的小伙伴心里都有一个答案吧,我这里简略列一下罕用的场景吧 用户登录记录统计重要增删改操作留痕须要统计用户的拜访次数接口调用状况统计线上问题排查等等等...既然有这么多应用场景,那咱们该怎么解决,总不能一条一条的去记录吧 面试是不是老是被问Spring的Aop的应用场景,那这个典型的场景就来了,咱们能够应用Spring的Aop,完满的实现这个性能,接下来上代码 先定义一下日志存储的对象吧本文波及到依赖: lombokswaggermybatisplus简略如下,能够依据本人的需要进行批改 贴一下建表sql吧 CREATE TABLE `sys_operate_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键', `title` varchar(50) DEFAULT '' COMMENT '模块题目', `business_type` int(2) DEFAULT '4' COMMENT '业务类型(0查问 1新增 2批改 3删除 4其余)', `method` varchar(100) DEFAULT '' COMMENT '办法名称', `resp_time` bigint(20) DEFAULT NULL COMMENT '响应工夫', `request_method` varchar(10) DEFAULT '' COMMENT '申请形式', `browser` varchar(255) DEFAULT NULL COMMENT '浏览器类型', `operate_type` int(1) DEFAULT '3' COMMENT '操作类别(0网站用户 1后盾用户 2小程序 3其余)', `operate_url` varchar(255) DEFAULT '' COMMENT '申请URL', `operate_ip` varchar(128) DEFAULT '' COMMENT '主机地址', `operate_location` varchar(255) DEFAULT '' COMMENT '操作地点', `operate_param` text COMMENT '申请参数', `json_result` text COMMENT '返回参数', `status` int(1) DEFAULT '0' COMMENT '操作状态(0失常 1异样)', `error_msg` text COMMENT '谬误音讯', `create_id` bigint(20) DEFAULT NULL COMMENT '操作人id', `create_name` varchar(50) DEFAULT '' COMMENT '操作人员', `create_time` datetime DEFAULT NULL COMMENT '操作工夫', `update_id` bigint(20) NULL DEFAULT NULL COMMENT '更新人id', `update_name` varchar(64) NULL DEFAULT '' COMMENT '更新者', `update_time` datetime NULL DEFAULT NULL COMMENT '更新工夫', PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统管理-操作日志记录';应用的mybatis plus的主动生成代码性能生成的对象,详情参考[SpringBoot集成Mybatis Plus](),真香 ...

March 13, 2023 · 4 min · jiezi

关于springboot:自动写代码

这几天,GitHub 上有个很火的插件在抖音刷屏了——Copilot。这个神器有啥用呢?简略来讲,它就是一款由人工智能打造的编程辅助工具。咱们来看看它有啥用。首先就是代码补全性能,你只有给出函数名和参数,Copilot 就会主动帮你补全代码。 第二个性能,就是它能够依据正文来写代码。也就是说,你只有把正文写好,它就会主动帮你生成对应的代码,吓人吧~ 除此之外,它还能够主动生成重复性代码、主动生成测试代码,同时也能够生成多套代码计划供你抉择。目前反对多种开发语言,包含 Python, JavaScript, TypeScript, Ruby, Java, Go等等,反对VS Code,Neovim,JetBrains 等 IDE。目前还没有齐全凋谢,处于邀请制状态。前几天我看到了这个插件并申请了,然而目前还没通过,所以想看演示的话大家能够上 B 站,很多大佬做了测评。看到这个插件这么弱小的性能,很多程序员曾经吓傻了,这么弱小,AI 都曾经能够帮你写代码了,程序员是不是要下岗了?但在我看来,这个插件目前还只是个高级的玩具而已。 只是帮你百度一下这个插件的实质是利用人工智能,通过开源社区里的大量代码进行训练,从而实现主动写代码的成果。所以,对于通用性的代码,它的生成准确性还是比拟高的。比方,咱们在写两个日期之间的天数,你写来写去,无非就是这样写(以 Python 为例):def days(str1,str2): date1=datetime.datetime.strptime(str1[0:10],"%Y-%m-%d") date2=datetime.datetime.strptime(str2[0:10],"%Y-%m-%d") num=(date1-date2).days return num复制代码这种代码说白了其实没有多少创造性,写纯熟了可能基本都不须要通过大脑,都造成肌肉记忆了,无非就是函数名、变量不太一样,其余的简直都一样。相似的,咱们还有一些算法(比方冒泡排序)、工具(比方哈希校验),其实也都是重复性十分高的代码,它也能够帮你实现得很好。重复性的工作,都有可能被代替的。在这种状况下,Copilot 的作用就相当于帮你百度一下,而后再帮你 CV 一下。如果没有这个插件,咱们一样也能做,只是花点工夫而已。业务代码品质不高Copilot 本人也抵赖,他们会尝试理解程序员的用意,并「尽可能」生成最好的代码,但生成的代码并不总是无效,有时甚至还没有意义。毕竟训练集来自公共代码,参差不齐,甚至齐全没有意义。这点在 B 站大神的测试下也失去了印证,有时候的确也生成了一堆不知所云的代码。毕竟,咱们的业务需要始终都是复杂多变的,有时咱们本人都不太能实现一些性能需要,还指望机器帮你写?别太空想了!而且,它主动补全业务代码的前提是,你曾经须要有肯定的代码量供它参考,它才能够去猜想你接下来筹备写什么。也就是说,你还是须要写一些代码,有这些代码了它才会写得更精确一些。而且你提供的代码量越少,它的举荐就越不精确。也有人说了,它会依据正文写代码啊。然而,实际上,正文你都写好了,你本人其实也曾经差不多把代码都写好了。再说了,程序员都晓得,读他人的代码是一件很苦楚的事件,Copilot 帮你写好了代码,你敢间接就用吗?你浏览并了解它的代码的工夫,兴许本人早就写完了。所以,你说它会齐全帮你写代码吗?必定不行,至多目前不会。就算能帮你写局部业务代码,也不肯定写得好。有肯定的平安问题Copilot 它的原理就是利用大量的代码进行训练,样本越多天然就越精确。那么问题来了,他们本人声称这些样本是来自开源的社区,但你应用了它们的插件,你敢保障你写的代码不会成为他们的样本?而且,如果它们生成的代码不合你的要求,你手动批改了,它更加了解了你的用意,这对 Copilot 的训练几乎是神助啊,他们难道真的不会思考利用一下使用者的收费劳动力?但凡应用第三方插件,而且还是不开源的,谁都无奈保障你的数据是否真的没有被透露进来。本人练习的代码必定是无所谓了,然而如果波及到公司的商业秘密,那就可能会有法律问题了。而且,更可怕的是,Copilot 生成的代码,有 0.1% 的概率会一成不变复制训练集的样本。如果这些样本没版权也没事,但要是有版权,有可能也会吃官司。之前已有新闻报道过,Copilot 原样复制了经典射击游戏《雷神之锤》里的代码,而这些代码是受法律爱护的,使用者也因而受到了一些麻烦。所以,当初不少公司曾经明确规定,禁止应用 Copilot 插件!照相机的呈现不会让画家下岗照相机被创造进去的时候,已经一度也有人开始唱衰画家这样的岗位。但时过境迁,这么多年过来了,画家的岗位隐没了吗?不仅没隐没,反而当初技术精湛的画家身价一涨再涨。相机,只是把画家的一部分重复性工作取代了,比方画肖像、风光,等等。它只会机械地,一比一还原事实。而当初的画家,更多是作为艺术创作,这部分相机短时间内是无奈取代的。(当初也有 AI 艺术创作,但还很难说取代)Copilot 何其不是这样的存在呢?我大胆预测,Copilot 只会取代局部根底的、重复性的编程工作,说它会让程序员下岗,我感觉这必定是天大的笑话。相机再智能,你也须要具备肯定的美术、审美根底,能力拍出难看的照片。 同样的,Copilot 帮你写了一堆代码,你如果没有肯定的编程根底的话,你连改都不会改。 瞻望我还是那句话,Copilot 的呈现不会让程序员下岗,只会取代局部工作而已。工具的呈现只是会让咱们从重复性的劳动中解放出来,从而让咱们的大脑有更多工夫思考。但思考和创作自身,很难被取代。我集体是十分欢送 Copilot 这样的工具呈现,能够让咱们缩小很多不必要的工作。作为程序员都晓得,咱们每天都须要破费很多工夫在百度/谷歌下面,并且各种搬运轮子。这种工作就是机械的重复性工作,如果能有工具让咱们从这样的工作中解放出来,必定会为咱们节约很多工夫。而且,Copilot 这个单词自身也明确了本人的定位,那就是「副驾驶」,人家曾经很明确本人的地位,基本就没想着成为主驾驶。所以,Copilot的呈现,大家不要如临大敌,而是要正当利用工具,长于利用工具,让工具真正为咱们所服务。学习编程,千万不要急于求成,肯定要多读一些经典书籍,多看源码,多下苦功夫去死磕代码,这样技术能力出息。给大家分享一些程序员必读经典书籍,肯定要多读几遍:

March 13, 2023 · 1 min · jiezi

关于springboot:笑小枫的SpringBoot系列十SpringBoot处理请求跨域问题

上一篇9.SpirngBoot用户登录拦截器刚刚说过应用拦截器对用户登录状态进行拦挡,本文就顺便说说应用拦截器解决跨域问题吧。 咱们本系列应该不会遇到跨域问题,但日常工作中,一个新我的项目和前端小伙伴联调时还是要解决跨域问题的,本文就一起简略的看一下,当前遇到了跨域问题也能够疾速解决。 什么是跨域CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去拜访另一个不同域名或者同域名不同端口的资源时,就会收回跨域申请。如果此时另一个资源不容许其进行跨域资源拜访,那么拜访就会遇到跨域问题。 跨域指的是因为浏览器的安全性限度,不容许前端页面拜访协定不同、域名不同、端口号不同的http接口,例如我本地创立一个html,外面写一个ajax申请拜访我服务器SpringBoot利用提供的接口:192.168.1.11:8080/getUser则会出报 No 'Access-Control-Allow-Origin' header is present on the requested resource. 谬误。 SpringBoot怎么解决跨域在springboot中能够采纳多种形式解决跨域问题,例如:能够在类或办法上增加@CrossOrigin 注解。还有一种就是全局配置,全局配置须要增加自定义类实现 WebMvcConfigurer 接口,而后实现接口中的 addCorsMappings 办法。addMapping:示意对哪种格局的申请门路进行跨域解决。allowedHeaders:示意容许的申请头,默认容许所有的申请头信息。allowedMethods:示意容许的申请办法,默认是 GET、POST 和 HEAD。这里配置为 * 示意反对所有的申请办法。maxAge:示意探测申请的有效期allowedOrigins 示意反对的域具体代码如下 package com.maple.demo.filter;import org.springframework.core.annotation.Order;import org.springframework.http.HttpHeaders;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 跨域拦挡. * * @author 笑小枫 * @date 2022-07-21 19:23:08 * @since JDK 1.8 */@WebFilter(filterName = "corsFilter", urlPatterns = "/*")@Order(0)public class CorsFilter implements Filter { private static final String HEADER_ORIGIN = "Origin"; private static final String METHOD_OPTIONS = "OPTIONS"; @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) req; final HttpServletResponse response = (HttpServletResponse) res; if (request.getHeader(HEADER_ORIGIN) != null) { response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader(HEADER_ORIGIN)); } response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 如果容许所有申请形式,用* response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE÷, "3600"); // 如果容许所有header,用* response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Authorization, Content-Type, Accept, X-Requested-With, remember-me"); response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Content-Disposition"); if (METHOD_OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); return; } chain.doFilter(req, res); }}对于笑小枫本章到这里完结了,喜爱的敌人关注一下我呦微信公众号:笑小枫笑小枫集体博客:https://www.xiaoxiaofeng.com本文源码:https://github.com/hack-feng/maple-demo ...

March 12, 2023 · 1 min · jiezi

关于springboot:笑小枫的SpringBoot系列九SpringBoot用户登录功能实现

对于本文其实用户登录拦挡的这块不想这么早写,加个登录前面好多货色就要思考登录状态了,我其实想把这个系列写成非必要关系,解耦性比拟强的系列。然而,写完redis,总是感觉登录是对它最简略的实际,那就加上吧,反正前面很多文章也会用到,但大多文章我仍会不思考登录状态。 这里只是讲明确登录机制,如何实现。理论应用中会思考很多别的,例如用户权限,登录机制限度等等~这里就先不做过多的叙述。 这里只讲技术和实现,不讲任何业务场景哈,牵扯到场景的问题就会简单N倍,而且通用性往往不尽人意~ 本文依赖于redis和mybatis plus,这些都是最根底的模块,所以都放在最后面写了,大家能够线过一下相干的文章。 [SpringBoot集成Redis]() [SpringBoot集成Mybatis Plus]() 本文是基于jwt+redis来实现。接下来咱们一起看看吧什么是JWT什么是JWT,JWT(全称:Json Web Token)是一个凋谢规范(RFC 7519),它定义了一种紧凑的、自蕴含的形式,用于作为JSON对象在各方之间平安地传输信息。 该信息能够被验证和信赖,因为它是数字签名的。 下面说法比拟文绉绉,简略点说就是一种认证机制,让后盾晓得该申请是来自于受信的客户端。 JWT的长处json格局的通用性,所以JWT能够跨语言反对,比方Java、JavaScript、PHP、Node等等。能够利用Payload存储一些非敏感的信息。便于传输,JWT构造简略,字节占用小。不须要在服务端保留会话信息,易于利用的扩大。JWT的毛病安全性没法保障,所以jwt里不能存储敏感数据。因为jwt的payload并没有加密,只是用Base64编码而已。无奈中途废除。因为一旦签发了一个jwt,在到期之前始终都是无效的,如果用户信息产生更新了,只能等旧的jwt过期后从新签发新的jwt。续签问题。当签发的jwt保留在客户端,客户端始终在操作页面,按情理应该始终为客户端续长无效工夫,否则当jwt有效期到了就会导致用户须要从新登录。弥补JWT的毛病针对JWT的毛病,咱们在应用的过程中,只贮存罕用的无敏感数据,比方用户ID,用户角色等。中途废除和续签问题,通过和redis配合应用,将token返回时,同步保留redis,通过管制token在redis的有效期来进行管制。还能够通过统计redis无效数据,对在线用户进行统计或强制下线等操作。用户登录流程以用户登录性能为例,程序流程如下: 用户登录 token认证拜访 注:零碎中采纳JWT对用户登录受权验证。 基于Token的身份验证流程 应用基于Token的身份验证,在服务端不须要存储用户的登录记录。大略的流程是这样的: 1、客户端应用用户名或明码申请登录; 2、服务端收到申请,去验证用户名与明码; 3、验证胜利后,服务端会应用JWT签发一个Token,保留到Redis中,同时再把这个Token发送给客户端; 4、客户端收到Token当前能够把它存储起来,比方放在Cookie里或者Local Storage里; 5、客户端每次向服务端申请资源的时候须要在申请Header外面带着服务端签发的Token; 6、服务端收到申请,而后去验证客户端申请外面带着的Token,如果验证胜利,就向客户端返回申请的数据。验证失败,返回失败起因。 性能实现主动生成的User.java、UserMapper.java、UserMapper.xml...就不贴代码了,没有业务代码,且占的篇幅过大。在[SpringBoot集成Mybatis Plus]()文章中创立过就能够疏忽了哈~ 代码生成见[SpringBoot集成Mybatis Plus]()一文。 波及到的表sql在[SpringBoot集成Mybatis Plus]()文章中创立过的就能够疏忽了 CREATE TABLE `usc_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `account` varchar(30) DEFAULT NULL COMMENT '用户账号', `user_name` varchar(30) DEFAULT NULL COMMENT '用户姓名', `nick_name` varchar(30) DEFAULT NULL COMMENT '用户昵称', `user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00零碎用户,01小程序用户)', `email` varchar(50) DEFAULT '' COMMENT '用户邮箱', `phone` varchar(11) DEFAULT '' COMMENT '手机号码', `sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)', `avatar` varchar(100) DEFAULT '' COMMENT '头像地址', `salt` varchar(32) DEFAULT NULL COMMENT '用户加密盐值', `password` varchar(100) DEFAULT '' COMMENT '明码', `status` char(1) DEFAULT '0' COMMENT '帐号状态(0失常 1停用)', `create_id` bigint(20) DEFAULT NULL COMMENT '创建人id', `create_name` varchar(64) DEFAULT '' COMMENT '创建者', `create_time` datetime DEFAULT NULL COMMENT '创立工夫', `update_id` bigint(20) DEFAULT NULL COMMENT '更新人id', `update_name` varchar(64) DEFAULT '' COMMENT '更新者', `update_time` datetime DEFAULT NULL COMMENT '更新工夫', `delete_flag` tinyint(1) DEFAULT '0' COMMENT '删除标记', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户核心-用户信息表';引入依赖首先咱们在pom文件中引入依赖 ...

March 11, 2023 · 8 min · jiezi

关于springboot:笑小枫的SpringBoot系列六SpringBoot日志打印Logback详解

什么是LogbackLogback 旨在作为风行的 log4j 我的项目的继承者,是SpringBoot内置的日志解决框架,spring-boot-starter其中蕴含了spring-boot-starter-logging,该依赖内容就是 Spring Boot 默认的日志框架 logback。具体如下图所示 <img src="http://file.xiaoxiaofeng.site/blog/image/2022/07/17/20220717123855.png"/> 官网文档:http://logback.qos.ch/manual/ SpringBoot应用logback介绍在咱们启动SpringBoot,发现咱们并没有被动去配置过任何和日志打印的相干配置,然而控制台却打印了相干的启动日志;因为SpringBoot为Logback提供了默认的配置文件base.xml,base.xml文件里定义了默认的root输入级别为INFO。零碎打印的日志信息如下: 咱们能够到SpringBoot源码里看一下base.xml具体是如何配置的,如下图所示 自定义logback配置能够看到默认的配置是非常简单,那么咱们能够自定义配置吗?答案当然是必定的 在resources目录下创立文件logback-spring.xml,具体配置如下 <?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="60 seconds"> <springProperty scope="context" name="spring.application.name" source="spring.application.name"/> <!-- 定义参数 --> <property name="log.lever" value="debug" /> <property name="log.maxHistory" value="365" /> <property name="log.filePath" value="logs"></property><!-- 如果这里不想写死C盘,那么下面的配置,零碎会主动在我的项目所在的盘符创立文件夹 --><!-- <property name="log.filePath" value="C:/{spring.application.name}_log"></property>--><!-- <property name="log.pattern" value="%-12(%d{yyyy-MM-dd HH:mm:ss.SSS}) |-%-5level [%thread] %c [%L] -| %msg%n" /> --> <property name="log.pattern" value="%-12(%d{MM-dd HH:mm:ss}) %c [%L] | %msg%n" /> <!-- 控制台设置 --> <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender"> <!-- <encoder> <pattern>${log.pattern}</pattern> </encoder>--> <encoder> <!--<pattern>%d %p (%file:%line\)- %m%n</pattern>--> <!--格式化输入:%d:示意日期 %thread:示意线程名 %-5level:级别从左显示5个字符宽度 %msg:日志音讯 %n:是换行符--> <pattern>%boldMagenta(笑小枫控制台-) %red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %yellow(%logger) - %cyan(%msg%n)</pattern> <charset>UTF-8</charset> </encoder> </appender> <!-- DEBUG --> <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 文件门路 --> <file>${log.filePath}/${spring.application.name}_debug.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 文件名称 --> <fileNamePattern>${log.filePath}/debug/${spring.application.name}_debug.%d{yyyy-MM-dd}.log.gz </fileNamePattern> <!-- 文件最大保留历史数量 --> <MaxHistory>${log.maxHistory}</MaxHistory> </rollingPolicy> <encoder> <pattern>${log.pattern}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>DEBUG</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- INFO --> <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 文件门路 --> <file>${log.filePath}/${spring.application.name}_info.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 文件名称 --> <fileNamePattern>${log.filePath}/info/${spring.application.name}_info.%d{yyyy-MM-dd}.log.gz </fileNamePattern> <!-- 文件最大保留历史数量 --> <MaxHistory>${log.maxHistory}</MaxHistory> </rollingPolicy> <encoder> <pattern>${log.pattern}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- ERROR --> <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 文件门路 --> <file>${log.filePath}/${spring.application.name}_error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 文件名称 --> <fileNamePattern>${log.filePath}/error/${spring.application.name}_error.%d{yyyy-MM-dd}.log.gz </fileNamePattern> <!-- 文件最大保留历史数量 --> <MaxHistory>${log.maxHistory}</MaxHistory> </rollingPolicy> <encoder> <pattern>${log.pattern}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender><!-- 上线后如果要查看谬误日志,能够把level=info改为level=debug --> <root level="info"> <appender-ref ref="consoleAppender" /> <appender-ref ref="debugAppender" /> <appender-ref ref="infoAppender" /> <appender-ref ref="errorAppender" /> </root></configuration>咱们在启动一下程序,看下成果 ...

March 9, 2023 · 3 min · jiezi

关于springboot:使用delayjob开源项目快速实现延迟调度业务

1 背景订单创立一段时间后未领取,如何及时的敞开订单?用户注册一段时间后未欠缺材料,如何及时揭示?delay-job就是专为此场景开发的轻量级分布式提早任务调度零碎,目前已在github开源。我的项目地址:https://github.com/findthinks/delay-job 2 应用2.1 服务端部署集体测试服务器已装置Java8+、Mysql5.7+环境,测试过程应用root账号。 2.1.1 下载delay-job# wget https://github.com/findthinks/delay-job/releases/download/0.6.1/delay-job-bin-0.6.1.zip# unzip delay-job-bin-0.6.1.zip 2.1.2 建库表提取解压文件下docs/db/schema_init.sql,执行建库建表。 mysql> source /root/delay-job/docs/db/schema_init.sql 2.1.3 批改配置批改数据库配置信息,本文应用与默认配置统一。 # vi /root/delay-job/config/application.yaml 2.1.4 启动服务# cd /root/delay-job/bin# ./startup.sh察看log/delay-job.log日志信息,确认服务失常启动,胜利监听1989(http)、1990(grpc)端口。 2.2 客户端对接delay-job触发告诉反对http、grpc、kafka,本次应用http协定告诉。测试应用springboot开发了一个繁难的http接口,用于接受任务触发告诉。为不便,客户端、服务端部署在同一台机器。 2.2.1 启动客户端客户端接管提早告诉的http接口为:http://127.0.0.1:9000/recv/notify 2.2.2 注册提早工作手工注册一个测试工作,触发工夫点为1678206771,工作触发信息告诉到接口http://127.0.0.1:9000/recv/notify curl -X 'POST' 'http://localhost:1989/api/v1/submit/job' \  -H 'Content-Type:application/json' \  -d'{      "outJobNo":"job_no_000000000004",      "triggerTime":1678206771,      "callbackProtocol":"HTTP",      "callbackEndpoint":"http://127.0.0.1:9000/recv/notify",      "jobInfo":"First delay job."    }' 2.2.3 触发告诉客户端工作job_no_000000000004在1678206771工夫点准时收到回调告诉。 3 总结本文展现了如何借助delay-job开源我的项目,疾速实现提早调度业务,实现过程非常简单。

March 9, 2023 · 1 min · jiezi

关于springboot:Spring-MVC执行流程及源码详解

Spring MVC中各组件初始化过程已在上篇分享:初始化过程 一、SpringMVC罕用组件DispatcherServlet:前端控制器,对立解决申请和响应,整个流程管制的核心,由它调用其它组件解决用户的申请HandlerMapping:处理器映射器,依据申请的url、method等信息查找Handler,即控制器办法Handler:处理器,在DispatcherServlet的管制下Handler对具体的用户申请进行解决HandlerAdapter:处理器适配器,通过HandlerAdapter对处理器(控制器办法)进行执行ViewResolver:视图解析器,不须要工程师开发,由框架提供,进行视图解析View:视图 将模型数据通过页面展现给用户二、DispatcherServlet(前端控制器)的继承构造IDEA中快捷键Ctrl+Shift+Alt+U 可查看继承图 graph TDA[Servlet]A==>B[GenericServlet]B==>C[HttpServlet]C==>D[HttpServletBean]D==>E[FrameworkServlet]E==>F[DispatcherServlet]从上图中能够看到DispatcherServlet的顶层接口是Servlet三、调用组件解决申请过程$\color{#F00}{尽管咱们看的是不同类中的调用过程,如果通过继承或者实现放到同一个类中}$$\color{#F00}{其实咱们就是在同一个类中来查看办法的调用。}$1.Servlet接口从Servlet接口开始步步剖析,在Servlet接口中存在下图中的5种形象办法。快捷键: Alt+7 用户每次发送申请时,Servlet容器都会调用service()办法对申请进行解决public interface Servlet { public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; }2.GenericServlet抽象类public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { //能够看到并没有对Servlet中的service()办法进行实现 @Override public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;}GenericServlet没有对Servlet中的service()办法进行实现,那么依据继承构造持续向下梳理3.HttpServle抽象类 能够看到又调用了service(request, response)办法,如下图申请形式有以下几种 依据req.getMethod()获取申请形式调用对应的doGet,doPut等等办法,持续向下看4.HttpServletBean抽象类 能够发现HttpServletBean没有重写service()办法,用的是父类HttpServle中的办法5.FrameworkServlet抽象类 FrameworkServlet中重写了doGet等办法,则应用本类中重写后的办法 能够看到无论service中的那个逻辑,都执行processRequest(request, response)办法,所以咱们只需查看该办法即可 6.DispatcherServlet类6.1执行流程图为什么说doDispatcher()是整个流程管制的核心,由它调用其它组件解决用户的申请? 那么看下文流程即可明确用户向服务器发送申请,申请被SpringMVC 前端控制器 DispatcherServlet捕捉。 执行流程图大家能够对照图来看接下来的流程6.2图中第的2,3步处理器映射器就是这一步返回处理器执行链蕴含拦截器 以后类重写了父类中的doservice()办法,又发现该办法的外围是doDispatcher办法,重点来了咱们来看该办法实现 6.3图中第的4,5,6,7步处理器适配器 1.蕴含拦截器的执行逻辑接下来就到拦截器的前置办法,首先看下拦截器的执行程序,不便了解接下来的代码流程请观赏源码流程:紧接上图中的适配器办法 拦截器的前置办法对应上图的逻辑解决(对上图详解) 正序下来是理论调用处理程序办法 返回ModelAndView对象拦截器的后置办法 6.4图中第的8,9,10步>进入该办法查看看拦截器的最初执行办法 返回用户五、总结1.简略总结1. Spring MVC所有的申请都通过DispatcherServlet来对立散发。DispatcherServlet将申请分发给Controller之前,须要借助于Spring MVC提供的HandlerMapping定位到具体的Controller。2. HandlerMapping接口负责实现客户申请到Controller映射。3. Controller接口将解决用户申请,这和Java Servlet表演的角色是统一的。一旦Controller解决完用户申请,则返回ModelAndView(数据和视图)对象给DispatcherServlet前端控制器。从宏观角度思考,DispatcherServlet是整个Web利用的控制器;从宏观思考,Controller是单个Http申请处理过程中的控制器,而ModelAndView是Http申请过程中返回的模型(Model)和视图(View)。4.返回的视图须要通过ViewResolver接口(视图解析器)在Web利用中负责查找View对象,从从而将相应后果渲染给客户。2.具体总结1. 用户向服务器发送申请,申请被SpringMVC 前端控制器 DispatcherServlet捕捉2. DispatcherServlet对申请URL进行解析,失去申请资源标识符(URI),判断申请URI对应的映射3. 依据该URI,调用HandlerMapping取得该Handler配置的所有相干的对象(包含Handler对象以及Handler对象对应的拦截器),最初以HandlerExecutionChain执行链对象的模式返回。4. DispatcherServlet 依据取得的Handler,抉择一个适合的HandlerAdapter。5. 如果胜利取得HandlerAdapter,此时将开始执行拦截器的preHandler(…)办法【正向】6. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)办法,解决申请。在填充Handler的入参过程中,依据你的配置,Spring将帮你做一些额定的工作: ...

March 9, 2023 · 1 min · jiezi

关于springboot:SpringBoot可以同时处理多少请求

大家好,我是不才陈某~ 咱们都晓得,SpringBoot默认的内嵌容器是Tomcat,也就是咱们的程序实际上是运行在Tomcat里的。所以与其说SpringBoot能够解决多少申请,倒不如说Tomcat能够解决多少申请。 对于Tomcat的默认配置,都在spring-configuration-metadata.json文件中,对应的配置类则是org.springframework.boot.autoconfigure.web.ServerProperties。 和解决申请数量相干的参数有四个: 「server.tomcat.threads.min-spare」:起码的工作线程数,默认大小是10。该参数相当于长期工,如果并发申请的数量达不到10,就会顺次应用这几个线程去解决申请。「server.tomcat.threads.max」:最多的工作线程数,默认大小是200。该参数相当于临时工,如果并发申请的数量在10到200之间,就会应用这些临时工线程进行解决。「server.tomcat.max-connections」:最大连接数,默认大小是8192。示意Tomcat能够解决的最大申请数量,超过8192的申请就会被放入到期待队列。「server.tomcat.accept-count」:期待队列的长度,默认大小是100。举个例子阐明一下这几个参数之间的关系: 如果把Tomcat比作一家饭店的话,那么一个申请其实就相当于一位客人。min-spare就是厨师(长期工);max是厨师总数(长期工+临时工);max-connections就是饭店里的座位数量;accept-count是门口小板凳的数量。来的客人优先坐到饭店外面,而后厨师开始忙活,如果长期工能够干得完,就让长期工干,如果长期工干不完,就再让临时工干。图中画的厨师一共15人,饭店里有30个座位,也就是说,如果当初来了20个客人,那么就会有5集体先在饭店里等着。如果当初来了35集体,饭店里坐不下,就会让5集体先到门口坐一下。如果来了50集体,那么饭店座位+门口小板凳一共40个,所以就会有10人来到。 也就是说,SpringBoot同所能解决的最大申请数量是max-connections+accept-count,超过该数量的申请间接就会被丢掉。 「纸上得来终觉浅,绝知此事要躬行。」 下面只是实践后果,当初通过一个理论的小例子来演示一下到底是不是这样: 创立一个SpringBoot的我的项目,在application.yml里配置一下这几个参数,因为默认的数量太大,不好测试,所以配小一点: server: tomcat: threads: # 起码线程数 min-spare: 10 # 最多线程数 max: 15 # 最大连接数 max-connections: 30 # 最大期待数 accept-count: 10再来写一个简略的接口: @GetMapping("/test") public Response test1(HttpServletRequest request) throws Exception { log.info("ip:{},线程:{}", request.getRemoteAddr(), Thread.currentThread().getName()); Thread.sleep(500); return Response.buildSuccess(); }代码很简略,只是打印了一下线程名,而后休眠0.5秒,这样必定会导致局部申请解决一次性解决不了而进入到期待队列。 而后我用Apifox创立了一个测试用例,去模仿100个申请: 察看一下测试后果: 从后果中能够看出,因为设置的 「max-connections+accept-count」 的和是40,所以有60个申请会被抛弃,这和咱们的预期是相符的。因为最大线程是15,也就是有25个申请会先期待,等前15个解决完了再解决15个,最初在解决10个,也就是将40个申请分成了15,15,10这样三批进行解决。 再从控制台的打印日志能够看到,线程的最大编号是15,这也印证了后面的想法。 「总结一下」:如果并发申请数量低于「server.tomcat.threads.max」,则会被立刻解决,超过的局部会先进行期待,如果数量超过max-connections与accept-count之和,则多余的局部则会被间接抛弃。 延长:并发问题是如何产生的到目前为止,就曾经搞明确了SpringBoot同时能够解决多少申请的问题。然而在这里我还想基于下面的例子再延长一下,就是为什么并发场景下会呈现一些值和咱们预期的不一样? 构想有以下场景:厨师们用一个账本记录一共做了多少道菜,每个厨师做完菜都记录一下,每次记录都是将账本上的数字先抄到草稿纸上,计算x+1等于多少,而后将计算的后果写回到账本上。 Spring容器中的Bean默认是单例的,也就是说,解决申请的Controller、Service实例就只有一份。在并发场景下,将cookSum定义为全局变量,是所有线程共享的,当一个线程读到了cookSum=20,而后计算,写回前另一个线程也读到是20,两个线程都加1后写回,最终cookSum就变成了21,然而实际上应该是22,因为加了两次。 private int cookSum = 0;@GetMapping("/test")public Response test1(HttpServletRequest request) throws Exception { // 做菜。。。。。。 cookSum += 1; log.info("做了{}道菜", cookSum); Thread.sleep(500); return Response.buildSuccess();} ...

March 8, 2023 · 1 min · jiezi

关于springboot:DispatcherServlet初始化过程及源码分析

DispatcherServlet 实质上是一个 Servlet,所以遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。一、DispatcherServlet是什么DispatcherServlet: 前端控制器, 对立解决申请和响应,整个流程管制的核心,由它调用其它组件解决用户的申请,实质上是一个 Servlet(初始化->运行->销毁),所以遵循 Servlet 的生命周期。所以宏观上是 Servlet 生命周期来进行调度。既然是Servlet那么就依照Servlet生命周期去剖析源码就好了。 二、DispatcherServlet的继承构造(重要)IDEA中快捷键Ctrl+Shift+Alt+U 可查看继承图 graph TDA[Servlet]A==>B[GenericServlet]B==>C[HttpServlet]C==>D[HttpServletBean]D==>E[FrameworkServlet]E==>F[DispatcherServlet]从上图中能够看到DispatcherServlet的顶层接口是Servlet三、初始化流程具体介绍能够应用IDEA工具,跟着本文档步骤,一起查看初始化流程 $\color{#F00}{尽管咱们看的是不同类中的调用过程,如果通过继承或者实现放到同一个类中}$$\color{#F00}{其实咱们就是在同一个类中来查看办法的调用。}$1.Servlet接口从Servlet接口开始步步剖析,在Servlet接口中存在下图中的5中形象办法。快捷键: Alt+7初始化办法则是init(ServletConfig config)办法 public interface Servlet { public void init(ServletConfig config) throws ServletException; }2.GenericServlet抽象类public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { //重写Servlet中的初始化办法 @Override public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } //重写的init办法中,又调用空参的初始化办法 public void init() throws ServletException { // NOOP by default }}重写Servlet接口中的初始化办法,发现其又调用了空参的初始化办法,那么接下来查看3.HttpServle抽象类 能够发现HttpServle没有重写init办法,用的是父类中的办法,那么咱们接着向下看4.HttpServletBean抽象类 HttpServletBean中重写了GenericServlet中的init初始化办法,那么证实GenericServlet中的空参init办法间接到这里了而后查看init办法调用过程也就是下图中的initServletBean()办法当初能够发现这次调用的是initServletBean()办法,咱们来看子类是否对该办法进行了重写 5.FrameworkServlet抽象类能够看到上图中重写了HttpServletBean中的initServletBean()办法,那么其中最重要的则是initWebApplicationContext()初始化web容器办法,接下来查看初始化办法都做了些什么如下图与上图为一个办法内,用来初始化和刷新WebApplicationContext ...

March 8, 2023 · 1 min · jiezi

关于springboot:IoTLink版本更新-v180

IoTLink v1.8.0版本更新 更新内容新增第三方接口 翼控新增第三方接口 中创新增第三方接口 旭宇

March 8, 2023 · 1 min · jiezi

关于springboot:笑小枫的SpringBoot系列四SpringBoot返回统一结果包装

为什么?前后端拆散的时代,如果没有对立的返回格局,给前端的后果各式各样,预计前端的小伙伴就要骂娘了。 咱们想对自定义异样抛出指定的状态码排查谬误,对系统的不可预知的异样抛出敌对一点的异样信息。 咱们想让接口对立返回一些额定的数据,例如接口执行的工夫等等。 ... 所以嘛,不论是日常和前端小伙伴对接,还是和其余部门进行接口对接,都应该返回固定的格局。 本文给出一个简略通用的返回格局,小伙伴们有需要,能够基于此版本依据本人的业务需要丰盛返回格局。返回数据格式如下,有趣味的小伙伴们能够持续往下看SpringBoot是怎么来实现的。 { "status": true, "code": "0000", "msg": "", "data": { "id": 1, "deptId": 103, "userName": "admin", "nickName": "笑小枫", "userType": "00", "email": "xxf@163.com", "phone": "15888888888", "status": "0", "remark": "管理员" }}怎么做?首先创立一个测试的Controller,代码如下 package com.maple.demo.controller;import com.maple.demo.util.ResultJson;import lombok.Data;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author 笑小枫 * @date 2022/07/15 */@RestController@RequiredArgsConstructor@RequestMapping("/example")public class TestResultController { @GetMapping("/testResult") public Test testResult() { Test test = new Test(); test.setName("笑小枫"); test.setAge(18); test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗"); return test; } @GetMapping("/testResultJson") public ResultJson testResultJson() { Test test = new Test(); test.setName("笑小枫"); test.setAge(18); test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗"); return new ResultJson(test); } @Data static class Test { private String name; private Integer age; private String remark; }}咱们先看看这是调用返回的后果 ...

March 7, 2023 · 3 min · jiezi

关于springboot:笑小枫的SpringBoot系列三SpringBoot集成Mybatis-Plus

3. 集成Mybatis Plus3.1 配置根底依赖⚙️首先,我的项目中会应用到mysql、mybatis-plus、druid数据连接池等等性能,接下来咱们就一一解说一下。 依赖名称依赖形容版本mysql-connector-java:mysql-connector-javamysql驱动8.0.29com.alibaba:druid阿里巴巴Druid数据库连接池1.2.11com.baomidou:mybatis-plus-boot-startermybatis-plus的依赖3.5.2com.baomidou:mybatis-plus-generatormybatis-plus的主动生成代码插件3.5.2org.apache.velocity:velocity-engine-coreJava 的模板引擎框架,用于代码主动生成2.3org.projectlombok:lombok代码简化,getter/setter、结构器编译时生成1.18.24com.alibaba:fastjson阿里巴巴Json操作工具类2.0.7残缺的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.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.maple</groupId> <artifactId>maple-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>maple-demo</name> <description>Demo project for Spring Boot</description> <properties> <java-version>1.8</java-version> <lombok-version>1.18.24</lombok-version> <druid-version>1.2.11</druid-version> <mybatis-plus-version>3.5.2</mybatis-plus-version> <velocity-version>2.3</velocity-version> <fastjson-version>2.0.7</fastjson-version> <mysql-connector-version>8.0.29</mysql-connector-version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!-- 引入web相干 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--应用Mysql数据库--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector-version}</version> </dependency> <!--应用阿里巴巴druid数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid-version}</version> </dependency> <!-- mybatis-plus的依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus-version}</version> </dependency> <!-- mybatis-plus的主动生成代码插件 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>${mybatis-plus-version}</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>${velocity-version}</version> </dependency> <!--Lombok治理Getter/Setter/log等--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> <version>${lombok-version}</version> </dependency> <!-- 解决JSON --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson-version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>3.2 引入Mysql配置首先咱们须要创立一个Mysql数据库maple,字符集抉择utf8。这里咱们应用的工具是Navicat Premium 11。当然其余工具都是一样,没有特使要求。 ...

March 7, 2023 · 5 min · jiezi

关于springboot:笑小枫的SpringBoot系列二基于swagger2的knife4j接口文档

2 配置后端接口2.1 什么是knife4jKnife4j的前身是swagger-bootstrap-ui,前身swagger-bootstrap-ui是一个纯swagger-ui的ui皮肤我的项目 一开始我的项目初衷是为了写一个加强版本的swagger的前端ui,然而随着我的项目的倒退,面对越来越多的个性化需要,不得不编写后端Java代码以满足新的需要,在swagger-bootstrap-ui的1.8.5~1.9.6版本之间,采纳的是后端Java代码和Ui都混合在一个Jar包外面的形式提供给开发者应用.这种形式虽说对于集成swagger来说很不便,只须要引入jar包即可,然而在微服务架构下显得有些臃肿。 因而,我的项目正式更名为knife4j,取名knife4j是心愿她能像一把匕首一样玲珑,轻量,并且性能强悍,更名也是心愿把她做成一个为Swagger接口文档服务的通用性解决方案,不仅仅只是专一于前端Ui前端. swagger-bootstrap-ui的所有个性都会集中在knife4j-spring-ui包中,并且后续也会满足开发者更多的个性化需要. 次要的变动是,我的项目的相干类包门路更换为com.github.xiaoymin.knife4j前缀,开发者应用加强注解时须要替换包门路 后端Java代码和ui包拆散为多个模块的jar包,以面对在目前微服务架构下,更加不便的应用加强文档注解(应用SpringCloud微服务项目,只须要在网关层集成UI的jar包即可,因而拆散前后端) knife4j沿用swagger-bootstrap-ui的版本号,第1个版本从1.9.6开始,对于应用办法,请参考文档。 2.2 怎么应用knife4j第一步:在maven我的项目的pom.xml中引入Knife4j的依赖包,代码如下: <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>2.0.7</version></dependency>第二步:创立Swagger配置依赖,代码如下: package com.maple.demo.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import springfox.documentation.builders.ApiInfoBuilder;import springfox.documentation.builders.PathSelectors;import springfox.documentation.builders.RequestHandlerSelectors;import springfox.documentation.service.Contact;import springfox.documentation.spi.DocumentationType;import springfox.documentation.spring.web.plugins.Docket;import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;/** * @author 笑小枫 * @date 2022/6/28 */@Configuration@EnableSwagger2WebMvcpublic class Knife4jConfiguration { @Bean(value = "example") public Docket example() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(new ApiInfoBuilder() .title("笑小枫实例演示接口") .description("笑小枫实例演示接口") .termsOfServiceUrl("http://127.0.0.1:6666") .contact(new Contact("笑小枫", "https://www.xiaoxiaofeng.site", "zfzjava@163.com")) .version("1.0") .build()) //分组名称 .groupName("演示实例接口") .select() //这里指定Controller扫描包门路 .apis(RequestHandlerSelectors.basePackage("com.maple.demo.controller")) .paths(PathSelectors.any()) .build(); }}第三步:创立一个Controller package com.maple.demo.controller;import io.swagger.annotations.Api;import io.swagger.annotations.ApiModelProperty;import io.swagger.annotations.ApiOperation;import lombok.Data;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/** * @author zhangfuzeng * @date 2022/6/30 */@Api(tags = "实例演示-Knife4j接口文档")@RestController@RequestMapping("/example")public class TestKnife4jController { @ApiOperation(value = "Knife4j接口文档演示") @GetMapping("/testKnife4j") public Test testKnife4j(Test param) { Test test = new Test(); test.setName("笑小枫"); test.setAge(18); test.setRemark("大家好,我是笑小枫,喜爱我的小伙伴点个赞呗,欢送拜访我的集体博客:http://www.xiaoxiaofeng.site"); return test; } @Data static class Test { @ApiModelProperty(value = "姓名") private String name; @ApiModelProperty(value = "年龄") private Integer age; @ApiModelProperty(value = "形容") private String remark; }}2.3 看下页面成果我的项目启动后,在浏览器输出http://127.0.0.1:6666/doc.html拜访 ...

March 7, 2023 · 2 min · jiezi

关于springboot:SpringBoot-SSM-vue课程作业在线批阅系统

SpringBoot SSM vue课程作业在线批阅零碎登录 新闻布告 在线留言 课程管理 指定课代表 作业下发 作业审批 评分 在线预览 所列性能残缺 应用技术: SpringBoot或SSM + Mybatis + Mysql + vue(前端)

March 6, 2023 · 1 min · jiezi

关于springboot:Spring-Boot2中如何优雅地个性化定制Jackson

概述本文的编写初衷,是想理解一下Spring Boot2中,具体是怎么序列化和反序列化JSR 310日期工夫体系的,Spring MVC利用场景有如下两个: 应用@RequestBody来获取JSON参数并封装成实体对象;应用@ResponseBody来把返回给前端的数据转换成JSON数据。对于一些Integer、String等根底类型的数据,Spring MVC能够通过一些内置转换器来解决,无需用户关怀,然而日期工夫类型(例如LocalDateTime),因为格局多变,没有内置转换器可用,就须要用户本人来配置和解决了。 浏览本文,假如读者初步理解了如何应用Jackson。测试环境本文应用Spring Boot2.6.6版本,锁定的Jackson版本如下: <jackson-bom.version>2.13.2.20220328</jackson-bom.version>Jackson解决JSR 310日期工夫须要引入依赖: <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.13.2</version></dependency>Spring Boot主动配置在spring-boot-autoconfigure包中,主动配置了Jackson: package org.springframework.boot.autoconfigure.jackson;@Configuration(proxyBeanMethods = false)@ConditionalOnClass(ObjectMapper.class)public class JacksonAutoConfiguration { // 具体代码略}其中有一段代码配置了ObjectMapper @Bean@Primary@ConditionalOnMissingBeanObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { return builder.createXmlMapper(false).build();}能够看到ObjectMapper是由Jackson2ObjectMapperBuilder构建的。 再往下会看到如下代码: @Configuration(proxyBeanMethods = false)@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)static class JacksonObjectMapperBuilderConfiguration { @Bean @Scope("prototype") @ConditionalOnMissingBean Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(ApplicationContext applicationContext, List<Jackson2ObjectMapperBuilderCustomizer> customizers) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.applicationContext(applicationContext); customize(builder, customizers); return builder; } private void customize(Jackson2ObjectMapperBuilder builder, List<Jackson2ObjectMapperBuilderCustomizer> customizers) { for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) { customizer.customize(builder); } }}发现在这里创立了Jackson2ObjectMapperBuilder,并且调用了customize(builder, customizers)办法,传入Lis<Jackson2ObjectMapperBuilderCustomizer> 进行定制ObjectMapper。 ...

March 3, 2023 · 2 min · jiezi

关于springboot:Spring-Boot连接多个数据库

微服务架构流行的明天,应用服务与数据库的对应关系通常是一个微服务对应一个数据库。在一个电商零碎中,用户信息存储在用户数据库中,订单信息存储在订单数据库中,如果一次申请须要获取订单相干信息,则须要将订单信息和下单的用户信息一起查问进去。 在订单服务中获取用户数据不会间接调用用户数据库,而是通过调用用户治理服务的接口。 这种架构在大型零碎中劣势很显著,在业务上能够解耦,防止一个服务对用多个数据库,导致业务凌乱,系统维护艰难。也能够进步数据库的安全性。利用场景然而在些B端的管理系统,架构通常没有这么简单(单体架构),只有一个服务。这种利用也有连贯多个数据库的需要,例如同步数据库。 Spring Boot 连贯数据库默认配置这里应用Spring Boot 和 Mybatis 。 因为Spring boot 的易用性,默认状况下,咱们只须要在application配置文件中, 配置好数据源的信息Mybtis的Mapper扫描门路,以及在Configuration类中配置MapperScan配置对应Mapper类的包门路spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8 username: root password: 12345mybatis: mapper-locations: /mapper/**.xml@SpringBootApplication@MapperScan("icu.daydream.demo.mybatisdemo.mapper")public class MybatisDemoApplication { public static void main(String[] args) { SpringApplication.run(MybatisDemoApplication.class, args); }}实现以上三个配置,不须要再创立 DataSource 对象,以及 SqlSessionFactory 对象,即可使利用主动连贯上数据库。体现了Spring Boot 约定大于配置 的个性。 在Spring Boot 中利用中如何同时连贯多个数据库呢?当初咱们的需要是同时连贯两个数据库,Spring Boot 的默认配置曾经满足不了需要,因而就须要手动的去配置数据源信息。 在application.yaml中配置两个自定义的数据源属性 创立 DataSource 对象和 SqlSessionFactory 对象先创立连贯数据库1的 须要的对象。 @Configuration@MapperScan(basePackages = DataSourceConfig1.PACKAGE,sqlSessionFactoryRef = "sqlSessionFactory1")public class DataSourceConfig1 { //数据库1 扫描的Mapper类门路 final static String PACKAGE = "test.mapper.db1"; @Bean(name = "dataSource1") //将yaml文件中的 datasource1 下的属性 注入到 DataSource对象中 @ConfigurationProperties(prefix = "datasource1") public DataSource businessDbDataSource() { return new DruidDataSource(); } @Bean(name = "sqlSessionFactory1") public SqlSessionFactory sqlSessionFactory1(@Qualifier("dataSource1") DataSource dataSource ) throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource ); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mapper/db1/*.xml")); return factoryBean.getObject(); } } 默认配置下这两个 Bean 对象是主动创立的,当初显式创立后,Spring Boot 的默认配置对象就不会再创立了, application.yaml 中mybatis下的一些配置也跟着生效了。须要显式创立 Myabtis 的 Configuration 配置对象。这个前面再讲。目前曾经配置好了数据库1的连贯,同样地,再创立一个连贯数据库2的对象。 ...

March 2, 2023 · 2 min · jiezi

关于springboot:通过skaffold快速部署微服务

通过skaffold疾速部署微服务随着技术的一直倒退,程序员们相熟的传统单体利用开发流程,慢慢地无奈适应当下微服务化的潮流趋势。同时随着云原生开发的理念一直推广,越来越多的服务运行在不可变的基础设施之上,随之而来的是传统单体利用开发流程与云化水平日益加深服务之间的隔膜越发微小,开发人员越来越难以容忍反复繁琐且容易出错的低效率开发流程。因而,一款面向开发人员而运维施行人员的继续构建与继续部署工具 skaffold 应运而生skaffold简介[skaffold]() 是一款 Google 推出的继续构建与继续部署工具,它次要面向开发人员而非运维施行人员,指标是突破本地开发与云化部署之间的隔膜,加重开发人员的心智累赘,帮忙开发人员专一于杰出地实现日常开发工作,防止开发人员在缭乱繁冗的运维流程中过多耗费贵重的精力与工夫。 根本架构 skaffold 的工作流依照开发流程的不同阶段,分为4个局部组成: 本地开发(文件同步)继续构建继续测试继续部署以上四个局部均能够依据理论需要进行定制化批改。 本地开发skaffold 对支流的编程语言以及配套应用的技术栈都有着十分不错的反对,例如 Go 、Java、JavaScript 等 本地开发的核心内容是 文件同步,文件同步的监听对象大略能够分为 源代码 和 编译产物 。 skaffold 官网的举荐做法是监听源代码变动,而后自动化把源代码复制到Docker容器中进行编译和构建。 这种做法的问题不少,首先是源代码变动十分频繁,而编译和构建过程往往十分耗时,因而主动触发构建不太正当。 其次,在Docker容器中编译和构建,须要把握编写 Multi Stage Dockerfile 技能,否则构建进去的镜像大小会占据十分大的空间,另外还要耗费本就不拮据的带宽进行镜像传输。 最初,在Docker容器中编译和构建,要解决环境变量,代理设置、缓存构建两头后果等一系列问题,对老手十分不敌对。 因而,集体举荐,在本地开发环节尽量采纳手动触发编译构建,通过监听编译产物的形式来触发热更新等流程。 继续构建因为抉择手动触发编译,所以本环节的内容次要讲述如何打包镜像的内容 目前 skaffold 官网反对的构建形式有三种:Docker、Jib(maven/gradle)、Bazel DockerJib(maven/gradle)Bazel这里以最常见 Docker 为例: build: local: push: false # 镜像打包胜利后是否推送到远端的镜像仓库 artifacts: # 反对打包多个不同组件的镜像 - image: datacenter-eureka # 打包后的镜像名称 context: "eureka" # Dockerfile相对路径,就放在eureka目录下 docker: dockerfile: Dockerfile - image: datacenter-school # 打包后的镜像名称 context: "school" # Dockerfile相对路径 docker: dockerfile: Dockerfile - image: datacenter-teacher # 打包后的镜像名称 context: "teacher" # Dockerfile相对路径 docker: dockerfile: Dockerfile - image: datacenter-student # 打包后的镜像名称 context: "student" # Dockerfile相对路径 docker: dockerfile: Dockerfile当运行 skaffold dev 时,会依照 编译 —> 构建 -> 测试 -> 部署 的规范流程走一遍。当监听到指定门路下的文件发生变化时,skaffold工具会尝试通过相似于 kubectl cp 命令的形式,间接把产生变动后的文件拷贝到运行中的容器外部,防止从新走一遍编译构建/上传镜像的步骤,缩小同步代码更改而耗费的工夫。 ...

February 26, 2023 · 9 min · jiezi

关于springboot:Spring-AOT介绍

Spring对AOT优化的反对意味着将哪些通常在运行时才产生的事件提前到编译期做,包含在构建时查看ApplicationContext,反对决策和发现执行逻辑。这样做能够构建一个更间接的应用程序启动安顿,并次要基于类门路和环境来关注一组固定的个性。 反对这样的优化意味着须要对原Spring利用做如下的限度: classpath是固定的,并在在构建时就曾经全副指定了。bean的定义在运行时不能扭转。 @Profile,特地是须要在构建时抉择特定于配置文件的配置影响bean存在的环境属性配置@Conditional仅能在构建时思考带有Supplier(包含lambda和办法援用)的Bean的定义不能被AOT转换。@Bean注解的办法的返回类型得是具体的类,而不能是接口了,以便容许正确的提醒推断。当以上的限度都防止了,就能够在构建时执行AOT的解决并生成额定的资产。 通过Spring AOT解决过的利用,通过会生成如下资产: Java源码字节码RuntimeHints,用于反射,资源定位,序列化和Java反射在当前情况下,Spring AOT专一于应用GraalVM将Spring的利用部署为原生的镜像,后续可能会反对更多的JVM。 AOT引擎介绍用于解决ApplicationContext排列的AOT引擎的入口点是ApplicationContextAotGenerator.它负责以下步骤,其基于的参数GenericApplicationContext示意要被优化的利用,和一个通用的上下文参数GenerationContext. 刷新用于AOT解决的ApplicationContext。与传统的刷新不同,此版本只创立bean定义,而不是bean实例调用可用的BeanFactoryInitializationAotProcessor的具体实现,并对GenerationContext应用。例如,外围实现 迭代所有候选bean definition,并生成必要的代码以复原BeanFactory的状态。一旦该解决实现,GenerationContext将被那些利用运行所必须的已生成代码、资源和类更新。RuntimeHints实例能够用于生成与GraalVM相干的原生镜像配置文件。 ApplicationContextAotGenerator#processAheadOfTime返回ApplicationContextInitializer入口点的类名,该入口点容许应用AOT优化启动上下文。 刷新AOT的解决所有GenericApplicationContext的实现都反对AOT解决的刷新。应用程序上下文由任意数量的入口点创立,通常以@Configuration注解类的模式。 通常的实现如下: @Configuration(proxyBeanMethods=false)@ComponentScan@Import({DataSourceConfiguration.class, ContainerConfiguration.class})public class MyApplication {}应用惯例运行时启动此应用程序波及许多步骤,包含类门路扫描、配置类解析、bean实例化和生命周期回调解决。AOT解决的刷新仅利用惯例刷新的子集。AOT解决可按如下形式触发: RuntimeHints hints = new RuntimeHints();AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.register(MyApplication.class);context.refreshForAotProcessing(hints);// ...context.close();在AOT模式下,BeanFactoryPostProcessor扩大点的实现和平时一样调用。包含configuration类的解析、import selector和类扫描等。这些步骤确保BeanRegistry蕴含应用程序的相干bean定义.如果Bean definition收到conditions(如 @Profile)的爱护,则在该阶段会被摈弃。因为此模式实际上不创立Bean的实例,除了与AOT相干的变体实现之外,BeanPostProcessor将不会被调用。变体实现包含: MergedBeanDefinitionPostProcessor的实现,后处理bean定义以提取其余设置,如init和destroy办法SmartInstantiationAwareBeanPostProcessor的实现,如果须要,确定更准确的bean类型,这确保创立运行时须要的任何代理类。一旦该步骤实现,BeanFactory就蕴含了利用运行所必须的bean definition 汇合。它不触发bean实例化,但容许AOT引擎查看将在运行时创立的bean。 Bean工厂初始化AOT奉献心愿参加此步骤的组件能够实现BeanFactoryInitializationAotProcessor接口。每个实现都能够依据bean工厂的状态返回AOT奉献。 AOT奉献是奉献生成的代码能够再现特定行为的组件。它还能够提供RuntimeHints来批示反射、资源加载、序列化或JDK代理的须要. BeanFactoryInitializationAotProcessor的实现能够注册在META-INF/spring/aot.factories中,key为该接口的全限定名。 BeanFactoryInitializationAotProcessor也能够间接被一个bean实现。在这种模式下,bean提供的AOT奉献与它在惯例运行时提供的个性相当。因而,这样的bean会主动从AOT优化上下文中排除。 留神: 如果bean实现了BeanFactoryInitializationAotProcessor接口,那么在AOT解决期间将初始化bean及其所有依赖项。咱们通常倡议此接口仅由根底构造bean(如BeanFactoryPostProcessor)实现,这些bean具备无限的依赖性,并且在bean工厂生命周期的晚期就曾经初始化。如果这样的bean是应用@bean工厂办法注册的,请确保该办法是动态的,以便其关闭的@Configuration类不用初始化。 Bean注册AOT奉献BeanFactoryInitializationAotProcessor实现的外围性能是负责为每个候选BeanDefinition收集必要的奉献。它应用专用的BeanRegistryAotProcessor来实现。 该接口的应用形式如下: 由BeanPostProcessorbean实现,以替换其运行时行为。例如,AutowiredAnnotationBeanPostProcessor实现了这个接口,以生成注入用@Autowired正文的成员的代码。由META-INF/spring/aot.factors中注册的类型实现,其key等于接口的齐全限定名称。通常在须要针对外围框架的特定个性进行调整的bean定义时应用。留神: 如果一个bean实现了BeanRegistryAotProcessor接口,那么在AOT解决期间将初始化该bean及其所有依赖项。咱们通常倡议此接口仅由根底构造bean(如BeanFactoryPostProcessor)实现,这些bean具备无限的依赖性,并且在bean工厂生命周期的晚期就曾经初始化。如果这样的bean是应用@bean工厂办法注册的,请确保该办法是动态的,以便其关闭的@Configuration类不用初始化。 如果没有BeanRegisterationAotProcessor解决特定注册的bean,则默认实现会解决它。这是默认行为,因为为bean definition 调整生成的代码应该仅限于比拟冷门的应用案例。 以后面的示例为例,咱们假如DataSourceConfiguration如下: @Configuration(proxyBeanMethods = false)public class DataSourceConfiguration { @Bean public SimpleDataSource dataSource() { return new SimpleDataSource(); }}因为该类上没有任何特定条件,因而dataSourceConfiguration和dataSource被标识为候选项。AOT引擎会将下面的配置类转换为与以下相似的代码: /** * Bean definitions for {@link DataSourceConfiguration} */public class DataSourceConfiguration__BeanDefinitions { /** * Get the bean definition for 'dataSourceConfiguration' */ public static BeanDefinition getDataSourceConfigurationBeanDefinition() { Class<?> beanType = DataSourceConfiguration.class; RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); beanDefinition.setInstanceSupplier(DataSourceConfiguration::new); return beanDefinition; } /** * Get the bean instance supplier for 'dataSource'. */ private static BeanInstanceSupplier<SimpleDataSource> getDataSourceInstanceSupplier() { return BeanInstanceSupplier.<SimpleDataSource>forFactoryMethod(DataSourceConfiguration.class, "dataSource") .withGenerator((registeredBean) -> registeredBean.getBeanFactory().getBean(DataSourceConfiguration.class).dataSource()); } /** * Get the bean definition for 'dataSource' */ public static BeanDefinition getDataSourceBeanDefinition() { Class<?> beanType = SimpleDataSource.class; RootBeanDefinition beanDefinition = new RootBeanDefinition(beanType); beanDefinition.setInstanceSupplier(getDataSourceInstanceSupplier()); return beanDefinition; }}依据bean定义的确切性质,生成的确切代码可能有所不同。 ...

February 25, 2023 · 2 min · jiezi

关于springboot:SpringBoot个人博客系统

SpringBoot集体博客零碎首页文章列表 查看文章 评论文章 归档 关键字搜寻 友情链接 对于 后盾治理性能: 登录 集体设置 仪表盘 公布文章 文章治理 页面治理 评论治理 分类标签治理 文件治理 友情链接治理 零碎设置 所列性能残缺 应用技术: SpringBoot + Mybatis + mysql + thymeleaf(前端)

February 22, 2023 · 1 min · jiezi

关于springboot:谁说-Java-不能多继承

能够看到当咱们在B类上增加注解@InheritClass并指定A1.class和A2.class之后,咱们的B实例就有了A1和A2的属性和办法就如同B同时继承了A1和A2这。。。难道是黑魔法?(为什么脑子里会忽然冒出来巴啦啦能量?) 来人,把.class文件带上来 其实就是把A1和A2的属性和办法都复制到了B上,和继承没有半毛钱关系! 这玩意儿有啥用说起来当初实现的性能和当初的目标还是有点出入的家喻户晓,Lombok中提供了@Builder的注解来生成一个类对应的Builder然而我想在build之前校验某些字段就不太好实现于是我就思考,能不能实现一个注解,只是生成对应的字段和办法(毕竟最麻烦的就是要复制一堆的属性),而build办法由咱们本人来实现,相似上面的代码public class A { private String a;public A(String a) { this.a = a;}@BuilderWith(A.class)public static class Builder { //注解主动生成 a 属性和 a(String a) 办法 public A build() { if (a == null) { throw new IllegalArgumentException("a is null"); } return new A(a); }}}复制代码这样的话,咱们不仅不必手动解决大量的属性,还能够在build之前退出额定的逻辑,不至于像Lombok的@Builder那么不灵便 而后在前面实现的过程中就发现:能够把一个类的属性复制过去,那也能够把一个类的办法复制过去!能够复制一个类,那也能够复制多个类!于是就倒退成了当初这样,给人一种多继承的错觉所以说这种形式也会存在很多限度和抵触,比方雷同名称但不同类型的字段,雷同名称雷同入参但不同返回值的办法,或是调用了super的办法等等,毕竟只是一个缝合怪这兴许就是Java不反对多继承的次要起因,不然要校验要留神的中央就太多了,一不小心就会有歧义,出问题目前我次要能想到两种应用场景BuilderBuilder原本就是我最后的目标,所以必定要想着法儿的实现public class A { private String a;public A(String a) { this.a = a;}@InheritField(sources = A.class, flags = InheritFlag.BUILDER)public static class Builder { //注解主动生成 a 属性和 a(String a) 办法 public A build() { if (a == null) { throw new IllegalArgumentException("a is null"); } return new A(a); }}}复制代码这个用法和之前构想的没有太大区别,就是对应的注解有点不太一样@InheritField能够用来复制属性,而后flags = InheritFlag.BUILDER示意同时生成属性对应的办法参数组合另一种场景就是用来组合参数比方咱们当初有两个实体A和B@Datapublic class A { ...

February 22, 2023 · 3 min · jiezi

关于springboot:记录多项目共用一个公众号逻辑修改

前言微信扫码登陆,前段时间写完微信扫码登录后,因为有多个我的项目都须要微信登录,而公众号的数量无限。 所以须要钻研一下多个我的项目应用同一个公众号登录。 思路原来的思路: 每个后盾与微信服务器之间进行通信, 须要多个公众号 那么,应用一个公众号的话,建设一个两头服务器,与微信进行通信就行了。 先说一下前提: 本我的项目是先登录,而后绑定微信用户后, 后续能力应用微信登录。 有了思路之后流程大略如下: 如果在登录的零碎是: 我的项目1, 而服务器收到微信推送事件后须要做这几件事 判断该事件是否是向 我的项目1 推送校验该微信用户是否与 我的项目1 绑定服务器 向 我的项目1 发送 登录胜利的申请。我的项目1 向登录胜利的客户端,执行登录胜利逻辑服务器向微信公众号, 发送登录胜利的信息。 如何判断事件是否属于我的项目1解决:我的项目1向服务器发送申请的时候带上我的项目关键字 例如: 我的项目的名称为 schedule, 则申请的时候带上参数为shcedule 例如第5行, 参数带上我的项目的关键字 1 final Map<String, String> variables = new HashMap<>();2 variables.put("client", this.wxMpConfig.getClient());3 variables.put("username", userDetails.getUsername());4 variables.put("sessionId", sessionId);5 String requestUrl = UserServiceImpl.addParam( this.wxMpConfig.getService() + "request/getBindQrCode", variables);6 RestTemplate restTemplate = new RestTemplate();7 String bindQrCode = restTemplate.getForObject(requestUrl, String.class, variables);如何校验该微信用户是否与 我的项目1 绑定办法1 :向我的项目1 发送申请, 让我的项目1来判断。 ...

February 21, 2023 · 1 min · jiezi

关于springboot:SpringBoot集成Tomcat服务

应用的老本越低,外部封装越简单;一、Tomcat集成1、依赖层级在SpringBoot框架的web依赖包中,引入的是内嵌Tomcat组件,基于SpringBoot的版本,Tomcat集成的是9.0版本; <!-- 1、我的项目工程依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.5.RELEASE</version></dependency><!-- 2、starter-web依赖 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.2.5.RELEASE</version> <scope>compile</scope></dependency><!-- 3、starter-tomcat依赖 --><dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.31</version> <scope>compile</scope></dependency>2、自动化配置在SpringBoot框架的主动配置类中,Web我的项目中不显式更换其余服务依赖时,默认提供了对Tomcat服务的治理; @ConditionalOnWebApplication(type = Type.SERVLET)@EnableConfigurationProperties(ServerProperties.class)@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class})public class ServletWebServerFactoryAutoConfiguration { @Bean @ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat") public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer( ServerProperties serverProperties) { return new TomcatServletWebServerFactoryCustomizer(serverProperties); }}二、Tomcat架构 Server:代表整个Tomcat容器; Service:服务器外部的两头组件,将一个或多个Connector绑定到一个Engine上; Engine:示意特定服务的申请解决管道,接管Connector的申请并响应; Host:网络主机名称; Connector:连接器解决与客户端的通信; Context:代表一个Web应用程序的上下文; 参考Tomcat9.0版本的外围组件形容,对于框架有大抵的理解后,再去剖析集成原理,会更容易把握主线逻辑; 三、Tomcat配置1、根底配置在配置文件中,对Tomcat做一些基础性的设置,查看上面的配置类能够晓得,这些属性存在默认值; server: port: 8082 # 端口号 tomcat: # Tomcat组件 uri-encoding: UTF-8 # URI编码 max-threads: 100 # 最大工作线程 min-spare-threads: 10 # 最小工作线程2、属性配置类在服务配置中,提供多种服务器的适配,像Tomcat、Jetty、Netty、Undertow,从策略上看,配置分为公共属性以及各种服务器的适配属性; ...

February 21, 2023 · 2 min · jiezi

关于springboot:Springboot实现文件上传和下载

序言笔者最近在查看作业评阅零碎的时候,对于其中的附件上传和下载不太理解,通过写这篇文章来记录本人的播种。 文件传到哪里1.传到工程目录下在一些文件存储量很小的工程中,有一些上传文件搁置在工程自身的目录下,然而随着文件上传的量越来越大,工程自身所在的文件夹容量会越来越大,不仅打包和部署的效率会升高,工程的启动和运行也会变慢,所以个别不会采纳这做法。 2.上传到工程所在服务器将文件专门上传到Web利用工程所在容器(如Tomcat)位于的服务器中,独自开拓一个盘符或文件夹用于存储上传的图片,这种做法让上传 文件与工程自身拆散,工程的打包和启动效率不受到任何影响。然而如果当前呈现了海量图片,Web利用工程所在的服务器的效率会升高,这样也会间接地升高利用的执行效率,所以在上传图片量不大的状况下,能够采纳该做法。 3.搭建文件服务器个别大型的互联网我的项目,都会为本人的文件上传独自架设一个文件服务器(有集群的利用,可能会有多台文件服务器),也有独立解决文件上传、文件拜访的服务器。这种计划就是太烧钱。总结:下面剖析了三种计划的特点和优缺点。第一种个别不采取,第二种可能会采取,最罕用的就是第三种计划。笔者所用零碎是应用的第一种计划。 上传MultipartFile工具类MultipartFile是SpringMVC提供简化上传操作的工具类。在不应用框架之前,都是应用原生的HttpServletRequest来接管上传的数据,文件是以二进制流传递到后端的,而后须要咱们本人转换为File类,十分麻烦。应用了MultipartFile工具类之后,咱们对文件上传的操作就简便许多了。以下是MultipartFile工具类全副的接口办法: 办法名返回值作用getContentType()String在取文件MIME类型getlnputStream()InputStream获取文件流getName()String获取 form 表单中文件组件的名字getOriginalFilename()String获取上传文件件的原名getSize()long获取文件的大小,单位为byteisEmpty()boolean是否为空transferTo(File dest)void将数据保留到一个指标文件中实现上传外围代码: public void saveFile(MultipartFile multipartFile, Path saveFilePath) throws Exception { logger.debug("获取文件名"); String fileName = multipartFile.getOriginalFilename(); logger.debug("从文件名中截取拓展名"); // 从"."最初一次呈现的地位的下一位开始截取,获取扩展名 assert fileName != null; String ext = fileName.substring(fileName.lastIndexOf(".") + 1); logger.debug("设置保留文件名"); saveName = fileName; logger.debug("判断上传的文件是否为空"); if (multipartFile.isEmpty()) { throw new RuntimeException("上传的附件不能为空" + fileName); } logger.debug("如果目录不存在,则创立目录。如果目录存在,则不创立"); if (!Files.exists(saveFilePath)) { Files.createDirectories(saveFilePath); new File(saveFilePath.resolve("index.html").toString()).createNewFile(); } logger.debug("将文件挪动至贮存文件的门路下"); Files.copy(multipartFile.getInputStream(), saveFilePath.resolve(saveName), StandardCopyOption.REPLACE_EXISTING);}笔者间接用文件名来将文件存储在工程中,这种行为是很不平安的,所以倡议用本人定义的加密解密方法来解决此问题,存入服务器的文件就会变成二进制文件,当他人间接冲服务器拿到文件时,也无奈查看,这就保障了安全性。 下载下载局部外围代码 public void download(File f, OutputStream outputStream) { Path path = Paths.get(f.getPath()) .resolve(f.getName()); java.io.File file = path.toFile(); logger.debug("输入文件类型"); FileInputStream inputStream; try { inputStream = new FileInputStream(file); } catch (FileNotFoundException e) { logger.error("读取文件出错" + f.getId() + file.getAbsolutePath()); e.printStackTrace(); throw new RuntimeException("读取文件产生谬误"); } try { org.apache.commons.io.IOUtils.copy(inputStream, outputStream); } catch (IOException e) { logger.error("下发数据时产生了谬误"); e.printStackTrace(); throw new RuntimeException("下发数据时产生了谬误"); } }

February 20, 2023 · 1 min · jiezi

关于springboot:Ribbon工作流程细节

起因是ribbon集成spring boot、openfeign实现负载平衡近程调用,初始阶段没有增加上面配置,发现第一次进行近程调用,ribbon报错 【read time out】。而后增加如下配置,解决问题~ # 设置ribbon 我的项目启动时加载配置项,防止feign第一次调用【read time out】ribbon: eager-load: enabled: true clients: api-service下面的配置,从字面意思看进去,让Ribbon及时加载,那么问题来了?利用启动时,ribbon是怎么起作用的呢?ok,持续往下看! @ConfigurationProperties(prefix = "ribbon.eager-load")public class RibbonEagerLoadProperties { private boolean enabled = false; private List<String> clients;}看进去了吧,是org.springframework.cloud.netflix.ribbon.RibbonEagerLoadProperties 起作用了。那么这个类是如何被唤起了呢? @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled", havingValue = "true", matchIfMissing = true)public class RibbonAutoConfiguration { @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); @Autowired private RibbonEagerLoadProperties ribbonEagerLoadProperties; @Bean @ConditionalOnProperty("ribbon.eager-load.enabled") public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() { return new RibbonApplicationContextInitializer(springClientFactory(), ribbonEagerLoadProperties.getClients()); }}这个类在spring-cloud-netflix包中,ribbonEagerLoadProperties被注入进来,而后在ribbonApplicationContextInitializer()中申明RibbonApplicationContextInitializer类,它继承了ApplicationListener,在实现办法onApplicationEvent()中调用initialize(),从而把咱们的配置的指标clients(也就是"api-service")加载到org.springframework.cloud.context.named.NamedContextFactory的contexts中。上面是具体的调用链: ...

February 18, 2023 · 2 min · jiezi

关于springboot:深入学习-Spring-Web-开发-Bean-的附加注解

后面的文章,咱们介绍了 Bean 的申明与注入,对于 Bean 还有一些辅助性注解是十分重要的,本文,咱们重点聊聊这些辅助性注解。 @Scope@Scope 用于指定 Bean 的作用域。 比方,通过 @Scope 能够标注 Bean 的作用域为 singleton(这也是 Spring Bean 的默认作用域),如上面代码失去的 Bean 就是单例的: @Configurationpublic class BeanConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public MyBean singletonBean() { return new MyBean(); }}也能够通过 @Scope 标注 Bean 的作用域为 prototype,上面代码失去的 Bean 是多例的(每次拜访都会获取一个新的对象): @Configurationpublic class BeanConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public MyBean prototypeBean() { return new MyBean(); }}@Componentpublic class GetBeanTest { @Autowired private ScopeBeanConfig scopeBeanConfig; @EventListener private void getBeans(ApplicationReadyEvent event) { MyBean singletonBean1 = scopeBeanConfig.singletonBean(); MyBean singletonBean2 = scopeBeanConfig.singletonBean(); MyBean prototypeBean1 = scopeBeanConfig.prototypeBean(); MyBean prototypeBean2 = scopeBeanConfig.prototypeBean(); System.out.println("singletonBean1 与 singletonBean2 是同一个对象: " + ((singletonBean1.equals(singletonBean2) ? "是" : "不是"))); System.out.println("prototypeBean1 与 prototypeBean2 是同一个对象: " + (prototypeBean1.equals(prototypeBean2) ? "是" : "不是")); }}下面代码演示了屡次申请获取同一个 Bean 时,失去的对象是否是同一个,它的输入后果为: ...

February 17, 2023 · 2 min · jiezi

关于springboot:SpringBoot容器化的多环境配置

SpringBoot容器化的多环境配置部署通常会有多个环境,如"dev"/"test"/"prod"等环境容器化部署通常应用环境变量,而非手动批改配置例子来自《Spring Boot实战派》中,并进行简化简略例子为了简化,疏忽了其余无关紧要的文件示例只配置了"env"的值* src * main * java * com.xxc.hello // 门路太长就间接这么写了 * controller * HelloController.java * HelloWorldApplication.java * resources * application.yml * application-dev.yml * application-prod.yml* pom.xml* Dockerfilesrc/main/resources/application-dev.ymlenv: dev-envsrc/main/resources/application-prod.ymlenv: prod-envsrc/main/resources/application.yml下方代码标识application-dev.yml文件失效spring: profiles: active: devsrc/main/java/com/xxc/hello/HelloWorldApplication.java@EnableAutoConfiguration@SpringBootApplicationpublic class HelloWorldApplication { public static void main(String[] args) { SpringApplication.run(HelloWorldApplication.class, args); }}src/main/java/com/xxc/hello/controller/HelloController.java@RestControllerpublic class HelloController { @Value("${env}") private String env; @GetMapping("/env") public String getEnv() { return env; }}应用"http://localhost:8080/env" 获取到的值为"env-dev"应用参数指定失效配置先将我的项目打包成"hello.jar"应用"--spring.profiles.active=<环境>"参数来指定配置文件失效优先级:命令参数 > application-<指定环境>.yml > application.yml# 不指定则应用application.yml里指定的环境java -jar hello.jar# 环境中指定失效环境# 以下命令代表指定应用application-prod.yml配置java -jar hello.jar --spring.profiles.active=prod容器配置在Dockerfile中这样设置...略ENV env=devCMD java -jar hello.jar --spring.profiles.active=$env容器启动减少env参数以下命令指定了prod环境配置docker run --name hello -dp 8080:8080 hello:latest -e env=prod

February 15, 2023 · 1 min · jiezi

关于springboot:用这4招优雅的实现Spring-Boot-异步线程间数据传递

Spring Boot 自定义线程池实现异步开发置信大家都理解,然而在理论开发中须要在父子线程之间传递一些数据,比方用户信息,链路信息等等 比方用户登录信息应用ThreadLocal寄存保障线程隔离,代码如下: /** * @author 公众号:码猿技术专栏 * @description 用户上下文信息 */public class OauthContext { private static final ThreadLocal<LoginVal> loginValThreadLocal=new ThreadLocal<>(); public static LoginVal get(){ return loginValThreadLocal.get(); } public static void set(LoginVal loginVal){ loginValThreadLocal.set(loginVal); } public static void clear(){ loginValThreadLocal.remove(); }}那么子线程想要获取这个LoginVal如何做呢? 明天就来介绍几种优雅的形式实现Spring Boot 外部的父子线程的数据传递。 1. 手动设置每执行一次异步线程都要分为两步: 获取父线程的LoginVal将LoginVal设置到子线程,达到复用代码如下: public void handlerAsync() { //1\. 获取父线程的loginVal LoginVal loginVal = OauthContext.get(); log.info("父线程的值:{}",OauthContext.get()); CompletableFuture.runAsync(()->{ //2\. 设置子线程的值,复用 OauthContext.set(loginVal); log.info("子线程的值:{}",OauthContext.get()); }); }尽管可能实现目标,然而每次开异步线程都须要手动设置,反复代码太多,看了头疼,你认为优雅吗? 2. 线程池设置TaskDecoratorTaskDecorator是什么?官网api的大抵意思:这是一个执行回调办法的装璜器,次要利用于传递上下文,或者提供工作的监控/统计信息。 晓得有这么一个货色,如何去应用? ...

February 15, 2023 · 4 min · jiezi

关于springboot:JAVA接口和抽象类有什么区别

前言Java 是十分典型的面向对象语言,已经有一段时间,程序员终日把面向对象、设计模式挂在嘴边。尽管现在大家对这方面曾经不再那么狂热,然而不可否认,把握面向对象设计准则和技巧,是保障高质量代码的根底之一。本篇博文的重点是,接口和抽象类有什么区别? 概述接口和抽象类是 Java 面向对象设计的两个根底机制。接口是对行为的形象,它是形象办法的汇合,利用接口能够达到 API 定义和实现拆散的目标。接口,不能实例化;不能蕴含任何十分量成员,任何 field 都是隐含着 public static final 的意义;同时,没有非静态方法实现,也就是说要么是形象办法,要么是静态方法。Java 规范类库中,定义了十分多的接口,比方 java.util.List。抽象类是不能实例化的类,用 abstract 关键字润饰 class,其目标次要是代码重用。除了不能实例化,模式上和个别的 Java 类并没有太大区别,能够有一个或者多个形象办法,也能够没有形象办法。抽象类大多用于抽取相干 Java 类的共用办法实现或者是独特成员变量,而后通过继承的形式达到代码复用的目标。Java 规范库中,比方 collection 框架,很多通用局部就被抽取成为抽象类,例如 java.util.AbstractList。Java 类实现 interface 应用 implements 关键词,继承 abstract class 则是使 用 extends 关键词,咱们能够参考 Java 规范库中的 ArrayList。public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ //...}复制代码 注释Java 相比于其余面向对象语言,如 C++,设计上有一些根本区别,比方 Java 不反对多继承。这种限度,在标准了代码实现的同时,也产生了一些局限性,影响着程序设计构造。Java 类能够实现多个接口,因为接口是形象办法的汇合,所以这是申明性的,但不能通过扩大多个抽象类来重用逻辑。在一些状况下存在特定场景,须要形象出与具体实现、实例化无关的通用逻辑,或者纯调用关系的逻辑,然而应用传统的抽象类会陷入到单继承的困境。以往常见的做法是,实现由静态方法组成的工具类(Utils),比方 java.util.Collections。构想,为接口增加任何形象办法,相应的所有实现了这个接口的类,也必须实现新增办法,否则会呈现编译谬误。对于抽象类,如果咱们增加非形象办法,其子类只会享受到能力扩大,而不必放心编译出问题。接口的职责也不仅仅限于形象办法的汇合,其实有各种不同的实际。有一类没有任何办法的接口,通常叫作 Marker Interface,顾名思义,它的目标就是为了申明某些货色,比方咱们熟知的 Cloneable、Serializable 等。这种用法,也存在于业界其余的 Java 产品代码中。从外表看,这仿佛和 Annotation 殊途同归,也的确如此,它的益处是简略间接。对于 Annotation,因为能够指定参数和值,在表达能力上要更弱小一些,所以更多人抉择应用 Annotation。Java 8 减少了函数式编程的反对,所以又减少了一类定义,即所谓 functional interface,简略说就是只有一个形象办法的接口,通常倡议应用 @FunctionalInterface Annotation 来标记。Lambda 表达式自身能够看作是一类 functional interface,某种程度上这和面向对象能够算是两码事。咱们熟知的 Runnable、Callable 之类,都是 functional interface。还有一点可能让人感到意外,严格说,Java 8 当前,接口也是能够有办法实现的!从 Java 8 开始,interface 减少了对 default method 的反对。Java 9 当前,甚至能够定义 private default method。Default method 提供了一种二进制兼容的扩大已有接口的方法。比方,咱们熟知的 java.util.Collection,它是 collection 体系的 root interface,在 Java 8 中增加了一系列 default method,次要是减少 Lambda、Stream 相干的性能。我在专栏后面提到的相似 Collections 之类的工具类,很多办法都适宜作为 default method 实现在根底接口外面。你能够参考上面代码片段:public interface Collection<E> extends Iterable<E> { ...

February 14, 2023 · 1 min · jiezi

关于springboot:聊聊Spring中的Autowired注解

明天来跟大家聊聊简略聊聊@Autowired,Autowired翻译过去为主动拆卸,也就是主动给Bean对象的属性赋值。@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Autowired { /** * Declares whether the annotated dependency is required. * <p>Defaults to {@code true}. */boolean required() default true;}复制代码以上是@Autowired的定义,重点看 @Target,咱们发现@Autowired能够写在: ElementType.CONSTRUCTOR:示意能够写在构造方法上 ElementType.METHOD:示意能够写在一般办法上 ElementType.PARAMETER:示意能够写在办法参数前 ElementType.FIELD:示意能够写在属性上 ElementType.ANNOTATION_TYPE:示意能够写在其余注解上 写在构造方法上对于@Autowired写在构造方法上的状况,跟Spring抉择构造方法的逻辑无关,一个类中是不是有多个构造方法,是不是加了@Autowired注解,是不是有默认构造方法,跟构造方法参数类型和个数都有关系,前面独自来介绍。写在一般办法上对于@Autowired写在一般办法上的状况,咱们通常写的setter办法其实就是一个一般的setter办法,那非setter办法上加@Autowired会有作用吗?比方:@Componentpublic class UserService { @Autowiredpublic void test(OrderService orderService) { System.out.println(orderService);}}复制代码这个test办法会被Spring主动调用到,并且能打印出OrderService对应的Bean对象。写在办法参数前把@Autowired写在参数前没有多大意义,只在spring-test中有去解决这种状况,源码正文原文:Although @Autowired can technically be declared on individual method or constructor parameters since Spring Framework 5.0, most parts of the framework ignore such declarations. The only part of the core Spring Framework that actively supports autowired parameters is the JUnit Jupiter support in the spring-test module写在属性上这种状况不必多说了,值得注意的是,默认状况下,因为@Autowired中的required属性为true,示意强制依赖,如果更加某个属性找不到所依赖的Bean是不会赋null值的,而是会报错,如果把required属性设置为false,则会赋null值。写在其余注解上比方咱们能够自定义要给注解:@Autowired@Retention(RetentionPolicy.RUNTIME)public @interface HoellerAutowired {}复制代码@HoellerAutowired和@Autowired是等价的,能用@Autowired的中央都能够用@HoellerAutowired代替。以上,轻易写写,谢谢大家的观看。 ...

February 14, 2023 · 1 min · jiezi

关于springboot:SpringBoot自动配置简单梳理

一、主动配置的作用1.将第三方的组件主动装载到IOC容器外面,不须要开发人员再去编写相干的配置,比如说咱们在pom.xml中增加启动器spring-boot-starter-data-redis之后,咱们就能够间接应用@Autowired来注入RedisTemplate,这就是主动配置的益处。2.本文波及到@Import注解的应用、SpringFactories机制 二、主动配置的原理1.首先他是通过启动类上的@SpringBootApplication实现的,其中蕴含两个注解@SpringBootConfiguration、@EnableAutoConfiguration,而@SpringBootConfiguration能够看做@Configuration注解,次要实现主动配置的注解是@EnableAutoConfiguration 2.应用@Import并定义AutoConfigurationImportSelector来实现批量注册(不晓得@Import作用的同学倡议查一下该注解),而后咱们点进去其中有个getAutoConfigurationEntry public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return NO_IMPORTS; } else { AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata); return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); } }3.进入该办法getCandidateConfigurations protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) { if (!this.isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } else { AnnotationAttributes attributes = this.getAttributes(annotationMetadata); List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes); configurations = this.removeDuplicates(configurations); Set<String> exclusions = this.getExclusions(annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.getConfigurationClassFilter().filter(configurations); this.fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); } }4.能够看到他调用了SpringFactoriesLoader.loadFactoryNames办法,最终调用该类的loadSpringFactories,该办法能够让ClassLoader读取所有的jar包下的META-INF/spring.factories下的文件信息,该文件蕴含了第三方jar包所提供的@Configuration配置类的全限定类名 ...

February 12, 2023 · 1 min · jiezi

关于springboot:SpringBoot-三大开发工具你都用过么

本文曾经收录到Github仓库,该仓库蕴含计算机根底、Java根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等外围知识点,欢送star~ Github地址:https://github.com/Tyson0314/... 一、SpringBoot Dedevtools他是一个让SpringBoot反对热部署的工具,上面是援用的办法 要么在创立我的项目的时候间接勾选上面的配置: 要么给springBoot我的项目增加上面的依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional></dependency>复制代码idea批改完代码后再按下 ctrl + f9 使其从新编译一下,即实现了热部署性能eclipse是按ctrl + s保留 即可主动编译如果你想一批改代码就主动从新编译,无需按ctrl+f9。只须要上面的操作: 1.在idea的setting中把上面的勾都打上 2.进入pom.xml,在build的反标签后给个光标,而后按Alt+Shift+ctrl+/ 3.而后勾选上面的货色,接着重启idea即可 二、LombokLombok是简化JavaBean开发的工具,让开发者省去结构器,getter,setter的书写。 在我的项目初始化时勾选上面的配置,即可应用Lombok 或者在我的项目中导入上面的依赖: <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional></dependency>复制代码应用时,idea还须要下载上面的插件: 上面的应用的例子 import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableName;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@AllArgsConstructor//全参结构器@NoArgsConstructor//无参结构器@Data//getter + setterpublic class User { private Long id; private String name; private Integer age; private String email;}复制代码三、Spring Configuration Processor该工具是给实体类的属性注入开启提醒,自我感觉该工具意义不是特地大! 因为SpringBoot存在属性注入,比方上面的实体类: package org.lzl.HelloWorld.entity;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * @author Lenovo * */@Component@ConfigurationProperties(prefix = "mypet")public class Pet { private String nickName; private String strain; public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } public String getStrain() { return strain; } public void setStrain(String strain) { this.strain = strain; } @Override public String toString() { return "Pet [nickName=" + nickName + ", strain=" + strain + "]"; } }复制代码想要在application.properties和application.yml中给mypet注入属性,却没有任何的提醒,为了解决这一问题,咱们在创立SpringBoot的时候勾选上面的场景: ...

February 11, 2023 · 1 min · jiezi

关于springboot:maven多模块管理

基于 springboot + maven 开发,这里整顿一波多模块(module)我的项目的开发治理。 1. 聚合与继承多模块的我的项目构造,根本离不开聚合和继承两种maven治理形式。二者不是相悖的,很多我的项目构造是二者组合在一起治理的。 1.1. 聚合目标能够一次构建多个模块的我的项目。 当一个我的项目中有多个须要构建的模块我的项目时,如果每个模块独自构建,太费工作量。最好能够基于某个模块一次构建配置的所有模块,这就是聚合。 利用形式先确定一个专门用于打包的maven我的项目,针对该我的项目pom 文件做以下的非凡解决: <packaging> 值为 pom。(后续会讲 packaging 值的区别)基于<modules> 申明须要打包的所有模块,来实现模块的聚合。1.2. 继承1、目标缩小反复的配置。 继承比拟好了解,相似于java类的继承,子模块能够主动继承父模块的一些属性。在maven我的项目中,能够把多个子模块独特的配置放到父模块中,那么子模块就不须要反复保护配置了。 2、利用形式先确定一个父模块我的项目,针对该我的项目pom 文件做以下的非凡解决: <packaging> 值为 pom。(后续会讲 packaging 值的区别)子模块中须要申明:(1)<parent> 为父模块的信息;(2)<relativePath>为父模块 pom 的相对路径。当我的项目构建时,Maven会首先依据 <relativePath> 查看父POM,如果找不到,再从本地仓库查找。3、配置中可继承的元素groupId :项目组 ID ,我的项目坐标的外围元素;version :我的项目版本,我的项目坐标的外围元素;properties :自定义的 Maven 属性;dependencies :我的项目的依赖配置;dependencyManagement :醒目的依赖治理配置;repositories :我的项目的仓库配置;build :包含我的项目的源码目录配置、输入目录配置、插件配置、插件治理配置等;reporting :包含我的项目的报告输入目录配置、报告插件配置等;description :我的项目的形容信息;organization :我的项目的组织信息;inceptionYear :我的项目的开创年份;url :我的项目的 url 地址develoers :我的项目的开发者信息;contributors :我的项目的贡献者信息;distributionManagerment :我的项目的部署信息;issueManagement :缺点跟踪零碎信息;ciManagement :我的项目的继续继承信息;scm :我的项目的版本控制信息;mailingListserv :我的项目的邮件列表信息;4、dependencies和dependencyManagement(plugins与pluginManagement)(1)dependencies: 如果父我的项目pom中定义的是独自的 dependencies,则代表援用对应的所有依赖项。其所有子项目的pom中,就算没有引入父我的项目中定义的依赖,也主动会继承父我的项目pom文件 dependencies 中的所有依赖项。 (2)dependencyManagement: 父我的项目pom中只是申明依赖,并不实现引入,因而子项目须要显示的申明须要的依赖。当父我的项目中申明过一些依赖我的项目,但如果不在子项目中申明依赖,是不会从父我的项目中继承的。 另外,只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父我的项目中继承该项,并且version和scope都读取自父pom;如果子项目中指定了版本号,那么会应用子项目中指定的version和scope版本。 个别举荐用 dependencyManagement,因为配置起来更灵便,子项目应该按需配置须要的依赖项,以及可自定义版本号。但 dependency 也有它的作用,看理论状况搭配了。 plugins 与 pluginManagement 的关系就如同 dependencies 和dependencyManagement,这里就不多说了。 ...

February 9, 2023 · 4 min · jiezi

关于springboot:使用-ChatGPT-轻松创建用户注册页面

ChatGPT 是 OpenAI 开发的 GPT(Generative Pre-trained Transformer)语言模型的变体。它是一种大型单向语言模型,已在大型人类生成文本数据集上进行训练,包含来自社交媒体平台、论坛和聊天记录的对话。ChatGPT 的次要性能之一是它可能对提供给它的提醒生成相似人类的响应。这是通过应用 transformer 架构和对大型数据集进行预训练来实现的。ChatGPT 可能了解对话的上下文和内容,并依据该信息生成适当的响应。ChatGPT 可用于各种应用程序,包含聊天机器人、语言翻译和自然语言解决工作。它能够针对特定工作或畛域进行微调,办法是在特定于该工作或畛域的额定数据上对其进行训练。尽管 ChatGPT 不是专门为编写代码而设计的,但它有可能用于在某些状况下帮助编写代码。例如,它可能可能帮忙程序员生成代码片段或依据给出的提醒倡议解决问题的代替办法。当初就来探讨一些可能性。当初就来开始。 创立一个 HTML 网站,题目为“应用 ChatGPT 制作一个HTML页面”,蕴含文字和照片 以下是创立题目为“应用 ChatGPT 制作一个 HTML 页面”并蕴含文字和图片的根本 HTML 代码:<!DOCTYPE html><html><head> <title>Using ChatGPT to Make an HTML Page</title></head><body> <h1>Using ChatGPT to Make an HTML Page</h1> <p>Hello, I am ChatGPT, and I am here to help you create an HTML page. Below is an image:</p> <img src="image.jpg" alt="An example image"> <p>I hope this example helps you in creating your own HTML pages!</p></body></html>复制代码此外,ChatGPT 还提供了如何解决此代码的更多阐明:留神:上述代码假如 HTML 文件与名为 image.jpg 的图片位于同一目录。alt 属性提供了图片的文本形容,以防图片因任何起因无奈显示。 ...

February 8, 2023 · 2 min · jiezi

关于springboot:SpringBoot实现电子文件签字合同系统

本文曾经收录到Github仓库,该仓库蕴含计算机根底、Java根底、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等外围知识点,欢送star~ Github地址:https://github.com/Tyson0314/... 一、前言明天公司领导提出一个性能,说实现一个文件的签字+盖章性能,而后本人进行了简略的学习,对文档进行数字签名与签订纸质文档的起因大致相同,数字签名通过应用计算机加密来验证 (身份验证:验证人员和产品所申明的身份是否属实的过程。例如,通过验证用于签名代码的数字签名来确认软件发行商的代码起源和完整性。)数字信息,如文档、电子邮件和宏。数字签名有助于确保:真实性,完整性,不可否认性。目前市面上的电子签章产品也是多样化,然而不论是哪个厂家的产品,在线签章简略易用,同时也能保障签章的有效性,防篡改,防伪造,稳固,牢靠就是好产品。 此次开源的系统模拟演示了文件在OA零碎中的流转,次要为办公零碎跨平台在线解决Office文档提供了完满的解决方案。Word文档在线解决的外围环节,包含:起草文档、领导审批、核稿、领导盖章、正式发文。PageOffice产品反对PC端Word文档在线解决的所有环节;MobOffice产品反对了挪动端领导审批和领导盖章的性能。反对PC端和挪动端对文档审批和盖章的互认。而后此次博客中应用的卓正软件的电子签章采纳自主知识产权的外围智能辨认验证技术,确保文档安全可靠。采纳 COM、ActiveX嵌入式技术开发,确保软件可能反对多种利用。遵循《中华人民共和国电子签名法》对于电子签名的标准,同时反对国内通用的 RSA算法,符合国家平安规范。 PageOffice和MobOffice产品联合应用为跨平台解决Office文件提供了完满的解决方案,次要性能有word在线编辑保留和留痕,word和pdf文件在线盖章(电子印章)。 二、我的项目源码及部署1、我的项目构造及应用框架该签字+盖章流程零碎应用了SpringBoot+thymeleaf实现的,而后jar包依赖应用了maven 管制层@Controller@RequestMapping("/mobile")public class MobileOfficeController { @Value("${docpath}") private String docPath; @Value("${moblicpath}") private String moblicpath; @Autowired DocService m_docService; /** * 增加MobOffice的服务器端受权程序Servlet(必须) * */ @RequestMapping("/opendoc") public void opendoc(HttpServletRequest request, HttpServletResponse response, HttpSession session,String type,String userName)throws Exception { String fileName = ""; userName= URLDecoder.decode(userName,"utf-8"); Doc doc=m_docService.getDocById(1); if(type.equals("word")){ fileName = doc.getDocName(); }else{ fileName = doc.getPdfName(); } OpenModeType openModeType = OpenModeType.docNormalEdit; if (fileName.endsWith(".doc")) { openModeType = OpenModeType.docNormalEdit; } else if (fileName.endsWith(".pdf")) { String mode = request.getParameter("mode"); if (mode.equals("normal")) { openModeType = OpenModeType.pdfNormal; } else { openModeType = OpenModeType.pdfReadOnly; } } MobOfficeCtrl mobCtrl = new MobOfficeCtrl(request,response); mobCtrl.setSysPath(moblicpath); mobCtrl.setServerPage("/mobserver.zz"); //mobCtrl.setZoomSealServer("http://xxx.xxx.xxx.xxx:8080/ZoomSealEnt/enserver.zz"); mobCtrl.setSaveFilePage("/mobile/savedoc?testid="+Math.random()); mobCtrl.webOpen("file://"+docPath+fileName, openModeType , userName); } @RequestMapping("/savedoc") public void savedoc(HttpServletRequest request, HttpServletResponse response){ FileSaver fs = new FileSaver(request, response); fs.saveToFile(docPath+fs.getFileName()); fs.close(); }}复制代码我的项目业务层源码@Servicepublic class DocServiceImpl implements DocService { @Autowired DocMapper docMapper; @Override public Doc getDocById(int id) throws Exception { Doc doc=docMapper.getDocById(id); //如果doc为null的话,页面所有doc.属性都报错 if(doc==null) { doc=new Doc(); } return doc; } @Override public Integer addDoc(Doc doc) throws Exception { int id=docMapper.addDoc(doc); return id; } @Override public Integer updateStatusForDocById(Doc doc) throws Exception { int id=docMapper.updateStatusForDocById(doc); return id; } @Override public Integer updateDocNameForDocById(Doc doc) throws Exception { int id=docMapper.updateDocNameForDocById(doc); return id; } @Override public Integer updatePdfNameForDocById(Doc doc) throws Exception { int id=docMapper.updatePdfNameForDocById(doc); return id; }}复制代码拷贝文件public class CopyFileUtil { //拷贝文件 public static boolean copyFile(String oldPath, String newPath) throws Exception { boolean copyStatus=false; int bytesum = 0; int byteread = 0; File oldfile = new File(oldPath); if (oldfile.exists()) { //文件存在时 InputStream inStream = new FileInputStream(oldPath); //读入原文件 FileOutputStream fs = new FileOutputStream(newPath); byte[] buffer = new byte[1444]; int length; while ((byteread = inStream.read(buffer)) != -1) { bytesum += byteread; //字节数 文件大小 //System.out.println(bytesum); fs.write(buffer, 0, byteread); } fs.close(); inStream.close(); copyStatus=true; }else{ copyStatus=false; } return copyStatus; }}复制代码二维码源码public class QRCodeUtil { private String codeText;//二维码内容 private BarcodeFormat barcodeFormat;//二维码类型 private int width;//图片宽度 private int height;//图片高度 private String imageformat;//图片格式 private int backColorRGB;//背景色,色彩RGB的数值既能够用十进制示意,也能够用十六进制示意 private int codeColorRGB;//二维码色彩 private ErrorCorrectionLevel errorCorrectionLevel;//二维码纠错能力 private String encodeType; public QRCodeUtil() { codeText = "www.zhuozhengsoft.com"; barcodeFormat = BarcodeFormat.PDF_417; width = 400; height = 400; imageformat = "png"; backColorRGB = 0xFFFFFFFF; codeColorRGB = 0xFF000000; errorCorrectionLevel = ErrorCorrectionLevel.H; encodeType = "UTF-8"; } public QRCodeUtil(String text) { codeText = text; barcodeFormat = BarcodeFormat.PDF_417; width = 400; height = 400; imageformat = "png"; backColorRGB = 0xFFFFFFFF; codeColorRGB = 0xFF000000; errorCorrectionLevel = ErrorCorrectionLevel.H; encodeType = "UTF-8"; } public String getCodeText() { return codeText; } public void setCodeText(String codeText) { this.codeText = codeText; } public BarcodeFormat getBarcodeFormat() { return barcodeFormat; } public void setBarcodeFormat(BarcodeFormat barcodeFormat) { this.barcodeFormat = barcodeFormat; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public String getImageformat() { return imageformat; } public void setImageformat(String imageformat) { this.imageformat = imageformat; } public int getBackColorRGB() { return backColorRGB; } public void setBackColorRGB(int backColorRGB) { this.backColorRGB = backColorRGB; } public int getCodeColorRGB() { return codeColorRGB; } public void setCodeColorRGB(int codeColorRGB) { this.codeColorRGB = codeColorRGB; } public ErrorCorrectionLevel getErrorCorrectionLevel() { return errorCorrectionLevel; } public void setErrorCorrectionLevel(ErrorCorrectionLevel errorCorrectionLevel) { this.errorCorrectionLevel = errorCorrectionLevel; } private BufferedImage toBufferedImage(BitMatrix bitMatrix) { int width = bitMatrix.getWidth(); int height = bitMatrix.getHeight(); BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { image.setRGB(x, y, bitMatrix.get(x, y) ? this.codeColorRGB: this.backColorRGB); } } return image; } private byte[] writeToBytes(BitMatrix bitMatrix) throws IOException { try { BufferedImage bufferedimage = toBufferedImage(bitMatrix); //将图片保留到长期门路中 File file = java.io.File.createTempFile("~pic","."+ this.imageformat); //System.out.println("长期图片门路:"+file.getPath()); ImageIO.write(bufferedimage,this.imageformat,file); //获取图片转换成的二进制数组 FileInputStream fis = new FileInputStream(file); int fileSize = fis.available(); byte[] imageBytes = new byte[fileSize]; fis.read(imageBytes); fis.close(); //删除临时文件 if (file.exists()) { file.delete(); } return imageBytes; } catch (Exception e) { System.out.println(" Image err :" + e.getMessage()); return null; } } //获取二维码图片的字节数组 public byte[] getQRCodeBytes() throws IOException { try { MultiFormatWriter multiFormatWriter = new MultiFormatWriter(); //设置二维码参数 Map hints = new HashMap(); if (this.errorCorrectionLevel != null) { //设置二维码的纠错级别 hints.put(EncodeHintType.ERROR_CORRECTION, this.errorCorrectionLevel); } if (this.encodeType!=null && this.encodeType.trim().length() > 0) { //设置编码方式 hints.put(EncodeHintType.CHARACTER_SET, this.encodeType); } BitMatrix bitMatrix = multiFormatWriter.encode(this.codeText, BarcodeFormat.QR_CODE, this.width, this.height, hints); byte[] bytes = writeToBytes(bitMatrix); return bytes; } catch (Exception e) { e.printStackTrace(); return null; } }}复制代码2、我的项目下载及部署我的项目源码下载地址:download.csdn.net/download/we…下载我的项目源码后,应用idea导入slndemo我的项目并运行 ...

February 8, 2023 · 4 min · jiezi

关于springboot:Spring-Boot快速整合MybatisPlus

一、引言Mybatis-Plus是一个MyBatis的加强工具,在MyBatis的根底上只做加强不做扭转,为简化开发、提高效率而生。它曾经被大规模地利用在理论我的项目中,极大地提高了开发效率。 Mybatis-Plus次要有以下特点: 无侵入(不会对现有工程造成影响)弱小的CURD操作(内置通用Mapper、通用Service来实现单表的大部分CURD操作)内置多种插件(分页插件、性能剖析插件、全局拦挡插件)二、疾速整合这里我应用的Spring Boot版本:2.5.14 2.1 引入依赖在pom文件中引入mybatis-plus-boot-starter依赖,这里应用版本3.5.1,如下 <!-- springboot集成mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <!-- mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency>2.2 配置Mybatis-Plus及数据源上面是mybatis-plus的局部配置项,具体配置可参考官网https://baomidou.com/pages/56... server: port: 8081spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver username: root password: 123456 url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8mybatis-plus: # 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml # 如果是放在resource目录 classpath:/mapper/*Mapper.xml mapper-locations: classpath:/mapper/**/*.xml #实体扫描,多个package用逗号或者分号分隔 typeAliasesPackage: com.kamier.springboot.mybatisplus.domain.* configuration: #开启 Mybatis 二级缓存,默认为 true cache-enabled: false #配置JdbcTypeForNull, oracle数据库必须配置 jdbc-type-for-null: 'null' global-config: db-config: # AUTO:数据库自增 INPUT:自行设置 ASSIGN_ID:雪花算法id ASSIGN_UUID:UUID id-type: AUTO # 逻辑删除配置(上面3个配置) logic-delete-field: del_flag logic-delete-value: 1 logic-not-delete-value: 02.3 创立数据库实体类、Service接口及其实现类、Mapper接口、xml文件这里官网举荐应用MybatisX插件来主动生成这些文件,在idea中搜寻MybatisX插件装置即可(留神:如果我的项目不须要自定义编写简单的sql语句,能够不须要生成Mapper接口和xml文件)装置之后,右侧栏Database连贯数据库后,可选中多张表,右键点击MybatisX-Generator,配置选项可参考下图 ...

February 7, 2023 · 1 min · jiezi

关于springboot:第三方登陆实现微信扫码登录

前言各种官方网站通常都会有app、微信公众号等。比方央视网,银行等。 当咱们关注公众号或者app后,这些利用就能够在挪动端不便地将信息推送给用户。 对立各产品线的账号体系,实现一个账号处处应用的指标是十分有必要的。 于是有需要:能够实现微信扫码登陆。 微信对接通常网站与集体微信的对接有两种形式: 第一种是Oauth2登陆:用户通过扫描网站的二维码, 受权公开信息,如昵称、头像等。便能够胜利登陆网站。 例如在爱奇艺网站扫描二维码登陆后,受权给网站,之后胜利登陆爱奇艺。 登陆完之后,微信会提示信息。 第二种是关注微信公众号网站本人弹出一个二维码,扫描二维码后弹出公众号的关注界面、只有一关注公众号网站主动登录、第二次扫描登录的时候网站间接登录。 例如这种,轻易找的网站。扫码后跳到公众号,关注后主动登陆。 这两种扫码登陆办法,对应微信提供的两种开发方式 一种是基于微信公众平台的扫码登录,另一种是基于微信开放平台的扫码登录。 微信开放平台就是为了让第三方利用投入微信的怀抱而设计的,这第三方利用指的是比方android、ios、网站、零碎等;援用微信公众平台就是为了让程序员小伙伴利用微信自家技术(公众号、小程序)开发公众号、小程序而筹备的。微信开放平台入口:https://open.weixin.qq.com/ 微信公众平台入口:https://mp.weixin.qq.com/ 两者应用微信扫码登录的区别: 微信开放平台须要开企业认证能力注册。比拟难申请 微信公众平台须要认证微信服务号,能力进行扫码登录的开发。只需申请一个公众号。 上面采纳第二种。 后盾与公众号配置后盾应用的是spring boot。前台用的angular 这里引入github一个公众号开发工具包github仓库 后盾pom.xml依赖: <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-mp</artifactId> <version>4.4.0</version> </dependency> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>wx-java-mp-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency>1.申请一个公众号。若无,可用测试公众号。微信公众平台接口测试帐号申请 2.配置服务器信息URL为开发者服务器的接口地址,微信服务器通过该接口与开发者服务器建设连贯。Token可由开发者能够任意填写,用作生成签名(该Token会和接口URL中蕴含的Token进行比对,从而验证安全性)若是没有服务器,能够用ngfork进行内网穿透,收费好用。这里用的就是ngfork 3.配置后盾:# 公众号配置(必填)wx: mp: appid: wxaf7fe05a8xxxxxxxxx secret: 57b48fcec2d5db1axxxxxxxxxxx token: yunzhi aesKey: 123@Servicepublic class WeChatMpService extends WxMpServiceImpl { @Autowired private WxMpConfig wxMpConfig; @PostConstruct public void init() { final WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); // 设置微信公众号的appid config.setAppId(this.wxMpConfig.getAppid()); // 设置微信公众号的app corpSecret config.setSecret(this.wxMpConfig.getAppSecret()); // 设置微信公众号的token config.setToken(this.wxMpConfig.getToken()); // 设置音讯加解密密钥 config.setAesKey(this.wxMpConfig.getAesKey()); super.setWxMpConfigStorage(config); }}4.验证服务器地址的有效性开发者提交信息后,微信服务器将发送GET申请到方才填写的服务器地址URL上, ...

February 7, 2023 · 4 min · jiezi

关于springboot:SpringBoot访问windows共享文件

前言最近有我的项目须要开发档案打包下载性能,其中蕴含很多大附件,我的项目应用minio存储且不在同一台服务器上,为了优化速度决定应用windows共享性能进行文件传输 SMB1.0集成jcifs类库,次要实用于一些老旧零碎,但下载速度比较慢,仅作参考 此类库没有maven援用,官网地址:http://jcifs.samba.org/ 注意事项: 设置jcifs.smb.client.dfs.disabled选项开启,能够进步传输速度 应用NtlmPasswordAuthentication认证代替smb协定url携带用户名明码形式,防止特殊字符传递造成认证失败 public static void downloadFile(String ip, String shareFolder, String filePath, String localDir) throws Exception { System.setProperty("jcifs.smb.client.dfs.disabled", "true"); String url = getFileUrl(ip, shareFolder, filePath); SmbFile smbFile = new SmbFile(url); smbFile.connect(); FileUtil.initfloderPath(localDir); String localFilePath = localDir + "/" + smbFile.getName(); BufferedInputStream buf = new BufferedInputStream(new SmbFileInputStream(smbFile)); FileUtil.writeFile(localFilePath, FileUtil.convertStreamToByte(buf)); } public static void downloadFileByAuth(String ip, String shareFolder, String userName, String password, String filePath, String localDir) throws Exception { System.setProperty("jcifs.smb.client.dfs.disabled", "true"); String url = getFileUrl(ip, shareFolder, filePath); NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(ip, userName, password); SmbFile smbFile = new SmbFile(url, auth); smbFile.connect(); FileUtil.initfloderPath(localDir); String localFilePath = localDir + "/" + smbFile.getName(); BufferedInputStream buf = new BufferedInputStream(new SmbFileInputStream(smbFile)); FileUtil.writeFile(localFilePath, FileUtil.convertStreamToByte(buf)); } public static String getFileUrl(String ip, String shareFolder, String filePath) { return "smb://" + ip + "/" + shareFolder + "/" + filePath; }SMB2.0集成smbj类库,实用于windows server2012及以上操作系统,默认装置开启无需额定配置 ...

February 5, 2023 · 2 min · jiezi

关于springboot:springBootstarter思想及原理

starter作用springBoot starter基于约定大于配置思维,应用spi机制及主动拆卸原理,能够将一些通用的性能可能封装成一个独立组件并很不便的集成到不同的我的项目外面,简化开发,晋升代码复用能力。 springBoot在配置上相比spring要简略许多, 其外围在于starter的设计, 在应用springBoot来搭建一个我的项目时, 只须要引入官网提供的starter, 就能够间接应用, 免去了各种配置。starter简略来讲就是引入了一些相干依赖和一些初始化的配置。 约定大于配置:对于大部分罕用状况,极简短的配置即可应用对于少部分的略非凡状况,大量的配置即可应用对于极为非凡的定制化需要,能够通过各选项手动配置实现约定能够缩小很多配置,比如说在maven的构造中:/src/main/java目录用来寄存java源文件src/main/resources目录用来寄存资源文件,如application.yml文件/src/test/java目录用来寄存java测试文件/src/test/resources目录用来寄存测试资源文件/target目录为我的项目的输入地位springBoot的理念就是约定大于配置,这一点在各种starter外面尤其体现的酣畅淋漓。在springBoot中提供了一套默认配置,不须要手动去写xml配置文件,只有默认配置不能满足咱们的需要时,才会去批改配置。相比于晚期的spring须要编写各种xml配置文件,starter极大的缩小了各种简单的配置starter命名spring官网提供了很多starter,第三方也能够定义starter。为了加以辨别,starter从名称上进行了如下标准:spring官网starter通常命名为 spring-boot-starter-{name},如spring-boot-starter-webspring官网倡议非官方starter命名应遵循 {name}-spring-boot-starter的格局,例如由mybatis提供的mybatis-spring-boot-starterstarter原理mybatis-spring-boot-starter来阐明主动配置的实现过程依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot.version}</version> </dependency>主动配置类@Configuration注解的类能够看作是能生产让Spring IoC容器治理的Bean实例的工厂。@Configuration和@Bean这两个注解一起应用就能够创立一个基于java代码的配置类,能够用来代替传统的xml配置文件。@Bean注解的办法返回的对象能够被注册到spring容器中。 上面的MybatisAutoConfiguration这个类,主动帮咱们生成了SqlSessionFactory和SqlSessionTemplate这些Mybatis的重要实例并交给spring容器治理。@EnableConfigurationProperties(MybatisProperties.class)能够将mybatis的配置信息注入到MybatisProperties对应的bean实例外面。@ConditionalOnClass,@ConditionalOnBean代表主动拆卸的条件要实现Mybatis的主动配置,须要在类门路中存在SqlSessionFactory.class、SqlSessionFactoryBean.class这两个类,同时须要存在DataSource这个bean且这个bean实现主动注册。 @org.springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })@ConditionalOnBean(DataSource.class)@EnableConfigurationProperties(MybatisProperties.class)@AutoConfigureAfter(DataSourceAutoConfiguration.class)public class MybatisAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class); private final MybatisProperties properties; private final Interceptor[] interceptors; private final ResourceLoader resourceLoader; @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } ... if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); } if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); } if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } return factory.getObject(); }配置属性@ConfigurationProperties把yml或者properties配置文件中的配置参数信息封装到ConfigurationProperties注解标注的bean里,个别联合@EnableConfigurationProperties注解应用/** * Configuration properties for MyBatis. */@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)public class MybatisProperties { public static final String MYBATIS_PREFIX = "mybatis"; /** * Location of MyBatis xml config file. */ private String configLocation; /** * Locations of MyBatis mapper files. */ private String[] mapperLocations; /** * Packages to search type aliases. (Package delimiters are ",; \t\n") */ private String typeAliasesPackage; /** * Packages to search for type handlers. (Package delimiters are ",; \t\n") */ private String typeHandlersPackage; ... /** * A Configuration object for customize default settings. If {@link #configLocation} * is specified, this property is not used. */ @NestedConfigurationProperty private Configuration configuration; ... }starter里Bean的发现与注册META-INF目录下的spring.factories文件# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.mybatis.spring.boot.autoconfigure.MybatisAutoConfigurationspring boot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包含依赖包中的类,那么依赖包中的bean是如何被发现和加载的? ...

February 1, 2023 · 3 min · jiezi

关于springboot:12ThreadLocal的那点小秘密

好久不见,不晓得大家新年过得怎么样?有没有痛痛快快得放松?是不是还能收到很多压岁钱?好了,话不多说,咱们开始明天的主题:ThreadLocal。我收集了4个面试中呈现频率较高的对于ThreadLocal的问题: 什么是ThreadLocal?什么场景下应用ThreadLocal?ThreadLocal的底层是如何实现的?ThreadLocal在什么状况下会呈现内存透露?应用ThreadLocal要留神哪些内容? 咱们先从一个“流言”开始,通过剖析ThreadLocal的源码,尝试纠正“流言”带来的误会,并解答下面的问题。流传已久的“流言”很多文章都在说“ThreadLocal通过拷贝共享变量的形式解决并发平安问题”,例如: 这种说法并不精确,很容易让人误会为ThreadLocal会拷贝共享变量。来看个例子:private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new Thread(() -> { try { System.out.println(DATE_FORMAT.parse("2023-01-29")); } catch (ParseException e) { e.printStackTrace(); } }).start();}}复制代码咱们晓得,多线程并发拜访同一个DateFormat实例对象会产生重大的并发平安问题,那么退出ThreadLocal是不是能解决并发平安问题呢?批改下代码:/** 第一种写法 */private static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = new ThreadLocal<>() { @Overrideprotected DateFormat initialValue() { return DATE_FORMAT;}}; public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { new Thread(() -> { try { System.out.println(DATE_FORMAT_THREAD_LOCAL.get().parse("2023-01-29")); } catch (ParseException e) { e.printStackTrace(); } }).start();}}复制代码预计会有很多小伙伴会说:“你这么写不对!《阿里巴巴Java开发手册》中不是这么用的!”。把书中的用法搬过去:/** ...

February 1, 2023 · 2 min · jiezi

关于springboot:SpringBoot一个注解轻松实现-Redis-分布式锁

1.业务背景有些业务申请,属于耗时操作,须要加锁,避免后续的并发操作,同时对数据库的数据进行操作,须要防止对之前的业务造成影响。 2剖析流程应用 Redis 作为分布式锁,将锁的状态放到 Redis 对立保护,解决集群中单机 JVM 信息不互通的问题,规定操作程序,爱护用户的数据正确。 梳理设计流程新建注解 @interface,在注解里设定入参标记减少 AOP 切点,扫描特定注解建设 @Aspect 切面工作,注册 bean 和拦挡特定办法特定办法参数 ProceedingJoinPoint,对办法 pjp.proceed() 前后进行拦挡切点前进行加锁,工作执行后进行删除 key 外围步骤:加锁、解锁和续时加锁应用了 RedisTemplate 的 opsForValue.setIfAbsent 办法,判断是否有 key,设定一个随机数 UUID.random().toString,生成一个随机数作为 value。从 redis 中获取锁之后,对 key 设定 expire 生效工夫,到期后主动开释锁。依照这种设计,只有第一个胜利设定 Key 的申请,能力进行后续的数据操作,后续其它申请因为无奈取得资源,将会失败完结。 超时问题放心 pjp.proceed() 切点执行的办法太耗时,导致 Redis 中的 key 因为超时提前开释了。例如,线程 A 先获取锁,proceed 办法耗时,超过了锁超时工夫,到期开释了锁,这时另一个线程 B 胜利获取 Redis 锁,两个线程同时对同一批数据进行操作,导致数据不精确。 解决方案:减少一个「续时」工作不实现,锁不开释:保护了一个定时线程池 ScheduledExecutorService,每隔 2s 去扫描退出队列中的 Task,判断是否生效工夫是否快到了,公式为:【生效工夫】<= 【以后工夫】+【生效距离(三分之一超时)】 /** * 线程池,每个 JVM 应用一个线程去保护 keyAliveTime,定时执行 runnable */private static final ScheduledExecutorService SCHEDULER =new ScheduledThreadPoolExecutor(1,new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());static { SCHEDULER.scheduleAtFixedRate(() -> { // do something to extend time }, 0, 2, TimeUnit.SECONDS);}3设计方案通过下面的剖析,设计出了这个计划: ...

January 29, 2023 · 4 min · jiezi

关于springboot:SpringBoot实现登录拦截器实战版

对于管理系统或其余须要用户登录的零碎,登录验证都是必不可少的环节,在SpringBoot开发的我的项目中,通过实现拦截器来实现用户登录拦挡并验证。 Spring Boot实现登录拦挡原理SpringBoot通过实现HandlerInterceptor接口实现拦截器,通过实现WebMvcConfigurer接口实现一个配置类,在配置类中注入拦截器,最初再通过@Configuration注解注入配置。 实现HandlerInterceptor接口实现HandlerInterceptor接口须要实现3个办法:preHandle、postHandle、afterCompletion.3个办法各自的性能如下:package blog.interceptor; import blog.entity.User;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession; public class UserLoginInterceptor implements HandlerInterceptor { /*** * 在申请解决之前进行调用(Controller办法调用之前) */@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("执行了拦截器的preHandle办法"); try { HttpSession session = request.getSession(); //对立拦挡(查问以后session是否存在user)(这里user会在每次登录胜利后,写入session) User user = (User) session.getAttribute("user"); if (user != null) { return true; } response.sendRedirect(request.getContextPath() + "login"); } catch (Exception e) { e.printStackTrace(); } return false; //如果设置为false时,被申请时,拦截器执行到此处将不会持续操作 //如果设置为true时,申请将会继续执行前面的操作}/*** * 申请解决之后进行调用,然而在视图被渲染之前(Controller办法调用之后) */@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行了拦截器的postHandle办法");}/*** * 整个申请完结之后被调用,也就是在DispatchServlet渲染了对应的视图之后执行(次要用于进行资源清理工作) */@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("执行了拦截器的afterCompletion办法");}}preHandle在Controller之前执行,因而拦截器的性能次要就是在这个局部实现:查看session中是否有user对象存在;如果存在,就返回true,那么Controller就会持续前面的操作;如果不存在,就会重定向到登录界面。就是通过这个拦截器,使得Controller在执行之前,都执行一遍preHandle. ...

January 23, 2023 · 2 min · jiezi

关于springboot:对于SpringBoot中约定大于配置的理解

约定大于配置是一种开发准则,就是缩小人为的配置,间接用默认的配置就能取得咱们想要的后果。 SpringBoot的约定大于配置,对于我的了解是:比照SpringMVC,须要在web.xml外面配置前端控制器,还须要在外围配置文件(*-servlet.xml)中配置视图解析器等等,还要配置第三方的Tomcat服务器。而SpringBoot不须要配置这些,它内嵌了Tomcat服务器,咱们只须要在Maven配置文件(Pom.xml)外面导入SpringMVC所须要的依赖就能够。SpringBoot的劣势,是在传统中须要配置的中央,SpringBoot都进行了约定(已配置好),开发人员能配置得更少,更间接地开发我的项目,写业务逻辑代码。SpringBoot和maven的约定大于配置体现点: 一.maven的目录文件构造 默认有resources文件夹,寄存资源配置文件。src-main-resources,src-main-java默认的编译生成的类都在targetwen。默认有target文件夹,将生成class文件盒编程生成的jar寄存在target文件夹下 二.SpringBoot默认的配置文件必须是,也只能是application.命名的yml文件或者properties文件,且惟一 1. SpringBoot默认只会去src-main-resources文件夹上来找application配置文件

January 22, 2023 · 1 min · jiezi

关于springboot:记录gitlab事件逻辑修改

背景: 目标是把gitlab发送的申请合并。 https://segmentfault.com/a/11...上次说写合并事件的时候。应用了线程睡眠,不仅会占用tcp连贯的工夫,也会容易出很多问题。 同时发现了一些问题,起初就扭转了写法。 原来的写法: 流程: 合并issue close 和 comment事件的时候,起初又发现一个问题: 判断是否同一个issue的办法有谬误 之前是通过判断iid是否相等, 申请是否来自同一个申请 之后发现,issue的iid都是从1 开始的,所以A我的项目issue 的 iid, 可能与B我的项目issue 的 iid雷同 所以咱们须要辨别各个申请来自的我的项目 所以应用了ConcurrentHashMap,key为token,value为该我的项目待处理的GitlabRequest申请 /** * key: access_token, github钉钉机器人的token * value 该我的项目待处理的GitlabRequest申请 */ private final Map<String, Queue<GitlabRequest>> map = new ConcurrentHashMap<>();问题2: 线程睡眠上次。应用了线程睡眠,不仅会占用tcp连贯的工夫,也会容易出很多问题。 改为了应用@Schedule // 距离5s解决我的项目申请 @Scheduled(fixedRate = 5000) private void sendRequest() { // 对每个我的项目的申请队列进行解决 this.map.forEach((key, value) -> { try { this.handleQueue(value); } catch (IOException e) { throw new RuntimeException(e); } }); }问题3: 事件不能合并 ...

January 18, 2023 · 1 min · jiezi

关于springboot:记录java-在遍历中删除元素-以及-mysql56版本添加unique失败

遍历中删除List或Queue等数据结构中,如何一边遍历一遍删除? 1. 常犯错误ArrayList可能没遇到坑过的人会用加强for循环这么写: public static void main(String[] args) {1 List<Integer> list = new ArrayList<>();2 list.add(1);3 list.add(2);4 list.add(3);56 for (Integer value : list) {7 if (value.equals(2)) {8 list.remove(value);9 }10 }11 System.out.println(list);}然而一运行,后果却抛 java.util.ConcurrentModificationException 异样即并发批改异样 简略看看异样是怎么产生的: 首先,从抛出异样的地位动手,发现起因是因为: modCount 的值不等于 expectedModCount modCount值是什么呢? The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.从源码正文中能够看进去,示意的是 批改该表构造的次数, 包含批改列表大小等。 ...

January 18, 2023 · 2 min · jiezi

关于springboot:Go的ORM也太拉跨了吧赶紧给他封装一下

背景去年缓缓开始接触了Go语言,也在公司写了几个Go的生产我的项目。我是从Java转过来的。(其实也不算转,公司用啥,我用啥)在这个过程中,老是想用Java的思维写Go,在开始的一两个月,那是边写边吐槽。俊俏的错误处理,没有流式解决,还居然没有泛型,框架生态链不成熟,没有一家独大的相似Spring的框架。(其实当初写了快一年的Go,Go还是挺香的,哈哈)明天,我来聊一下,我在我在写Go过程中用的最多orm框架gorm。Java的orm写过Java的根本都晓得Mybatis,Mybatis-plus。在Mybatis-plus中操作单表十分不便,通过QueryWrapper,对于单表的操作十分的丝滑,没有任何的思维累赘。相似上面这样: QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.lambda().eq(User::getUsername,"zhangsan").eq(User::getAge,18); userMapper.selectList(queryWrapper);复制代码Go的orm这里的条件查问都须要开发手动来拼接字符串,如果我的项目比拟大的话,就会看到漫天飞的SQL字段,保护起来十分麻烦。 var users []*User sqlResult := db.Where("username = ? and age = ?", "zhangsan", 18).Find(&users)复制代码解决形式写了一段时间的Go之后,切实不想每次都写这些字符串拼接了,于是我给gorm封装了一个gorm-plus。这里我应用到了go 1.18的泛型。泛型出了这么久,也该应用上了。其实就是把Mybatis-plus的那套语法借鉴了一下。(好吧,就是抄他的)Mybatis-plus对于单表操作提供了十分多的CRUD操作。 我给gorm-plus 也提供了相似的操作 上面是具体用法下载:go get github.com/acmestack/gorm-plus复制代码初始化sqlDROP TABLE IF EXISTS users;CREATE TABLE users ( `id` int(0) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `age` int(0) NULL DEFAULT NULL, `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `score` int(0) NULL DEFAULT NULL, `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `dept` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `created_at` datetime(0) NULL DEFAULT NULL, `updated_at` datetime(0) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; ...

January 16, 2023 · 2 min · jiezi

关于springboot:通过Docker启动DB2并在Spring-Boot整合DB2

1 简介DB2是IBM的一款优良的关系型数据库,简略学习一下。 2 Docker装置DB2为了疾速启动,间接应用Docker来装置DB2。先下载镜像如下: docker pull ibmcom/db2:11.5.0.0启动数据库如下: docker run -itd \ --name mydb2 \ --privileged=true \ -p 50000:50000 \ -e LICENSE=accept \ -e DB2INST1_PASSWORD=pkslow \ -e DBNAME=testdb \ ibmcom/db2:11.5.0.0这样取得的数据库,具体信息如下: 连贯URL:jdbc:db2://localhost:50000/testdb 用户名:db2inst1 明码:pkslow 在IDEA上连贯如下: 默认的Schema为DB2INST1,测试SQL如下: 创立表: CREATE TABLE EMPLOYEE_SALARY(DEPTNO CHAR(3) NOT NULL, DEPTNAME VARCHAR(36) NOT NULL, EMPNO CHAR(6) NOT NULL, SALARY DECIMAL(9,2) NOT NULL WITH DEFAULT);插入数据: INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('001', 'IT', '001322', 80000.00);INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('001', 'IT', '001323', 20000.00);INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('002', 'Architecture', '001324', 3220.00);INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('002', 'Architecture', '001325', 8000.00);INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('003', 'Front Office', '001326', 13320.00);INSERT INTO EMPLOYEE_SALARY (DEPTNO, DEPTNAME, EMPNO, SALARY) VALUES ('001', 'IT', '001327', 5433.00);查问: ...

January 15, 2023 · 2 min · jiezi

关于springboot:springboot30快速搭建入门测试资料整理

环境筹备jdk17参考《JDK17装置教程及环境变量配置》 jdk17下载,或者先装置Scoop,应用 scoop install openjdk19; scoop install openjdk8-redhatIntelliJ工具下载:jetbrains-toolbox,装置 IntelliJ IDEA Ultimate官网文档中文官网入门文档[api文档]https://springdoc.org/v2/一、装置 Spring Boot[gradle]以下为win11环境 1. 装置Scoop应用中文官网入门文档中 3.2.6. Windows Scoop 装置 Set-ExecutionPolicy RemoteSigned -Scope CurrentUserirm get.scoop.sh -outfile 'install.ps1'.\install.ps1 -RunAsAdminhttps://github.com/ScoopInstaller/Install#for-admin官网教程admin用户。应用迷信上网装置,或增加hosts: 解决raw.githubusercontent.com无法访问的问题。 #或应用国内镜像下载:iwr https://gitee.com/glsnames/scoop-installer/raw/master/bin/install.ps1 | iex2. 初始化装置scoop install git# 官网提供scoop bucket add extrasscoop bucket add javascoop install gradlescoop install springboot# scoop install springboot@2.7.6spring --version参考《Scoop windows下的包管理器》换源:进步下载速度要改善 Scoop 的下载速度,具体能够参照 Scoop | Gitee 版 的阐明更换下载源。换源之后的Scoop,速度晋升不是一星半点儿。 更换 Scoop 源scoop config SCOOP_REPO 'https://gitee.com/glsnames/scoop-installer'scoop config SCOOP_REPO https://gitee.com/squallliu/scoopscoop updategit -C "${Env:USERPROFILE}\scoop\buckets\main" remote set-url origin https://hub.fastgit.org/ScoopInstaller/Main.gitgit -C "${Env:USERPROFILE}\scoop\buckets\extras" remote set-url origin https://hub.fastgit.org/lukesampson/scoop-extras.git注gradle与Java 兼容性阐明3. 建设空我的项目新建我的项目 ...

January 13, 2023 · 2 min · jiezi

关于springboot:记录本周问题

我的项目中应用hashMap我的项目里两个中央都用到了hashmap。然而感觉本人用的时候并没有感觉十分的清晰。同时发现hashmap有线程不平安问题,而本人用的时候就是多线程来应用。于是在这里介绍一下。 我的项目中两个中央用到了hashmap。 1.策略模式一个是应用hashmap来贮存service, 依据不同的事件调用不同的service。 在构造函数中,把service增加到map中, 而后应用的时候,依据eventName获取相应的service进行解决。 @Service1 public class GitLabNotifyServiceImpl implements GitLabNotifyService {2 private Map<String, EventService> map = new HashMap<>();3 public GitLabNotifyServiceImpl(PushEventService pushEventService,4 IssueEventService issueEventService,5 CommentEventService) {6 this.addService(issueEventService);7 this.addService(pushEventService);); } @Override8 public void handleEventData(String json, String eventName,String access_token) throws IOException {9 EventService eventService = this.map.get(eventName); }2.合并事件开发背景: gitlab中有评论并敞开的按钮 点击后钉钉机器人会发送两条告诉 于是就有需要是合并事件。 测试发现,总是评论事件优于issue事件发送。 实现逻辑而后就写了个办法。 因为spring boot的bean默认都是单例的,所以定义了一个hashmap, 记录以后要执行的gitlab的事件 /** * key: access_token, github钉钉机器人的token * value GitlabEvent, gitlab事件 */ private static final HashMap<String, String> hashMap = new HashMap<>();两个事件发送后,会有comment申请和issue close申请短距离被服务器承受。 ...

January 11, 2023 · 2 min · jiezi

关于springboot:更简洁的参数校验使用-SpringBoot-Validation-对参数进行校验

在开发接口时,如果要对参数进行校验,你会怎么写?编写 if-else 吗?尽管也能达到成果,然而不够优雅。明天,举荐一种更简洁的写法,应用 SpringBoot Validation 对办法参数进行校验,特地是在编写 Controller 层的办法时,间接应用一个注解即可实现参数校验。示例代码:spring-validation-demo: SpringBootValidation Demo (gitee.com)引入依赖想要实现上述所说的参数校验,咱们须要一个外围依赖:spring-boot-starter-validation,此外,为了不便演示,还须要其余依赖。依赖如下: <dependencies>     <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>     <dependency>       <groupId>org.projectlombok</groupId>       <artifactId>lombok</artifactId>       <optional>true</optional>     </dependency>   </dependencies>复制代码 以下局部不是核心内容:你在编写上面的示例代码中,会发现次要应用到了javax.validation.constraints 包下的注解,而这个包次要来自于 jakarta.validation-api 这个依赖。如果引入依赖的时候间接引入 jakarta.validation-api 是无奈实现参数校验性能的,因为它只定义了标准,而没有具体实现。然而 hibernate-validator 实现了这个标准,间接引入 hibernate-validator 也是能够实现参数校验性能的。 <!--无奈实现性能--> <dependency>     <groupId>jakarta.validation</groupId>     <artifactId>jakarta.validation-api</artifactId> </dependency> <!--能够实现参数校验性能--> <!--spring-boot-starter-validation 引入了这个依赖--> <dependency>     <groupId>org.hibernate.validator</groupId>     <artifactId>hibernate-validator</artifactId> </dependency>复制代码 相干注解阐明这里列举出一些次要的注解,这些注解次要来自于包 javax.validation.constraints,有趣味查看源码的能够去这个包下查看。能够先跳过这部分内容,上面的代码如果遇到不分明作用的注解再回来查阅。✈ 空值查看 注解阐明@NotBlank用于字符串,字符串不能为null 也不能为空字符串@NotEmpty字符串同上,对于汇合(Map,List,Set)不能为空,必须有元素@NotNull不能为 null@Null必须为 null✈ 数值查看 ...

January 9, 2023 · 4 min · jiezi

关于springboot:为什么-java-容器推荐使用-ExitOnOutOfMemoryError

前言好久没写文章了, 明天之所以忽然灵机一动, 是因为昨天呈现了这样一个状况:咱们公司的某个手机APP后端的用户(customer)微服务呈现内存泄露, 导致OutOfMemoryError, 然而因为通过咱们精心优化的openjdk容器参数, 这次故障对用户齐全无感知. 那么咱们是如何做到的呢?HeapDumpOnOutOfMemoryError VS ExitOnOutOfMemoryError咱们都晓得, 在传统的虚拟机上部署的Java实例. 为了更好地剖析问题, 个别都是要加上: -XX:+HeapDumpOnOutOfMemoryError这个参数的. 加这个参数后, 如果遇到内存溢出, 就会主动生成HeapDump, 前面咱们能够拿到这个HeapDump来更准确地剖析问题.然而, "小孩儿, 时代变了!"容器技术的倒退, 给传统运维模式带来了微小的挑战, 这个挑战是革命性的: 传统的利用都是"永恒存在的" vs 容器pod是"短暂长期的存在"传统利用扩缩容绝对艰难 vs 容器扩缩容丝般顺滑传统利用运维模式关注点是:"定位问题" vs 容器运维模式是: "疾速复原"传统利用一个实例报HeapDumpError就会少一个 vs 容器HeapDump shutdown后能够主动启动, 已达到指定正本数... 简略总结一下, 在应用容器平台后, 咱们的工作偏向于: 遇到故障疾速失败遇到故障疾速复原尽量做到用户对故障"无感知" 所以, 针对Java利用容器, 咱们也要优化以满足这种需要, 以OutOfMemoryError故障为例: 遇到故障疾速失败, 即尽可能"疾速退出, 疾速终结"有问题java利用容器实例退出后, 新的实例迅速启动填补;"疾速退出, 疾速终结", 同时配合LB, 退出和冷启动的过程中用户申请不会散发进来. -XX:+ExitOnOutOfMemoryError就正好满足这种需要:传递此参数时,抛出OutOfMemoryError时JVM将立刻退出。 如果您想终止应用程序,则能够传递此参数。细节让咱们从新回顾故障: "咱们公司的某个手机APP后端的用户(customer)微服务呈现内存泄露, 导致OutOfMemoryError"该customer利用概述如下: 无状态通过Deployment部署, 有6个正本通过SVC提供服务 残缺的过程如下: 6个正本, 其中1个呈现OutOfMomoryError因为正本的jvm参数配置有: -XX:+ExitOnOutOfMemoryError, 该实例的JVM(PID为1)立刻退出.因为pid 1过程退出, 此时pod立即出于Terminating状态, 并且变为:Terminated同时, customer的SVC 负载平衡会将该正本从SVC 负载平衡中移除, 用户申请不会被散发到该节点.K8S检测到正本数和Deployment replicas不统一, 启动1个新的正本.待新的局部Readiness Probe 探测通过, customer的SVC负载平衡将这个新的正本退出到负载平衡中, 接管用户申请. ...

January 9, 2023 · 1 min · jiezi

关于springboot:springboot-实现邮箱找回密码使用到redis-stmp

背景小组我的项目要做一个邮箱找回明码的性能。 须要用到redis 大略流程如下: 第一步,用户填写邮箱,并点击“获取验证码”,浏览器发送申请,调用获取验证码接口。而后,服务端依据邮箱,生成验证码,发送验证码给这个邮箱,并将验证码和邮箱和有效期放到redis/内存中。用户在邮箱中查到验证码后→填写到登录界面→点击登录→前端申请登录接口,携带邮箱和验证码参数。服务器收到申请后,到redis中取到这个邮箱对应的信息→校验验证码是否正确,并验证是否过期,如果验证码正确且没有过期,则失常登录。Redis先介绍一下redis Redis 是齐全开源收费的,,是一个高性能的key-value非关系性数据库(NoSql)。 Redis 与其余 key - value 缓存产品有以下三个特点: Redis反对数据的长久化,能够将内存中的数据保留在磁盘中,重启的时候能够再次加载进行应用。Redis不仅仅反对简略的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。Redis能够存储键与5种不同数据结构类型之间的映射,这5种数据结构类型别离为String(字符串)、List(列表)、Set(汇合)、Hash(散列)和 Zset(有序汇合)。 下载Redis间接拜访github网址:https://github.com/MSOpenTech...,如下图所示: 我下载的是windows版本 启动Redisredis.windows.conf为redis配置文件,相干参数能够在这里配置,如:端口等,我这里应用默认参数,暂不批改,默认端口为6379。双击redis-server.exe启动,则呈现如下图所示,则启动胜利。 SpringBoot中应用Redis在我的项目的pom.xml中增加如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId></dependency>application.yml 中配置如下 # redis 配置 redis: # 地址 host: localhost # 端口,默认为6379 port: 6379 # 数据库索引 database: 0 # 明码 password: # 连贯超时工夫 timeout: 10s lettuce: pool: # 连接池中的最小闲暇连贯 min-idle: 0 # 连接池中的最大闲暇连贯 max-idle: 8 # 连接池的最大数据库连接数 max-active: 8 # #连接池最大阻塞等待时间(应用负值示意没有限度) max-wait: -1ms编写Redis操作工具类咱们对redis操作须要用到RedisTemplate RedisTemplate 是简化 Redis 数据库拜访代码的助手类,也是 Spring Data Redis 对 Redis 反对的核心类。 ...

January 5, 2023 · 3 min · jiezi

关于springboot:Java版管程Synchronized

一、同步机制保障共享资源的读写平安,须要一种同步机制:用于解决2方面问题: 线程间通信:线程间替换信息的机制线程间同步:管制不同线程之间操作产生绝对程序的机制 二、同步机制-管程2.1 意识管程同步机制中有经典的管程计划,对于管程在在中国大学mooc中搜寻 管程 有些大学的操作系统课程会解说管程。管程其实就是对共享变量以及其操作的封装: 将共享资源封装起来,对外提供操作这些共享资源的办法。线程只能通过调用管程中的办法来间接地拜访管程中的共享资源 2.2 管程如何解决同步和通信问题: 同步问题: 管程是互斥进入,管程提供了入口期待队列:存储期待进入同步代码块的线程管程的互斥性是由编译器负责保障的。 通信问题:管程中设置条件变量,期待/唤醒操作,以解决同步问题。 条件变量(java里了解为锁对象本身)期待操作:能够让过程、线程在条件变量上期待(此时,应先开释管程的使用权,不然别其它线程、过程拿不到使用权);将线程存储到条件变量的期待队列中。发信号操作:也能够通过发送信号将期待在条件变量上的过程、线程唤醒(将期待队列中的线程唤醒) 2.3 要害数据结构和办法: 线程队列: 入口期待队列:存储期待进入同步代码块的线程;线程进入管程后,能够执行同步块代码。java中的_EntryList条件期待队列:入口期待队列中的线程,进入管程后,执行同步块代码的过程中,须要期待某个条件满足之后,能力继续执行,就将线程放入此变量的期待队列中。java是面向对象的设计,这里的条件变量即锁对象本身(线程都在期待领有这个锁),所以只有一个条件变量期待队列即_WaitSet。 同步办法: wait() :期待条件变量,将线程放入条件变量的期待队列中。notify():激活某个条件变量上期待队列中的一个线程notifyAll():激活某个条件变量上期待队列中的所有线程 三、java版的管程 synchronizedsynchronized 是语法糖,会被编译器编译成:1个monitorenter 和 2个moitorexit(一个用于失常退出,一个用于异样退出)。monitorenter 和 失常退出的monitorexit两头是synchronized包裹的代码,如下图: 在HotSpot虚拟机中,monitor是由ObjectMonitor实现的,ObjectMonitor次要数据结构如下: _count:记录owner线程获取锁的次数,即重入次数,也即是可重入的。_owner:指向领有该对象的线程_EntryList:管程的入口期待队列,即寄存期待锁而被block的线程。_WaitSet:管程的条件变量期待队列,寄存领有锁后,调用了wait()办法的线程; 进入EntryList的线程须要与其余线程争抢锁,抢到锁之后以排它形式执行同步代码块的代码,当一个线程被notify后,将从_WaitSet中挪动到EntryList中。 四、应用锁4.1 对实例对象加锁 同步实例办法 public synchronized void fun(){}复制代码 同步代码块 参数是实例 public void fun(){ synchronized(this){ ...}}复制代码4.2 对类加锁 同步静态方法 class Aclass{ static synchronized void fun(){}}复制代码 同步代码块 参数是类 class Aclass{ static void fun(){ synchronized (Aclass.class){ }}}复制代码4.3 对象的内存构造HotSpot虚拟机中,对象在内存中存储的布局能够分为三块区域:对象头 (Header)、实例数据(Instance Data)和对齐填充(Padding)。其中对象头中的Mark Word 区域中会存储 对象锁,锁状态标记,偏差 锁(线程)ID,偏差工夫,数组长度(数组对象)等,Mark Word被设计成一个非固定的数据结构以便在极小的空间内 存存储尽量多的数据,它会依据对象的状态复用本人的存储空间,也就是说, Mark Word会随着程序的运行发生变化,32位虚拟机中变动状态如下: ...

January 3, 2023 · 1 min · jiezi

关于springboot:Apache-ShardingSphere在转转亿级交易系统落地实践

一、背景与挑战这几年随着转转二手业务的疾速倒退,订单零碎的根底性能问题也愈发重大,作为零碎运行的基石,订单库压力不容小觑。面临的问题: 大促期间DB压力大,单库查问qps上万占用大量数据库资源,写性能大大降低;数据与日剧增,单库中蕴含十分多数据量过数亿的大表,占用空间靠近服务器的容量下限;数据量太大,数据备份和复原须要消耗很长时间,极其状况下失落数据的危险越高。 二、为什么选ShardingSphere迫于上述所说的DB压力问题,起初咱们做了一些缓解措施,其中包含: 优化大事务,放大事务范畴,甚至打消事务 通过调整原有业务逻辑程序将外围的生单步骤搁置在最初 ,仅在订单主表放弃事务,主表操作异样时其余订单相干的表容许有脏数据产生。 订单数据增加缓存 缓存这块最重要同时最麻烦的中央是保证数据的一致性,订单信息波及结算抽佣等,数据的不实时或不统一会造成严重事故。严格保障缓存数据的一致性,代码实现比较复杂同时会升高零碎的并发,因而缓存计划实现这块咱们做了肯定的斗争: 容许数据缓存失败状况下申请间接查库;给缓存key增加版本号,通过读最新版本号的数据确保数据的实时性。 简单查问走ES、主从拆散、一些大表进行冷热数据拆散等 通过上述几个方面的优化DB压力虽有所降落,但面对大促等一些高并发场景时仍显得力不从心。为了从根本上解决订单库的性能问题,咱们决定对订单库进行数据分片(分库分表)革新,使得将来3-5年内不须要放心订单容量问题。 数据分片组件选型这块,咱们从效率、稳定性、学习老本和工夫多个方面比照,最终抉择了ShardingSphere。 ShardingSphere劣势如下:提供标准化的数据分片、分布式事务和数据库治理性能,可实用于如Java同构、异构语言、容器、云原生等各种多样化的利用场景;分片策略灵便,反对多种分片形式;集成不便、业务侵入水平低;文档丰盛、社区沉闷。ShardingShpere提出DB Plus的理念,采纳可插拔的架构设计,模块互相独立,能够独自应用,亦能够灵便组合应用。目前ShardingSphere 由 JDBC、Proxy 和 Sidecar(布局中)这 3 款既可能独立部署,又反对混合部署配合应用的产品组成。3款产品个性比照如下: 通过上图比照,联合订单高并发个性,本次数据分片中间件抉择了ShardingSphere-JDBC。 ShardingSphere-JDBC定位为轻量级 Java 框架,在JDBC层提供的额定服务。它应用客户端直连数据库,以 jar 包模式提供服务,无需额定部署和依赖,可了解为增强版的 JDBC 驱动,齐全兼容 JDBC 和各种 ORM 框架。 随着5.x版本的公布,ShardingSphere还提供了许多新个性: 全新 distsql 用于加载及展现 shardingsphere配置信息反对跨不同数据库实例的分片 join sql 查问减少数据网关能力,反对异构数据库存储反对在线动态创建及批改用户权限新增自动化探针模块 三、我的项目落地中的关键点 分片key的抉择 以后订单id的生成,采纳了工夫戳+用户标识码+机器码+递增序列的形式,其中用户标识码取自买家id的第9~16位,该码是用户id生成时的真随机位,很适宜作为分片key。 抉择该用户标识码作为分片key有如下劣势: 能够使分到各个库表的数据尽可能平均;无论是以订单id、还是以买家id作为查问条件,都能够疾速定位到具体分片地位;同一买家的数据能散布到雷同的库表,不便买家维度的聚合查问。 具体分片策略上:咱们采纳了16库16表,用户标识码取模分库,用户标识码高四位取模分表。 新老库数据迁徙 迁徙必须是在线的,不思考停机迁徙,迁徙的过程中还会有数据的写入;数据应该是残缺的,C端体验是无感知的,迁徙之后须要保障新库和旧库的数据是严格统一的;迁徙过程中须要做到能够回滚,一旦迁徙过程中呈现问题,能够立刻回滚到源库,不会对系统可用性造成影响。 数据迁徙步骤如下:双写->迁徙历史数据->校验->老库下线。四、成果&收益 解决了单库容量下限问题;数据分片后单库表的数据量大大减少,单表数据量由原来的近亿降为百万级别,总体性能大大提高;升高了因单库、单表数据过大极其状况数据失落危险,加重运维压力。以下是两次大促期间,下单服务接口调用量与接口耗时比照。 革新前: 革新后: 五、总结感悟任何公司的“分库分表我的项目”说白了,都不是一个考验点子思路的常见我的项目,更多的反而是对一个人、一个团队干活的粗疏水平、上下游部门的沟通合作、工程化的操作施行以及抗压能力的综合考验。 同时业务的一直倒退,又促使零碎数据架构须要跟着一直降级,ShardingSphere以其良好的架构设计、高度灵便、可插拔和可扩大的能力简化了数据分片的开发难度,使研发团队只需关注业务自身,进而实现了数据架构的灵便扩大。附,最终技术计划目录: ...

January 3, 2023 · 1 min · jiezi

关于springboot:干掉-重复代码-的技巧有哪些

软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜爱写反复代码而软件工程师会利用各种技巧去干掉反复的冗余代码。业务同学埋怨业务开发没有技术含量,用不到设计模式、Java 高级个性、OOP,平时写代码都在堆 CRUD,个人成长无从谈起。其实,我认为不是这样的。设计模式、OOP 是前辈们在大型项目中积攒下来的教训,通过这些方法论来改善大型项目的可维护性。反射、注解、泛型等高级个性在框架中大量应用的起因是,框架往往须要以同一套算法来应答不同的数据结构,而这些个性能够帮忙缩小反复代码,晋升我的项目可维护性。在我看来,可维护性是大型项目成熟度的一个重要指标,而晋升可维护性十分重要的一个伎俩就是缩小代码反复。那为什么这样说呢? 如果多处反复代码实现完全相同的性能,很容易批改一处遗记批改另一处,造成 Bug有一些代码并不是齐全反复,而是类似度很高,批改这些相似的代码容易改(复制粘贴)错,把本来有区别的中央改为了一样。 明天,我就从业务代码中最常见的三个需要开展,聊聊如何应用 Java 中的一些高级个性、设计模式,以及一些工具打消反复代码,能力既优雅又高端。通过明天的学习,也心愿扭转你对业务代码没有技术含量的认识。 利用工厂模式 + 模板办法模式,打消 if…else 和反复代码假如要开发一个购物车下单的性能,针对不同用户进行不同解决:普通用户须要收取运费,运费是商品价格的 10%,无商品折扣;VIP 用户同样须要收取商品价格 10% 的快递费,但购买两件以上雷同商品时,第三件开始享受肯定折扣;外部用户能够免运费,无商品折扣。 咱们的指标是实现三种类型的购物车业务逻辑,把入参 Map 对象(Key 是商品 ID,Value 是商品数量),转换为出参购物车类型 Cart。先实现针对普通用户的购物车解决逻辑://购物车@Datapublic class Cart {   //商品清单   private List<Item> items = new ArrayList<>();   //总优惠   private BigDecimal totalDiscount;   //商品总价   private BigDecimal totalItemPrice;   //总运费   private BigDecimal totalDeliveryPrice;   //应酬总价   private BigDecimal payPrice;}//购物车中的商品@Datapublic class Item {   //商品ID   private long id;   //商品数量   private int quantity;   //商品单价   private BigDecimal price;   //商品优惠   private BigDecimal couponPrice;   //商品运费   private BigDecimal deliveryPrice;}//普通用户购物车解决public class NormalUserCart {   public Cart process(long userId, Map<Long, Integer> items) {       Cart cart = new Cart();       //把Map的购物车转换为Item列表       List<Item> itemList = new ArrayList<>();       items.entrySet().stream().forEach(entry -> {           Item item = new Item();           item.setId(entry.getKey());           item.setPrice(Db.getItemPrice(entry.getKey()));           item.setQuantity(entry.getValue());           itemList.add(item);       });       cart.setItems(itemList);       //解决运费和商品优惠       itemList.stream().forEach(item -> {           //运费为商品总价的10%           item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1")));           //无优惠           item.setCouponPrice(BigDecimal.ZERO);       });       //计算商品总价       cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add));       //计算运费总价       cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add));       //计算总优惠       cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add));       //应酬总价=商品总价+运费总价-总优惠       cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));       return cart;   }}复制代码而后实现针对 VIP 用户的购物车逻辑。与普通用户购物车逻辑的不同在于,VIP 用户能享受同类商品多买的折扣。所以,这部分代码只须要额定解决多买折扣局部:public class VipUserCart {   public Cart process(long userId, Map<Long, Integer> items) {       ...       itemList.stream().forEach(item -> {           //运费为商品总价的10%           item.setDeliveryPrice(item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())).multiply(new BigDecimal("0.1")));           //购买两件以上雷同商品,第三件开始享受肯定折扣           if (item.getQuantity() > 2) {               item.setCouponPrice(item.getPrice()                       .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100")))                       .multiply(BigDecimal.valueOf(item.getQuantity() - 2)));           } else {               item.setCouponPrice(BigDecimal.ZERO);           }       });       ...       return cart;   }}复制代码最初是免运费、无折扣的外部用户,同样只是解决商品折扣和运费时的逻辑差别:public class InternalUserCart {   public Cart process(long userId, Map<Long, Integer> items) {       ...       itemList.stream().forEach(item -> {           //免运费           item.setDeliveryPrice(BigDecimal.ZERO);           //无优惠           item.setCouponPrice(BigDecimal.ZERO);       });       ...       return cart;   }}复制代码比照一下代码量能够发现,三种购物车 70% 的代码是反复的。起因很简略,尽管不同类型用户计算运费和优惠的形式不同,但整个购物车的初始化、统计总价、总运费、总优惠和领取价格的逻辑都是一样的。正如咱们开始时提到的,代码反复自身不可怕,可怕的是漏改或改错。比方,写 VIP 用户购物车的同学发现商品总价计算有 Bug,不应该是把所有 Item 的 price 加在一起,而是应该把所有 Item 的 price*quantity 加在一起。这时,他可能会只批改 VIP 用户购物车的代码,而疏忽了普通用户、外部用户的购物车中,反复的逻辑实现也有雷同的 Bug。有了三个购物车后,咱们就须要依据不同的用户类型应用不同的购物车了。如下代码所示,应用三个 if 实现不同类型用户调用不同购物车的 process 办法:@GetMapping("wrong")public Cart wrong(@RequestParam("userId") int userId) {   //依据用户ID取得用户类型   String userCategory = Db.getUserCategory(userId);   //普通用户解决逻辑   if (userCategory.equals("Normal")) {       NormalUserCart normalUserCart = new NormalUserCart();       return normalUserCart.process(userId, items);   }   //VIP用户解决逻辑   if (userCategory.equals("Vip")) {       VipUserCart vipUserCart = new VipUserCart();       return vipUserCart.process(userId, items);   }   //外部用户解决逻辑   if (userCategory.equals("Internal")) {       InternalUserCart internalUserCart = new InternalUserCart();       return internalUserCart.process(userId, items);   }   return null;}复制代码电商的营销玩法是多样的,当前势必还会有更多用户类型,须要更多的购物车。咱们就只能一直减少更多的购物车类,一遍一遍地写反复的购物车逻辑、写更多的 if 逻辑吗?当然不是,雷同的代码应该只在一处呈现!如果咱们熟记抽象类和形象办法的定义的话,这时或者就会想到,是否能够把反复的逻辑定义在抽象类中,三个购物车只有别离实现不同的那份逻辑呢?其实,这个模式就是模板办法模式。咱们在父类中实现了购物车解决的流程模板,而后把须要非凡解决的中央留空白也就是留形象办法定义,让子类去实现其中的逻辑。因为父类的逻辑不残缺无奈独自工作,因而须要定义为抽象类。如下代码所示,AbstractCart 抽象类实现了购物车通用的逻辑,额定定义了两个形象办法让子类去实现。其中,processCouponPrice 办法用于计算商品折扣,processDeliveryPrice 办法用于计算运费。public abstract class AbstractCart {   //解决购物车的大量反复逻辑在父类实现   public Cart process(long userId, Map<Long, Integer> items) {       Cart cart = new Cart();       List<Item> itemList = new ArrayList<>();       items.entrySet().stream().forEach(entry -> {           Item item = new Item();           item.setId(entry.getKey());           item.setPrice(Db.getItemPrice(entry.getKey()));           item.setQuantity(entry.getValue());           itemList.add(item);       });       cart.setItems(itemList);       //让子类解决每一个商品的优惠       itemList.stream().forEach(item -> {           processCouponPrice(userId, item);           processDeliveryPrice(userId, item);       });       //计算商品总价       cart.setTotalItemPrice(cart.getItems().stream().map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))).reduce(BigDecimal.ZERO, BigDecimal::add));       //计算总运费       cart.setTotalDeliveryPrice(cart.getItems().stream().map(Item::getDeliveryPrice).reduce(BigDecimal.ZERO, BigDecimal::add));       //计算总折扣       cart.setTotalDiscount(cart.getItems().stream().map(Item::getCouponPrice).reduce(BigDecimal.ZERO, BigDecimal::add));       //计算应酬价格       cart.setPayPrice(cart.getTotalItemPrice().add(cart.getTotalDeliveryPrice()).subtract(cart.getTotalDiscount()));       return cart;   }   //解决商品优惠的逻辑留给子类实现   protected abstract void processCouponPrice(long userId, Item item);   //解决配送费的逻辑留给子类实现   protected abstract void processDeliveryPrice(long userId, Item item);}复制代码有了这个抽象类,三个子类的实现就非常简单了。普通用户的购物车 NormalUserCart,实现的是 0 优惠和 10% 运费的逻辑:@Service(value = "NormalUserCart")public class NormalUserCart extends AbstractCart {   @Override   protected void processCouponPrice(long userId, Item item) {       item.setCouponPrice(BigDecimal.ZERO);   }   @Override   protected void processDeliveryPrice(long userId, Item item) {       item.setDeliveryPrice(item.getPrice()               .multiply(BigDecimal.valueOf(item.getQuantity()))               .multiply(new BigDecimal("0.1")));   }}复制代码VIP 用户的购物车 VipUserCart,间接继承了 NormalUserCart,只须要批改多买优惠策略:@Service(value = "VipUserCart")public class VipUserCart extends NormalUserCart {   @Override   protected void processCouponPrice(long userId, Item item) {       if (item.getQuantity() > 2) {           item.setCouponPrice(item.getPrice()                   .multiply(BigDecimal.valueOf(100 - Db.getUserCouponPercent(userId)).divide(new BigDecimal("100")))                   .multiply(BigDecimal.valueOf(item.getQuantity() - 2)));       } else {           item.setCouponPrice(BigDecimal.ZERO);       }   }}复制代码外部用户购物车 InternalUserCart 是最简略的,间接设置 0 运费和 0 折扣即可:@Service(value = "InternalUserCart")public class InternalUserCart extends AbstractCart {   @Override   protected void processCouponPrice(long userId, Item item) {       item.setCouponPrice(BigDecimal.ZERO);   }   @Override   protected void processDeliveryPrice(long userId, Item item) {       item.setDeliveryPrice(BigDecimal.ZERO);   }} ...

January 3, 2023 · 5 min · jiezi

关于springboot:手把手带你开发starter点对点带你讲解原理

京东物流 孔祥东 _____ _ ____ _ / ____| (_) | _ \ | | | (___ _ __ _ __ _ _ __ __ _| |_) | ___ ___ | |_ ___ | '_ | '__| | '_ \ / _` | _ < / _ \ / _ | __| ____) | |_) | | | | | | | (_| | |_) | (_) | (_) | |_ |_____/| .__/|_| |_|_| |_|__, |____/ ___/ ___/ __| | | __/ | |_| |___/ 1. 为什么要用Starter?当初咱们就来回顾一下,在还没有Spring-boot框架的时候,咱们应用Spring 开发我的项目,如果须要某一个框架,例如mybatis,咱们的步骤个别都是:<!----> ...

January 3, 2023 · 3 min · jiezi

关于springboot:SpringBoot动态更新yml文件

前言在零碎运行过程中,可能因为一些配置项的简略变动须要从新打包启停我的项目,这对于在运行中的我的项目会造成数据失落,客户操作无响应等状况产生,针对这类状况对开发框架进行降级提供yml文件实时批改更新性能 我的项目依赖我的项目基于的是2.0.0.RELEASE版本,所以snakeyaml须要独自引入,高版本已蕴含在内 <dependency> <groupId>org.yaml</groupId> <artifactId>snakeyaml</artifactId> <version>1.23</version> </dependency>网上大多数办法是引入spring-cloud-context配置组件调用ContextRefresher的refresh办法达到同样的成果,思考以下两点未应用 开发框架应用了logback日志,引入spring-cloud-context会造成日志配置读取谬误引入spring-cloud-context会同时引入spring-boot-starter-actuator组件,会凋谢一些健康检查路由及端口,须要对框架平安方面进行额定管制YML文件内容获取读取resource文件下的文件须要应用ClassPathResource获取InputStream public String getTotalYamlFileContent() throws Exception { String fileName = "application.yml"; return getYamlFileContent(fileName); } public String getYamlFileContent(String fileName) throws Exception { ClassPathResource classPathResource = new ClassPathResource(fileName); return onvertStreamToString(classPathResource.getInputStream()); } public static String convertStreamToString(InputStream inputStream) throws Exception{ return IOUtils.toString(inputStream, "utf-8"); }YML文件内容更新咱们获取到yml文件内容后可视化显示到前台进行展现批改,将批改后的内容通过yaml.load办法转换成Map构造,再应用yaml.dumpAsMap转换为流写入到文件 public void updateTotalYamlFileContent(String content) throws Exception { String fileName = "application.yml"; updateYamlFileContent(fileName, content); } public void updateYamlFileContent(String fileName, String content) throws Exception { Yaml template = new Yaml(); Map<String, Object> yamlMap = template.load(content); ClassPathResource classPathResource = new ClassPathResource(fileName); Yaml yaml = new Yaml(); //字符输入 FileWriter fileWriter = new FileWriter(classPathResource.getFile()); //用yaml办法把map构造格式化为yaml文件构造 fileWriter.write(yaml.dumpAsMap(yamlMap)); //刷新 fileWriter.flush(); //敞开流 fileWriter.close(); }YML属性刷新yml属性在程序中读取应用个别有三种 ...

December 31, 2022 · 4 min · jiezi

关于springboot:如何优雅地记录操作日志

日志对于一个零碎来说不可或缺,对于问题的排查,问题的再现有着至关重要的作用。因为不想从日志文件一行行摸索,想谋求简略、快捷、不便;只需依据条件就能够检索到相应接口日志,以及是否呈现报错的状况。同时我不想用在业务代码中记录业务日志的形式。那还有什么办法呢?思考片刻,想到了用 AOP 的形式去记录接口日志。于是秉着一劳永逸的思维。我打算将我的项目封装成 starter,在须要用到的中央,间接引入即可失效。无需做更多简单、繁琐、反复的工作。 具体做法分三步走定义日志级别枚举,因为不同日志级别,代表着接口办法不同的日志记录形式。包含:NONE 不记录日志,INFO 记录失常日志,ERROR 记录异样日志, ALL 记录全副日志行为。定义 AOP 通过 @Logger 注解实现须要加强的切入点,应用 @Around 盘绕加强实现接口办法 入参、出参等信息的填补。应用 @PostConstruct 实现 日志表构造 的初始化,使得只须要引入依赖即可主动实现日志的生成与记录。详细描述因为在1点和 3点绝对简略,接下来就不再赘述,以下次要针对 2 点 进行详细描述。 因为波及到注解实现切点,于是我兼容了注解在 类 上 和 办法 上都能够实现操作,留神,此处办法上的注解优先级更高些,这也使得一个类下能够排除某些办法,使得使用更加灵便多变。初始化日志实体数据,通过 ProceedingJoinPoint 参数以及 Hutool 工具类实现相干信息的提取。通过开启新线程的形式实现日志的长久化,因为不想影响到主业务逻辑的执行。@Around("@within(com.cpz.log.annotation.Logger) || @annotation(com.cpz.log.annotation.Logger)")public Object doAround(ProceedingJoinPoint point) throws Throwable应用成果通过 maven 将我的项目打包,并将依赖装置到本地仓库,不便其它我的项目应用,同时为了不便这一流程,实现了将 jar 打包为本地依赖的脚本文件,install.batmvn install:install-file -Dfile=../spring-boot-starter-logger/target/spring-boot-starter-logger-1.0.jar -DgroupId=com.cpz -DartifactId=spring-boot-starter-logger -Dversion=1.0 -Dpackaging=jar2) 引入依赖 <dependency> <groupId>com.cpz</groupId> <artifactId>spring-boot-starter-logger</artifactId> <version>1.0</version></dependency>3) 开启记录接口日志 @Logger(level = LogLevel.ALL)@GetMapping("/test/query")public List<User> query(User user) { return userService.list();}调用接口查看数据库日志表信息(1, '/test/query', 'com.cpz.mybatisplus.controller.LoggerController', 'query', '192.168.42.104', '169.254.180.230', 1, '{\"Accept\":\"*/*\",\"User-Agent\":\"PostmanRuntime/7.30.0\",\"Host\":\"192.168.42.104:8080\",\"Accept-Encoding\":\"gzip, deflate, br\",\"Postman-Token\":\"9c3015be-c5a4-4195-8c76-45a03203e201\",\"Content-Length\":\"40\",\"Content-Type\":\"application/json\"}', '{\"user\":{\"name\":\"zhansan\"}}', '[{\"name\":\"战三\",\"id\":1,\"age\":11,\"email\":\"cpzisme@qq.com\"},{\"name\":\"李四\",\"id\":2,\"age\":12,\"email\":\"lisi@qq.com\"},{\"name\":\"王五\",\"id\":3,\"age\":121,\"email\":\"das@qq.com\"}]', '', 153, '2022-12-31 15:55:47');能够看到,曾经实现日志记录。 ...

December 31, 2022 · 1 min · jiezi

关于springboot:Spring-Boot快速整合Swagger

一、引言swagger是当下较风行的生成Api文档的工具,在理论开发过程中,前后端开发基于Api文档来进行对接联调,而有一个好的Api文档对晋升工作效率至关重要。大多数程序员是不太喜爱写文档的,比方我,而swagger很好地帮忙咱们解决了这个问题,通过一些简略配置,就能够帮忙开发人员生成Api文档,接下来介绍Spring Boot工程如何来疾速整合Swagger。 这里我应用的Spring Boot版本:2.5.14 二、疾速整合2.1 pom引入swagger依赖在pom文件中引入swagger依赖,这里应用版本2.7.0,如下 <!-- 整合swagger --><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version></dependency><dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version></dependency>2.2 定义配置类配置类上增加注解@EnableSwagger2,阐明启用swagger,配置类示例如下 /** * Description: SwaggerConfiguration * Created by kamier on 2022/12/28 22:06 */@Configuration@EnableSwagger2public class SwaggerConfiguration { /** * 文档定义 * @return */ @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) // 文档类型 .apiInfo(apiInfo()) // api信息 .select() .apis(RequestHandlerSelectors.basePackage("com.kamier.springboot.swagger")) // 抉择根底门路来须要生成api .paths(PathSelectors.any()) // 匹配某些门路,比方/user/* .build() .globalOperationParameters(setHeaderToken()); // 增加全局参数 } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("action-swagger") .description("api文档1") .termsOfServiceUrl("") .version("1.0") .build(); } /** * 设置swagger文档中全局参数 */ private List<Parameter> setHeaderToken() { List<Parameter> params = new ArrayList<>(); ParameterBuilder parameterBuilder = new ParameterBuilder(); Parameter parameter = parameterBuilder.name("token") .description("用户token") .modelRef(new ModelRef("string")) .parameterType("header") .required(true) .build(); params.add(parameter); return params; }}2.3 编写Controller、实体类编写一个简略Controller,用于测试,代码如下 ...

December 31, 2022 · 2 min · jiezi

关于springboot:少走十年弯路39W字顶级SpringBoot笔记学废springboot

如果只能举荐一本对于学习Spring的书,你会举荐哪一本? 如果只举荐一本书,那么我会举荐大家看一本超经典的书——《Spring Boot趣味实战课》。 与市面上其余Spring相干的技术书籍不同,本书没有拘泥于技术教程似的训导,像讲故事一样,将Spring Boot的基 础原理和面向实战利用的技巧娓娓道来,行文流畅。将来的Java我的项目开发必然是基于Spring Boot的我的项目开发。把握Spring Boot未然成为 所有Java工程师进入这个行业的必备技能。 上面就由小编来展现一下这本书吧! 因为篇幅起因,以下只展现局部内容,有须要的小伙伴【间接点击此处】即可获取残缺文档!第1章 Spring Boot凭什么成为JVM圈的框架“一哥”1.1用数据谈话1.2多方反对1.3打铁还需本身硬1.4要点回顾 第2章 兵马未动,粮草先行——码前筹备2.1软件环境2.2大管家Maven2.3打造一件趁手的兵器2.4要点回顾 第3章 牛刀小试——五分钟入门Spring Boot3.1万物皆可Hello World3.2 Spring Boot的工程构造3.3珍视生命,我用Starters3.4值得领有的YAML3.5要点回顾 第4章 斗转星移,无人能及——Spring MVC4.1 Spring MVC简介4.2接管参数的各种形式4.3参数校验4.4原理剖析4.5拦截器4.6要点回顾 第5章 你有REST Style吗5.1你应该懂—点HTTP5.2接口代言人Swagger5.3解密REST5.4 URL与URI5.5要点回顾 第6章 与长久化无关的那些事儿6.1倒退6.2派别之争6.3 Spring Data JPA6.4 MyBatis Plus6.5弱小的Druid6.6事务6.7要点回顾 第7章 出征前送你3个锦囊7.1代码的护身符——单元测试7.2天有不测风云——异样解决7.3软件系统的黑匣子——日志7.4要点回顾 第8章 Spring Boot的外围原理8.1你真的懂IOC吗8.2什么是AOP8.3为什么一个main办法就能启动我的项目8.4比你更懂你的主动配置8.5要点回顾 第9章 互联网利用性能瓶颈的“万金油”——Redis9.1初识Redis9.2 Redis能够做什么9.3应用Redis9.4更多用法9.5 Redis实现分布式锁9.6要点回顾 第10章 平安畛域的“扛把子”——Spring Security10.1认证和受权10.2 Spring Security简介10.3性能一览10.4入手实际10.5前景10.6要点回顾 第11章 自律到“令人发指”的定时工作11.1什么时候须要定时工作11.2 Java中的定时工作11.3 Spring Task实战11.4整合Quartz11.5 cron表达式11.6要点回顾 第12章 RabbitMQ从哪里来、是什么、能干什么、怎么干12.1音讯队列的由来12.2外围概念12.3业务场景12.4工作模式12.5入手实际12.6要点回顾 第13章 反其道行之的Elasticsearch13.1 Elasticsearch简介13.2外围概念13.3入手实际13.4数据同步13.5要点回顾 第14章 我的项目上线的“最初一公里”——部署与监控14.1部署14.2监控14.3要点回顾 第15章 你学习技术的“姿态”对吗15.1技术应该怎么学15.2不怕麻烦15.3遇到问题怎么办15.4要点回顾 以上这本书就是我举荐的,学Spring的好书。 ...

December 29, 2022 · 1 min · jiezi

关于springboot:springboot-连接不上-redis-的三种解决方案

针对于springboot 连贯不上 redis 这种状况,首先,咱们最简略间接的办法就是须要确认Redis是否曾经失常启动(验证办法:如果装置在Linux下的话能够应用ps-ef|grep redis来进行确认是否开启) 如果未开启,咱们能够抉择输出相干命令操作来开启Redis:第一种:先进入到redis src目录下(依据本人装置的中央能够通过cd命令进入指定目录,而后应用ls命令查看当前目录下或者指定目录下的所有文件和目录来寻找是否有src目录),进入src外面之后,能够间接输出./redis-server命令就能够开启redis(然而这种启动形式有个毛病就是,Redis在启动之后须要始终关上这个窗口,如果这个窗口关掉Redis服务也会被关掉)。 第二种:为了避免窗口敞开Redis服务也随之敞开的状况,咱们须要应用后盾过程形式来启动Redis,简略来讲就是让他这个窗口始终关上。首先咱们只须要批改redis.conf文件即可(通过ls命令找到redis.conf文件) 再通过vi编辑器来进入redis.conf文件 进入文件之后咱们只须要批改守护线程(这一步真的非常简单啦) 批改之后就能够启动啦(依据本人的目录进入就能够啦!!)当然敞开的时候咱们须要查找过程的形式来进行敞开哦,因为当初曾经转成后盾执行了嘛,毕竟它的服务咱们是看不见滴!很简略,敞开的时候咱们先用ps-aux|grep redis查看redis过程(会显示对应的redis服务信息,咱们通过redis过程标号杀死过程。例如图中的过程编号是4609,咱们通过输出kill-9 4609的命令杀死这条过程就能够)。 以上就是对于后盾过程的启动跟敞开办法,是不是很简略的,对照输出相干命令就能够啦,而且这种后盾启动的益处就是,开启redis之后,再也不必放心进行其余操作的时候造成redis服务敞开啦!其次如果不是因为Redis自身没有失常启动胜利,那么能够思考上面这种状况:第一种:查看防火墙是否关上6379端口(能够应用systemctl status firewalld命令来查看防火墙的状态),1.输出开启端口命令(-permanent:是永恒失效的意思) 2.重启防火墙命令 3.查看防火墙凋谢端口命令(查看外面是否有6379端口) 第二种:批改application.yml的redis配置中的spring.redis.timeout中连贯超时工夫 第三种:找到你们装置redis时候的配置文件进行批改(通过执行vim/vi redis.conf命令), 第一步:须要将bind 127.0.0.1进行批改(因为如果不批改的话,bind 127.0.0.1失效后,就只能本机拜访redis,所以咱们能够尝试批改成 bind 0.0.0.0 这样所有的ip就都能够进行拜访,然而须要留神的是:再部署的时候还是倡议还原) 第二步:将protected-mode yes 改成:protected-mode no(因为redis3.2版本减少了protected-mode配置,默认是yes,即开启。如果要设置让内部网络连接redis服务就必须先敞开这个模式,否则内部网络无奈间接拜访,其次就是开启protected-mode保护模式的时候,需配置一个bind ip或者是设置拜访明码) 以上就是我集体在呈现此类问题时的几个简略的解决方案,大家能够依据本人的集体状况找到相应的解决方案,心愿对大家有所帮忙哦!

December 26, 2022 · 1 min · jiezi

关于springboot:一次SpringBoot版本升级引发的血案

前言最近项目组降级了SpringBoot版本,由之前的2.0.4降级到最新版本2.7.5,却引出了一个大Bug。到底是怎么回事呢?1.案发现场有一天,项目组的共事反馈给我说,我之前有个接口在新的测试环境报错了,具体异样是:Missing argment level for method parameter of type Integer。我过后的第一反馈有点懵,心想这个接口是一个老接口,有一年多的工夫都没改过了,怎么会出问题呢?他说近期另外一个共事为了部署阿里云服务器,把新测试环境SpringBoot的版本升级到了最新版。之后,在测试的过程中,发现我有个Get申请接口报异样了。该接口代码相似于这样:在getCategory接口中,有两个参数: type示意大类,是必传的。level示意要返回几级分类,比方:4级分类,就传4,是非必传的,默认就是查4级分类。 就是这样一个接口的level参数,前端没有传参,例如: 后果被Spring MVC拦挡间接报错了。2 报错的起因从打印的异样信息看,当初level参数必须要传值了,之前是可传,可不传的。我起初本打算自定义Spring的转换器,批改一下校验规定,跟老版本保持一致。这样那些根本接口就不必改了。但起初发现,被spring-web-5.3.23的源码有情的打脸了。在org.springframework.web.method.annotation包下的AbstractNamedValueMethodArgumentResolver类的resolveArgument办法中:多了这样的校验。如果该参数为空,没有设置默认值,required属性为true,并且不是Optional类型,则执行handleMissingValueAfterConversion办法。该办法会调用handleMissingValue办法,具体代码如图中所示:最初会抛出之前我看到的那个异样。起因最新版本的Spring中不容许Get接口的申请参数,在不应用@RequestParam注解时,值为空的状况呈现了。3 如何解决问题?想要解决下面的报错问题,其实很简略,只需在level参数前加@RequestParam注解,并且设置required属性为false。例如:然而前面发现,我的项目中不只我这一个接口要调整,其余好多共事的接口,也有相似的问题,须要批改的接口很多。这个改变的工作量不小。哭晕在测试。。。后话这个问题有很多人中招,所以十分有必要把这个问题分享给大家,防微杜渐。我之前level参数不加@RequestParam注解,也没设置required属性,过后持有的心态是Spring有默认值,有些注解不加,程序也能失常运行,既然这样就能够少写点代码,并且在过后的版本测试过,没有呈现过什么问题。这种状况其实是Spring框架的一个bug,曾经在最新版本中被修复了。。。连忙review一下你们的代码,看看有没有相似的用法,不然迟早有一天也会中招。

December 22, 2022 · 1 min · jiezi

关于springboot:Spring事务失效场景

抛出查看异样比方你的事务控制代码如下:@Transactionalpublic void transactionTest() throws IOException{ User user = new User();UserService.insert(user);throw new IOException();}复制代码如果@Transactional 没有特地指定,Spring 只会在遇到运行时异样RuntimeException或者error时进行回滚,而IOException等查看异样不会影响回滚。 public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error);}复制代码解决方案: 晓得起因后,解决办法也很简略。配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class)。 业务办法自身捕捉了异样@Transactional(rollbackFor = Exception.class)public void transactionTest() { try { User user = new User(); UserService.insert(user); int i = 1 / 0;}catch (Exception e) { e.printStackTrace();}}复制代码这种场景下,事务失败的起因也很简略,Spring是否进行回滚是依据你是否抛出异样决定的,所以如果你本人捕捉了异样,Spring 也无能为力。 看了下面的代码,你可能认为这么简略的问题你不可能犯这么愚昧的谬误,然而我想通知你的是,我身边简直一半的人都被这一幕困扰过。 写业务代码的时候,代码可能比较复杂,嵌套的办法很多。如果你不小心,很可能会触发此问题。举一个非常简单的例子,假如你有一个审计性能。每个办法执行后,审计后果保留在数据库中,那么代码可能会这样写。 @Servicepublic class TransactionService { @Transactional(rollbackFor = Exception.class)public void transactionTest() throws IOException { User user = new User(); UserService.insert(user); throw new IOException();}} ...

December 22, 2022 · 2 min · jiezi

关于springboot:Springboot快速整合Quartz

一、引言在java中最罕用的定时工作框架当属quartz了,早在spring boot还未公布之前,spring就曾经在spring-context-support包中对quartz进行了集成,而应用spring boot来整合quartz就变的尤为不便、快捷。 二、疾速整合2.1 pom引入starter依赖spring boot最重要的就是它的插件化设计,想引入哪个模块或框架个别只需引入对应的starter即可,quartz也是一样,如下 <!-- 整合quartz --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId></dependency>(这里的spring-boot-starter-quartz没有写明version版本,默认与spring boot的版本统一即可)。 在pom中点击spring-boot-starter-quartz进去能够看到其外部依赖spring-boot-starter、spring-context-support、spring-tx以及quartz,如下 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.6.6</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.3.18</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.18</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version> <scope>compile</scope> </dependency></dependencies>2.2 定义工作类自定义的工作类须要实现quartz提供的Job接口,样例如下 /** * 工作类 */public class MyJob1 implements Job { @Autowired private MyService1 myService1; @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();// System.out.println("工作创建人:" + jobDataMap.getString("createUser")); myService1.work(); }}如上代码中,工作类中能够间接应用@Autowired来注入bean,这为咱们编写工作类时提供了很好的便当,不须要通过工作参数jobDataMap来传入bean了。MyService1是一个简略的bean,代码如下 ...

December 22, 2022 · 1 min · jiezi

关于springboot:深入学习-Spring-Web-开发-Bean-的注入

上一篇文章,咱们聊了与 Bean 的申明相干的内容,本文,咱们重点聊聊与 Bean 注入相干的内容。 注入形式Bean 注入的形式,次要蕴含三种,即:构造函数注入、setter 注入和属性注入。 构造函数注入构造函数注入,示例代码如下。对于上面的代码,利用启动后,myBean 属性会被顺利注入: @Componentpublic class InjectionComponent { private MyBean myBean; public InjectionComponent(MyBean myBean) { this.myBean = myBean; }}setter 注入setter 注入的示例代码如下(必须给 setter 办法增加 @Autowired 注解): @Componentpublic class InjectionComponent2 { private MyBean myBean; @Autowired public void setMyBean(MyBean myBean) { this.myBean = myBean; }}属性注入属性注入的示例代码如下: @Componentpublic class InjectionComponent3 { @Autowired private MyBean myBean;}申明注解@Autowired从下面的代码例子,咱们能够晓得 @Autowired 是用来申明 Bean 的注入的。 @Autowired 的作用指标@Autowired 是 JSR-330 javax.inject.Inject 的代替计划,能够作用在构造函数、办法、参数、字段和注解上。 作用在构造函数上如下面的例子 构造函数注入,应用该形式进行注入时,@Autowired 不是必须的。构造函数能够是公共的,也能够是公有的。构造函数中的参数,必须在 Spring IoC 容器中能够找到,否则利用启动时将抛出异样。如果存在多个构造函数,则必须存在一个无参构造函数,否则利用启动时将抛出异样。如果存在无参构造函数,则创立实例时,将应用该构造函数进行创立。当存在多个构造函数时,如果在其中一个构造函数上加上 @Autowired,则下面一条规定会被突破,即此时能够不存在无参构造函数,且创立实例时,应用的不是无参构造函数,而是加上了 @Autowired 的构造函数。@Autowired 有一个 required 属性,默认为 true,示意构造函数中的全副参数都必须在 Spring IoC 容器中存在,否则利用启动时将抛出异样。默认状况下,不能有多个构造函数存在 @Autowired 注解,否则,利用启动时将抛出异样。如果多个构造函数都加上 @Autowired 注解,那么,这些注解的 required 属性必须全都设置为 false。这种状况下,示意这几个构造函数都是候选构造函数,创立实例时,将抉择可能满足的参数最多的一个构造函数来进行对象的创立。在这根底上,会优先选择公共的构造函数。例如:如果存在一个蕴含 2 个参数和一个蕴含 3 个参数的构造函数,且这两个构造函数都可能被满足,然而蕴含 2 个参数的构造函数是公共的,蕴含 3 个参数的构造函数是公有的,那么,此时抉择的将是蕴含 2 个参数的构造函数。如果下面一条规定中的所有候选构造函数,没有一个能够被满足,并且存在无参构造函数,则创立实例时会抉择无参构造函数。如果此时不存在无参构造函数,则利用启动时将抛出异样。如上面的代码,因为不存在 Cache 类的实例,而 MyBean 类和 RestTemplate 类的实例是存在的,因而,利用启动时将应用构造函数 InjectionComponent4(MyBean, RestTemplate) 来初始化 InjectionComponent4 的实例。 ...

December 20, 2022 · 4 min · jiezi

关于springboot:深入学习-Spring-Web-开发-Bean-的声明

IoC 是 Spring 框架的最重要个性之一,对于 Spring IoC,咱们可能最直观感触到的可能就是 Bean 的申明与注入,本文,咱们先讲讲与 Bean 的申明相干的内容。 有多种形式能够申明 Spring Bean。 @Component通常能够在某个类上加上 @Component 注解,以标识主动将该类的实例注册到 Spring IoC 容器中: @Componentpublic class MyComponent {}@Controller、@Service、@Repository 是相似的,它们之间概念上的区别大于实际上的区别,通常不同作用的类应用不同的注解。 @Controller如果这个类是一个 controller 类,那么个别应用 @Controller 注解(或者 @RestController 注解): @Controllerpublic class UserController { @Autowired private UserService userService; @GetMapping("/api/users/{userId}") public User findUser(@PathVariable(name = "userId") Long userId) { return userService.findUser(userId); }}@Service如果这个类是一个 service 类,那么个别应用 @Service 注解: @Servicepublic class UserService { @Autowired private UserRepository userRepository; public User findUser(Long userId) { return userRepository.findById(userId); }}@Repository如果这个类是一个 dao 类,那么个别应用 @Repository 注解: ...

December 20, 2022 · 5 min · jiezi

关于springboot:非阻塞-SpringBoot-之-Kotlin-协程实现

非阻塞 SpringBoot 之 Kotlin 协程实现Why?Spring Boot 默认应用 Servlet Web服务器,Tomcat,每个申请调配一个线程。如果服务不是计算密集型,而是存在大量 I/O 期待,那么会节约大量CPU工夫,导致CPU利用率不高。如果强行加大线程池,会消耗大量内存,且减少线程切换的损耗。 于是,咱们能够思考应用 Reactive Web 服务器,Netty,基于事件循环,对于I/O密集型服务,性能极高。 背景介绍咱们有个服务,须要封装调用大量内部接口,而后做防腐转换和数据聚合。随着业务变得复杂,接口响应速度越来越慢,无奈满足业务的时延需要。于是咱们开始了第一轮优化,应用CompletableFuture + 线程池进行并发调用。一番操作之后,时延降下来了,然而资源利用率不高,单个节点能接受的并发量很小。如果遇到搞流动,并发需要上升时,须要申请大量资源进行扩容,十分节约。 此时要问:为何做了异步化革新,并发能力还是上不来? 起因在于整个服务的模型还是阻塞式I/O,异步调用的时候,尽管用了一个新线程,但调用过程还是阻塞式的,这条线程就被阻塞了。当服务并发升高时,线程池里就会产生大量被阻塞的线程,而这些线程不是绿色线程(用户态线程),而是抢占式的,会分走贵重的CPU工夫,那么后果就是资源利用率低下,并发能力差了。 How?为了解决I/O密集型服务并发能力低下的问题,能够改用响应式(Reactive)模型。实际上Spring很早就有相应的解决方案:Reactor + WebFlux,可实现非阻塞式IO。 尽管响应式编程非常弱小,但也有其难点:不是过程式的,写业务代码很难懂,而且难以调试和测试。响应式编程不是本文的探讨重点,感兴趣的同学能够钻研一下,从最早的 RxJava 到目前的 Project Reactor。 那有没有更简略的计划?无妨看看:协程。 Next:CoroutinesJava 也有协程计划,叫 Quasar(协程在外面叫 Fiber),然而18年之后就没有更新了,据说作者跑去写 Project Loom 了。Loom是下一代Java协程库,但目前还没有成熟,上生产是不可能的了。 尽管Java没有协程,然而JVM语言Kotlin有。上面就用 Kotlin Coroutines 联合 WebFlux 实现非阻塞式 SpringBoot 服务。 假如有个API,/slowInt,通过 1s 返回一个整数。咱们要调两次,而后计算 sum。 响应工夫 1s 极其一点,不过测试的时候更容易看出区别咱们无妨应用非阻塞式(WebClient)和阻塞式(RestTemplate)的web客户端,别离做性能测试。 import kotlinx.coroutines.*import org.springframework.beans.factory.annotation.Autowiredimport org.springframework.http.MediaType.APPLICATION_JSONimport org.springframework.stereotype.Serviceimport org.springframework.web.client.RestTemplateimport org.springframework.web.client.getForObjectimport org.springframework.web.reactive.function.client.WebClientimport org.springframework.web.reactive.function.client.awaitBody@Serviceclass ExampleService { @Autowired lateinit var webClient: WebClient @Autowired lateinit var restTemplate: RestTemplate /** * 应用协程 */ suspend fun sumTwo(): Int = coroutineScope { // 别离异步调用,换成 getInt2() 再测一遍 val i1: Deferred<Int> = async { getInt() } val i2: Deferred<Int> = async { getInt() } // 聚合 i1.await() + i2.await() } /** * None-Blocking web client * very fast */ suspend fun getInt(): Int { return webClient.get() .uri("/slowInt") .accept(APPLICATION_JSON) .retrieve().awaitBody() } /** * Blocking web client * very slow */ fun getInt2(): Int { val result = restTemplate.getForObject<Int>("/slowInt").toInt() println(result) return result }}@RestControllerclass ExampleController { @Autowired lateinit var exampleService: ExampleService @GetMapping("/sum") suspend fun sum(): String? = "Sum: ${exampleService.sumTwo()}"}性能测试而后用 JMeter 压一压。 ...

December 19, 2022 · 1 min · jiezi

关于springboot:SpringBoot-30-新特性内置声明式HTTP客户端

http interface从 Spring 6 和 Spring Boot 3 开始,Spring 框架反对将近程 HTTP 服务代理成带有特定注解的 Java http interface。相似的库,如 OpenFeign 和 Retrofit 依然能够应用,但 http interface 为 Spring 框架增加内置反对。 什么是申明式客户端申明式 http 客户端宗旨是使得编写 java http 客户端更容易。为了贯彻这个理念,采纳了通过解决注解来主动生成申请的形式(官网称说为申明式、模板化)。通过申明式 http 客户端实现咱们就能够在 java 中像调用一个本地办法一样实现一次 http 申请,大大减少了编码老本,同时进步了代码可读性。 举个例子,如果想调用 /tenants 的接口,只须要定义如下的接口类即可public interface TenantClient { @GetExchange("/tenants") Flux<User> getAll();}Spring 会在运行时提供接口的调用的具体实现,如上申请咱们能够如 Java 办法一样调用 @AutowiredTenantClient tenantClient;tenantClient.getAll().subscribe();测试应用1. maven 依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><!-- For webclient support --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId></dependency>如下图: 目前官网只提供了非阻塞 webclient 的 http interface 实现,所以依赖中咱们须要增加 webflux2. 创立 Http interface 类型须要再接口类上增加 @HttpExchange 申明此类事 http interface 端点@HttpExchangepublic interface DemoApi { @GetExchange("/admin/tenant/list") String list();办法上反对如下注解@GetExchange: for HTTP GET requests.@PostExchange: for HTTP POST requests.@PutExchange: for HTTP PUT requests.@DeleteExchange: for HTTP DELETE requests.@PatchExchange: for HTTP PATCH requests.办法参数反对的注解@PathVariable: 占位符参数.@RequestBody: 申请body.@RequestParam: 申请参数.@RequestHeader: 申请头.@RequestPart: 表单申请.@CookieValue: 申请cookie.2. 注入申明式客户端通过给 HttpServiceProxyFactory 注入携带指标接口 baseUrl 的的 webclient,实现 webclient 和 http interface 的关联 @Bean DemoApi demoApi() { WebClient client = WebClient.builder().baseUrl("http://pigx.pigx.vip/").build(); HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build(); return factory.createClient(DemoApi.class); }3. 单元测试调用 http interface@SpringBootTestclass DemoApplicationTests { @Autowired private DemoApi demoApi; @Test void testDemoApi() { demoApi.list(); }}基于Spring Boot 2.7、 Spring Cloud 2021 & Alibaba、 SAS OAuth2 一个可反对企业各业务零碎或产品疾速开发实现的开源微服务利用开发平台

December 1, 2022 · 1 min · jiezi

关于springboot:SpringBoot30发布Are-you-ready

自从2018年3月 SpringBoot2.0.X版本开始,整个 2.X 版本曾经通过了4年多的工夫,而就在前不,2.X系列的也曾经迎来了他的最终版本:SpringBoot2.7.X 而就在2022年11月24号,SpringBoot也公布了他 3.0 的最终 RELEASE 版本 版本简介:M 示意里程碑版本;RC 示意候选公布版本;SNAPSHOT(快照版)示意构建,后续会降级 对于SpringBoot3的一些官网文档,这里给出一下地址: https://spring.io/projects/sp... 有几点环境的注意事项,这里先跟大家留神一下: 1、SpringBoot3的依赖,防止降级时呈现包抵触https://docs.spring.io/spring... 2、版本适配,各次要依赖中间件最低版本要求软 件最 低 要 求备 注JDKJDK17+ Gradlegradle-7.5.1 IdeaIdea 2021.2+ mavenmaven-3.5+ SpringFrameworkSpring Framework 6+ 3、抛弃办法在 SpringBoot 2.x 中不举荐应用的类、办法和属性已在此版本中删除,这里大家要留神,降级的时候,代码中不要有过期办法的应用哦. 4、删除反对· Apache ActiveMQ· Atomikos· EhCache 2· Hazelcast 3唠唠 JDK17Java 17的新个性,别离是:306:复原始终严格的浮点语义356:增强型伪随机数发生器382:新的 macOS 渲染管道391:macOS/AArch64 端口398:弃用行将删除的 Applet API403:强封装JDK的外部API406:Switch模式匹配(预览)407:删除 RMI 激活409:密封类410:删除实验性 AOT 和 JIT 编译器411:弃用行将删除平安管理器412:内部函数和内存 API(孵化器)414:Vector API(第二次进行个性孵化)415:特定于上下文的反序列化过滤器 更多对于JDK17的新个性,大家能够参考 JDK17 新个性 Java 17GC速度OptaPlanner网站做了一项基准测试:Java到底有多快?通过比拟 JDK 17、JDK 16 和 JDK 11 来通知你答案。 基准测试总结均匀而言,以 OptaPlanner 为例的基准测试结果表明 ...

November 29, 2022 · 1 min · jiezi

关于springboot:记一次自定义starter引发的线上事故复盘

前言本文素材来源于业务部门技术负责人某次线上事变复盘分享。故事的背景是这样,该业务部门招了一个技术挺不错的小伙子小张,因为小张技术能力在该部门比较突出,在入职不久后,他便成为这个部门某个项目组的team leader,同时也领有review 该项目标权力。(注: 该我的项目为微服务项目),在某次小张review我的项目的时候,他发现好几个我的项目,发现代码有很多反复,于是他就动了把这些反复代码封装成starter的念头,而后也是因为这次的封装,带来一次线上事变。上面就以代码示例的模式,模仿这次事变 代码示例注: 本文仅模仿呈现事变的代码片段,不波及业务 1、模仿小张的封装的starter@Slf4jpublic class HelloSevice { private ThreadPoolTaskExecutor threadPoolTaskExecutor; public HelloSevice(ThreadPoolTaskExecutor threadPoolTaskExecutor){ this.threadPoolTaskExecutor = threadPoolTaskExecutor; } public String sayHello(String username){ threadPoolTaskExecutor.execute(()->{ log.info("hello: {} ",username); }); return " hello : " + username; }}@Configurationpublic class HelloServiceAutoConfiguration { @Bean public ThreadPoolTaskExecutor threadPoolTaskExecutor(){ ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(2); threadPoolTaskExecutor.setMaxPoolSize(4); threadPoolTaskExecutor.setQueueCapacity(1); threadPoolTaskExecutor.setThreadFactory(new ThreadFactory() { private AtomicInteger atomicInteger = new AtomicInteger(); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("hello-pool-" + atomicInteger.getAndIncrement()); return thread; } }); return threadPoolTaskExecutor; } @Bean public HelloSevice helloSevice(ThreadPoolTaskExecutor threadPoolTaskExecutor){ return new HelloSevice(threadPoolTaskExecutor); }}spring.factories ...

November 22, 2022 · 2 min · jiezi

关于springboot:SpringBoot之用拦截器避免重复请求

拦截器什么是拦截器Spring MVC中的拦截器(Interceptor)相似于Servlet中的过滤器(Filter),它次要用于拦挡用户申请并作相应的解决。例如通过拦截器能够进行权限验证、记录申请信息的日志、判断用户是否登录等。 如何自定义拦截器自定义一个拦截器非常简单,只须要实现HandlerInterceptor这个接口即可,这个接口有三个可实现的办法 preHandle()办法:该办法会在控制器办法前执行,其返回值示意是否晓得如何写一个接口。中断后续操作。当其返回值为true时,示意持续向下执行;当其返回值为false时,会中断后续的所有操作(包含调用下一个拦截器和控制器类中的办法执行等)。postHandle()办法:该办法会在控制器办法调用之后,且解析视图之前执行。能够通过此办法对申请域中的模型和视图做出进一步的批改。afterCompletion()办法:该办法会在整个申请实现,即视图渲染完结之后执行。能够通过此办法实现一些资源清理、记录日志信息等工作。如何让拦截器在Spring Boot中失效想要在Spring Boot失效其实很简略,只须要定义一个配置类,实现WebMvcConfigurer这个接口,并且实现其中的addInterceptors()办法即可,代码如下: @Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private XXX xxx; @Override public void addInterceptors(InterceptorRegistry registry) { // 不拦挡的uri final String[] commonExclude = {}}; registry.addInterceptor(xxx).excludePathPatterns(commonExclude); }}用拦截器躲避反复申请需要开发中可能会常常遇到短时间内因为用户的反复点击导致几秒之内反复的申请,可能就是在这几秒之内因为各种问题,比方网络,事务的隔离性等等问题导致了数据的反复等问题,因而在日常开发中必须躲避这类的反复申请操作,明天就用拦截器简略的解决一下这个问题。 思路在接口执行之前先对指定接口(比方标注某个注解的接口)进行判断,如果在指定的工夫内(比方5秒)曾经申请过一次了,则返回反复提交的信息给调用者。 依据什么判断这个接口曾经申请了? 依据我的项目的架构可能判断的条件也是不同的,比方IP地址,用户惟一标识、申请参数、申请URI等等其中的某一个或者多个的组合。 这个具体的信息寄存在哪? 因为是短时间内甚至是霎时并且要保障定时生效,必定不能存在事务性数据库中了,因而罕用的几种数据库中只有Redis比拟适合了。 实现Docker启动一个Redisdocker pull redis:7.0.4docker run -itd \ --name redis \ -p 6379:6379 \ redis:7.0.4创立一个Spring Boot我的项目应用idea的Spring Initializr来创立一个Spring Boot我的项目,如下图: 增加依赖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.7.5</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springboot_06</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot_06</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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--spring redis配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <!-- 1.5的版本默认采纳的连接池技术是jedis 2.0以上版本默认连接池是lettuce, 在这里采纳jedis,所以须要排除lettuce的jar --> <exclusions> <exclusion> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </exclusion> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>配置Redisapplication.properties ...

November 21, 2022 · 2 min · jiezi

关于springboot:深入学习-Spring-Web-开发-启动日志

上一篇文章,咱们紧紧围绕 @SpringBootApplication 引入的注解和类,对 Spring Boot 我的项目的启动过程做了一次剖析。在理论的开发过程中,我的项目的代码毫无疑问是与咱们最为相干的,另外,咱们也不可漠视我的项目日志在咱们日常开发中所起的作用。因而,本文将围绕我的项目的启动日志,对我的项目的启动过程再做一次剖析,以便于咱们更好地了解我的项目的运行逻辑。本系列文章,笔者将会应用较多的笔墨展现代码与日志的互相关联,心愿通过这样的形式,能够缓缓让读者造就起一种代码与日志彼此贯通的思路,以帮忙读者在理论开发过程中,更好地解决所遇到的问题。 上面是我的项目的一次启动日志: . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.2)2022-09-08 08:28:22.964 INFO 54995 --- [ main] o.s.springweb.HelloWorldApplication : Starting HelloWorldApplication using Java 11.0.12 on susamludeMac.local with PID 54995 (/Users/susamlu/code/java/spring-web/spring-web-helloworld/target/classes started by susamlu in /Users/susamlu/code/java/spring-web)2022-09-08 08:28:22.968 INFO 54995 --- [ main] o.s.springweb.HelloWorldApplication : No active profile set, falling back to 1 default profile: "default"2022-09-08 08:28:24.900 INFO 54995 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)2022-09-08 08:28:24.912 INFO 54995 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]2022-09-08 08:28:24.913 INFO 54995 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.65]2022-09-08 08:28:25.090 INFO 54995 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext2022-09-08 08:28:25.091 INFO 54995 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1910 ms2022-09-08 08:28:25.857 INFO 54995 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''2022-09-08 08:28:25.873 INFO 54995 --- [ main] o.s.springweb.HelloWorldApplication : Started HelloWorldApplication in 3.735 seconds (JVM running for 4.41)SpringApplication 的动态 run() 办法,最终会调用到本身的实例 run() 办法。实例 run() 办法的内容会绝对比较复杂,为了简化其中的逻辑,咱们重点关注 printBanner()、prepareContext()、refreshContext() 几个办法。 ...

November 20, 2022 · 5 min · jiezi

关于springboot:深入学习-Spring-Web-开发-应用启动

不晓得读者在编写我的项目的时候,有没有思考过启动类中为何须要同时应用 @SpringBootApplication 和 SpringApplication,它们的作用别离又是什么?上面让咱们一起来一探到底。 @SpringBootApplication@SpringBootApplication 引入了 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解: @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication { // ...}其中 @SpringBootConfiguration 又引入了 @Configuration,从而让 HelloWorldApplication 成为了一个配置类。 @Configuration@Indexedpublic @interface SpringBootConfiguration { // ...}@EnableAutoConfiguration 通过 @Import 引入了 AutoConfigurationImportSelector 类,又通过 @AutoConfigurationPackage 间接地引入了 AutoConfigurationPackages.Registrar 类。 @AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration { // ...}@Import(AutoConfigurationPackages.Registrar.class)public @interface AutoConfigurationPackage { // ...}总的来说,就是 @SpringBootApplication 通过 @Configuration 注解让启动类 HelloWorldApplication 成为了配置类,通过 @EnableAutoConfiguration 开启了主动配置的扫描,通过 @ComponentScan 开启了 Spring Bean 的主动扫描。 ...

November 20, 2022 · 4 min · jiezi

关于springboot:深入学习-Spring-Web-开发-依赖引入

上一篇文章介绍了如何疾速搭建一个 Spring Web 我的项目,本文重点聊聊我的项目的依赖是如何引入的。 咱们后面提到,搭建 Spring Web 我的项目时,只须要继承 spring-boot-starter-parent 并指定它的版本,接着引入 spring-boot-starter-web ,且无需指定 spring-boot-starter-web 的版本,即可把 Spring Web 我的项目所须要的全副依赖引进来,具体是如何做到的呢? 这里会波及到 Maven 的 parent 和 dependencyManagement 标签,咱们先讲讲这两个标签的作用。 Maven 标签parent在 Maven 我的项目中,能够通过继承的形式,让子项目继承父我的项目所定义的内容,如:继承 groupId、version、properties、dependencies 等。 上面的例子中,my-app-child 只须要继承 my-app-parent ,即可引入父我的项目的全副依赖。即父我的项目 my-app-parent 引入了 maven-artifact 和 maven-core 两个依赖,并指定了它们的版本。 <!-- my-app-parent --><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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app-parent</artifactId> <version>1.0-SNAPSHOT</version> <name>my-app-parent</name> <url>http://www.example.com</url> <properties> <mavenVersion>3.0</mavenVersion> </properties> <dependencies> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-artifact</artifactId> <version>${mavenVersion}</version> </dependency> <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-core</artifactId> <version>${mavenVersion}</version> </dependency> </dependencies></project>子项目只需继承 my-app-parent 并指定它的版本,就引入了这两个依赖,并且依赖的版本也跟父我的项目所指定的版本一样。 ...

November 20, 2022 · 2 min · jiezi

关于springboot:深入学习-Spring-Web-开发-HelloWorld

本系列文章将从一个简略的 HelloWorld 我的项目开始,讲述如何一步步搭建一个残缺的 Spring Web 我的项目。必要时,将翻阅相干的源代码,剖析其中的实现细节。 版本阐明: 所有的我的项目代码均构建在 Spring Boot 2.7.2 之上。学习前提: 文章中波及到的代码次要构建在 Java 语言之上,因而浏览之前把握根本的 Java 语言根底是必须的;我的项目代码以 Maven 作为项目管理工具,开始之前你该当对 Maven 的基础知识有所理解。Maven 我的项目我的项目构造开始之前,咱们先温习一下 Maven 我的项目的根本内容,它的目录构造如下: .├── pom.xml└── src ├── main │ ├── java │ └── resources └── test ├── java └── resourcespom.xml 文件次要用于定义我的项目的依赖,main 目录次要用于寄存我的项目的代码和配置,test 目录次要用于寄存测试的代码和配置。 pom.xml上面是一个简略的 pom.xml 文件: <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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <version>1.0-SNAPSHOT</version> <name>my-app</name> <url>http://www.example.com</url></project>modelVersion 指定 pom 文件合乎哪个版本的描述符,groupId、artifactId、version 用来独特标识一个惟一的 jar 包,name 和 url 为描述性内容。 疾速开始搭建一个 Spring Web 我的项目,只须要简略的几步: ...

November 19, 2022 · 1 min · jiezi

关于springboot:Spring-Boot同时使用hibernate和mybatis

背景web课的我的项目开发中, 有一项是用 mybatis 进行我的项目开发。 而目前的写好的后盾仓库层是用 hibernate 进行的全自动化。 为了满足实验报告要求, 决定用mybatis改一下目前的代码, 使这两个ORM框架共存。 hibernate和mybatis两者都是ORM框架。 ORM全称是:Object Relational Mapping(对象关系映射) ORM的呈现,使得关系型数据库映射成了对象;简略来说,有了ORM之后,JAVA程序员从面向JDBC编程转化成面向JAVA对象编程 目前最风行的次要有两个,一个是声称能够不必写一句 SQL 的 hibernate,一个是能够灵便调试动静 sql 的 mybatis。 两者区别 hibernatehibernate 属于全自动的 ORM 框架,着力点在于 POJO 和数据库表之间的映射,实现映射即可主动生成和执行 sql。 Hibernate封装了很多有用的API给开发者,升高了操作数据库的难度和复杂度,但Hibernate留给开发者可操作的空间绝对Mybatis少了很多; mybatismybatis 相对来说属于半自动的 ORM 框架,着力点在于 POJO 和 SQL 之间的映射,本人编写 sql 语句。 Mybatis框架应用起来很灵便,开发者能够自定义查问语句,但看起来没有Hibernate那么便捷。比方,须要编写mapper和xml文件。 两种框架在便捷与灵便两个指标上做出了取舍与斗争。 没有最好的框架, 适宜业务自身就是最好的框架。 为了学习mybatis, 以及练习sql语句的编写,这里引入mybatis. pom.xml增加Spring Data JPA和Mybatis依赖 <!-- JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency><!-- mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency>配置application.ymlspring: # hibernate 数据库 验证级别 jpa: hibernate: ddl-auto: update show-sql: ${show-sql:false} # 配置数据源 datasource: url: jdbc:mysql://${datasource.url:127.0.0.1}:${datasource.port:3306}/${datasource.dbname:equipment_management}?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai username: ${datasource.username:root} password: ${datasource.password:}server: # 配置端口 port: ${port:8002} servlet: session: timeout: 60m# mybatis配置mybatis: mapper-locations: classpath:mapper/*.xml # mapper映射文件地位 type-aliases-package: equipmentManagementSystem.entity # 实体类所在的地位 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #用于控制台打印sql语句建设Spring Data JPA的Entity类@Entitypublic class User implements Serializable { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Long id; /** * 姓名 */ private String name; private String phone; /** * 明码 */ @Column(nullable = false) @JsonView(PasswordJsonView.class) private String password; /** * 用户名 */ @Column(nullable = false) private String username;创立数据接口层申明JPA接口public interface UserRepository extends CrudRepository<User,Long> {}借助Spring Data JPA,咱们能够通过继承CrudRepository接口,疾速定义利用的数据层。CrudRepository定义并实现了很多用于crud(创立、读取、更新、删除)操作的办法,当利用启动的时候,Spring Data JPA会在运行期主动生成实现类。 ...

November 16, 2022 · 2 min · jiezi

关于springboot:Flowable-设置流程变量的四种方式

@[toc]在之前的文章中,松哥也有和小伙伴们应用过流程变量,然而没有和大家零碎的梳理过流程变量的具体玩法以及它对应的数据表详情,明天咱们就来看看 Flowable 中流程变量的具体玩法。 1. 为什么须要流程变量首先咱们来看看为什么须要流程变量。 举一个简略的例子,假如咱们有如下一个流程: 这是一个销假流程,那么谁销假、请几天、起始工夫、销假理由等等,这些都须要阐明,不然领导审批的根据是啥?那么如何传递这些数据,咱们就须要流程变量。 2. 流程变量的分类整体上来说,目前流程变量能够分为三种类型: 全局流程变量:在整个流程执行期间,这个流程变量都是无效的。本地流程变量:这个只针对流程中某一个具体的 Task(工作)无效,这个工作执行结束后,这个流程变量就生效了。长期流程变量:顾名思义就是长期的,这个不会存入到数据库中。在接下来的内容中,我会跟大家挨个介绍这些流程变量的用法。 3. 全局流程变量假如咱们就是下面这个销假流程,咱们一起来看下流程变量的设置和获取。 3.1 启动时设置第一种形式,就是咱们能够在流程启动的时候,设置流程变量,如下: @Testvoid test01() { Map<String, Object> variables = new HashMap<>(); variables.put("days", 10); variables.put("reason", "劳动一下"); variables.put("startTime", new Date()); ProcessInstance pi = runtimeService.startProcessInstanceByKey("demo01", variables); logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId());}咱们能够在启动的时候为流程设置变量,小伙伴们留神到,流程变量的 value 也能够是一个对象(不过这个对象要可能序列化,即实现了 Serializable 接口),而后在启动的时候传入这个变量即可。 咱们在流程启动日志中搜寻 劳动一下 四个字,能够找到和流程变量相干的 SQL,一共有两条,如下: insert into ACT_HI_VARINST (ID_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, NAME_, REV_, VAR_TYPE_, SCOPE_ID_, SUB_SCOPE_ID_, SCOPE_TYPE_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_, CREATE_TIME_, LAST_UPDATED_TIME_) values ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) , ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )INSERT INTO ACT_RU_VARIABLE (ID_, REV_, TYPE_, NAME_, PROC_INST_ID_, EXECUTION_ID_, TASK_ID_, SCOPE_ID_, SUB_SCOPE_ID_, SCOPE_TYPE_, BYTEARRAY_ID_, DOUBLE_, LONG_ , TEXT_, TEXT2_) VALUES ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) , ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) , ( ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )从标名称上大略就能看进去,ACT_HI_VARINST 是存储流程执行的历史信息的,ACT_RU_VARIABLE 则是保留流程运行时候的信息的。 ...

November 8, 2022 · 3 min · jiezi

关于springboot:聊聊springboot项目如何优雅的修改或者填充请求参数

前言之前咱们的文章记一次springboot我的项目自定义HandlerMethodArgumentResolver不失效起因与解法开端留了一个思考题:在咱们我的项目中如何优雅批改或者填充申请参数,本期就来揭晓这个谜底 办法一:自定义HandlerMethodArgumentResolver执行步骤: 1、自定义HandlerMethodArgumentResolver类public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { private HandlerMethodArgumentResolver handlerMethodArgumentResolver; public UserHandlerMethodArgumentResolver(HandlerMethodArgumentResolver handlerMethodArgumentResolver) { this.handlerMethodArgumentResolver = handlerMethodArgumentResolver; } @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class) && User.class.isAssignableFrom(parameter.getParameterType()); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { User user = (User) handlerMethodArgumentResolver.resolveArgument(parameter,mavContainer,webRequest,binderFactory); if(StringUtils.isBlank(user.getId())){ HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String id = request.getHeader("id"); user.setId(id); } System.out.println(user); return user; }}2、将自定义的HandlerMethodArgumentResolver增加进行argumentResolvers@Configurationpublic class HandlerMethodArgumentResolverAutoConfiguration implements InitializingBean { @Autowired private RequestMappingHandlerAdapter requestMappingHandlerAdapter; @Override public void afterPropertiesSet() throws Exception { List<HandlerMethodArgumentResolver> argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers(); List<HandlerMethodArgumentResolver> customArgumentResolvers = new ArrayList<>(); for (HandlerMethodArgumentResolver argumentResolver : argumentResolvers) { if(argumentResolver instanceof RequestResponseBodyMethodProcessor){ customArgumentResolvers.add(new UserHandlerMethodArgumentResolver(argumentResolver)); } customArgumentResolvers.add(argumentResolver); } requestMappingHandlerAdapter.setArgumentResolvers(customArgumentResolvers); }}至于为啥这么搞,而不是通过 ...

November 8, 2022 · 4 min · jiezi

关于springboot:108拦截器获取请求body

定义一个拦截器:@Componentpublic class SjcjInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } String token = request.getHeader("token"); RequestWrapper requestWrapper = new RequestWrapper(request); String body = (String)requestWrapper.getAttribute("body"); return true; } }定义一个申请wrapperpublic class RequestWrapper extends HttpServletRequestWrapper { private static Logger logger = LoggerFactory.getLogger(RequestWrapper.class); private String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { String charsetName = "UTF-8"; String contentType = request.getHeader("Content-Type"); if (!(StrUtil.isBlank(contentType) || "undefined".equals(contentType))) { contentType = contentType.replaceAll(" ", ""); // application/json; charset=utf-8 String[] contentTypeArr = contentType.split(";"); for (String str : contentTypeArr) { if (str.startsWith("charset=")) { charsetName = str.substring(8); break; } } } logger.info("defaultCharsetName: {}", Charset.defaultCharset().name()); logger.info("charsetName: {}", charsetName); if (StrUtil.isBlank(charsetName) || "undefined".equals(charsetName)) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); } else { bufferedReader = new BufferedReader(new InputStreamReader(inputStream, charsetName)); } String str = null; while ((str = bufferedReader.readLine()) != null) { stringBuilder.append(str); } } else { stringBuilder.append(""); } } catch (IOException ex) { logger.error("body读取失败"); logger.error(ex.getMessage()); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } public void setBody(String body) { this.body = body; }}定义RequestFilter@Componentpublic class CustomRequestFilter extends OncePerRequestFilter { private static Logger logger = LoggerFactory.getLogger(CustomRequestFilter.class); private String aesSecret = "B16883417B43599B6AF58A0E8769F46D"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { RequestWrapper requestWrapper = new RequestWrapper(request); if(requestWrapper == null) { filterChain.doFilter(request, response); } else { String body = requestWrapper.getBody(); //在这里能够批改body的内容 // requestWrapper.setBody(obj); requestWrapper.setAttribute("body",body); filterChain.doFilter(requestWrapper, response); } }}

November 8, 2022 · 2 min · jiezi

关于springboot:不愧是阿里内部Spring-Boot笔记从头到尾全是干货

Spring Boot是什么?Spring Boot 具备 Spring 所有优良个性,Spring 能做的事,Spring Boot 都能够做,而且应用更加简略,性能更加丰盛,性能更加稳固而强壮。随着近些年来微服务技术的风行,Spring Boot 也成了时下煊赫一时的技术。 Spring Boot 集成了大量罕用的第三方库配置,Spring Boot 利用中这些第三方库简直能够是零配置的开箱即用(out-of-the-box),大部分的 Spring Boot 利用都只须要十分大量的配置代码(基于 Java 的配置),开发者可能更加专一于业务逻辑。 Spring Boot 能做什么?应用 Spring 我的项目疏导页面能够在几秒构建一个我的项目;不便对外输入各种模式的服务,如 REST API、WebSocket、Web、Streaming、Tasks;十分简洁的安全策略集成;反对关系数据库和非关系数据库;反对运行期内嵌容器,如 Tomcat、Jetty;弱小的开发包,反对热启动;主动治理依赖;自带利用监控;反对各种 IED,如 IntelliJ IDEA、NetBeans。为什么学习 Spring Boot ?学习Spring Boot,因为它提供的性能和长处如下 它提供了一种灵便的办法来配置Java Bean,XML配置和数据库事务。它提供弱小的批处理和治理REST端点。在Spring Boot中,一切都是主动配置的; 无需手动配置。它提供基于正文的spring应用程序。简化依赖治理。它包含嵌入式Servlet容器。从软件倒退的角度来讲,越简略的开发模式越风行,简略的开发模式解放出更多生产力,让开发人员能够防止将精力消耗在各种配置、语法所设置的门槛上,从而更专一于业务。 Spring Boot 所集成的技术栈,涵盖了各大互联网公司的支流技术,跟着 Spring Boot 的路线去学习,根本能够理解国内外互联网公司的技术特点。 基于这些,咱们有必要来学习Spring Boot技术。本文实用于所有Java编程语言开发人员,老手小白也能把握。 那如何学习呢?市面上对于Spring解说的材料零零碎碎,基本不成残缺体系;去官网学习又无从下手,饱受打击。因而我将在这分享我精心收集整理的《Spring boot 学习笔记》从入门到入魂 点击此处获取 除了spring boot 笔记外小编还筹备了很多Java外围常识内容的材料。 Ps:细节内容过多,所以只把局部知识点截图进去粗略的介绍,每个小节点外面都有更细化的内容。 第一局部介绍了spring boot入门、数据库的应用和拜访性能晋升、界面设计、平安设计等重要技术常识,以实用性为主。 模块一:spring boot入门 介绍开发环境的搭建和开发工具的抉择和装置,并以一个非常简单的实例演示了如何应用spring boot 框架创立工程和公布利用。 模块二:在spring boot中应用数据库 ...

November 4, 2022 · 1 min · jiezi

关于springboot:Spring-Boot-Redis-实现分布式锁还有谁不会

一、业务背景有些业务申请,属于耗时操作,须要加锁,避免后续的并发操作,同时对数据库的数据进行操作,须要防止对之前的业务造成影响。 二、剖析流程应用 Redis 作为分布式锁,将锁的状态放到 Redis 对立保护,解决集群中单机 JVM 信息不互通的问题,规定操作程序,爱护用户的数据正确。梳理设计流程 新建注解 @interface,在注解里设定入参标记减少 AOP 切点,扫描特定注解建设 @Aspect 切面工作,注册 bean 和拦挡特定办法特定办法参数 ProceedingJoinPoint,对办法 pjp.proceed() 前后进行拦挡切点前进行加锁,工作执行后进行删除 key外围步骤:加锁、解锁和续时 加应用了 RedisTemplate 的 opsForValue.setIfAbsent 办法,判断是否有 key,设定一个随机数 UUID.random().toString,生成一个随机数作为 value。从 redis 中获取锁之后,对 key 设定 expire 生效工夫,到期后主动开释锁。依照这种设计,只有第一个胜利设定 Key 的申请,能力进行后续的数据操作,后续其它申请因为无奈取得资源,将会失败完结。 超时问题放心 pjp.proceed() 切点执行的办法太耗时,导致 Redis 中的 key 因为超时提前开释了。例如,线程 A 先获取锁,proceed 办法耗时,超过了锁超时工夫,到期开释了锁,这时另一个线程 B 胜利获取 Redis 锁,两个线程同时对同一批数据进行操作,导致数据不精确。 解决方案:减少一个「续时」工作不实现,锁不开释:保护了一个定时线程池 ScheduledExecutorService,每隔 2s 去扫描退出队列中的 Task,判断是否生效工夫是否快到了,公式为:【生效工夫】<= 【以后工夫】+【生效距离(三分之一超时)】 /** * 线程池,每个 JVM 应用一个线程去保护 keyAliveTime,定时执行 runnable */private static final ScheduledExecutorService SCHEDULER =new ScheduledThreadPoolExecutor(1,new BasicThreadFactory.Builder().namingPattern("redisLock-schedule-pool").daemon(true).build());static { SCHEDULER.scheduleAtFixedRate(() -> { // do something to extend time }, 0, 2, TimeUnit.SECONDS);}三、设计方案通过下面的剖析,共事小设计出了这个计划: ...

October 26, 2022 · 2 min · jiezi

关于springboot:SpringBoot请求参数解析原理

1.SpringMVC的执行流程2.SpringBoot申请参数解析原理 1.SpringMVC的执行流程上一篇博客SpringBoot申请映射原理,咱们讲过SpringMVC的执行流程,为了便于学习,每次在这一系列的博客开始前,我都会附上上面这张图。 2.SpringBoot申请参数解析原理通过上篇博客的学习,咱们曾经晓得了SpringBoot是如何通过申请门路找到对应的执行handler的,那么这篇博客,咱们将学习springBoot是如何将申请参数和handler的参数一一对应并执行指标办法的过程。 先说一句话概括论断:SpringBoot通过不同的注解,读取对应的参数并封装。 咱们在应用springBoot的时候,常常会在Controller办法上写上以下注解: @PathVariable@RequestParam@RequestBody...//等等类似的若干注解咱们以上面的controller办法为例,察看参数是如何封装的: @GetMapping("/test/{id}") public String test1(@PathVariable String id, @RequestParam String age) { System.out.println(id); System.out.println(age); return "ok!"; }咱们仍旧在DispatcherServlet的doDispatch办法上打上断点,打在上面这一行: 往里继续执行,咱们会发现一行重要的代码,看函数名字就能够通晓,这是执行handler的代码: 持续往里执行,咱们会看到很多类的初始化,(可不必具体浏览,抓住主流程即可),不过得记住一个重要的参数,参数解析器,等等会用来解析参数,咱们在图上曾经标出: 继续执行,咱们会发现一个重要办法invokeAndHandle: 咱们持续往里看: 能够得出,下面一行依据申请调用了handler的办法, 并且返回了一个参数,看来间隔咱们要找的代码越来越近了,咱们持续往里点击。 依据名字能够得出,咱们当初获取了所有参数args,并依据申请参数调用指标handler,咱们看看getMethodArgumentValues()办法是如何获取参数的,这就是咱们明天要阐明的重点: 咱们能够看到进行了如下三个步骤:1.springBoot先获取了handler上的所有形式参数2.查看handler上的参数是否反对解析3.若反对解析,则应用参数解析器进行解析。 this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);接下来咱们进入suopport办法,咱们handler的第一个参数应用的是@PathVariable注解标注:进入这个办法后,会遍历所有的参数解析器,找到反对以后参数的解析器,能够看到PathVariableMethodArgumentResolver很可能就是咱们想到的,咱们循环到这个值,并进入办法supportsParameter:能够看出办法很简略,就是查看这个参数是否标注了PathVariable.class注解,这里咱们返回true。(其它的参数解析也是同样的形式,能够自行debug) 参数解析器咱们曾经失去了,接下来咱们看看解析参数的流程: 这里的逻辑也非常简略,大略分成了以下三步:1.取得handler参数名字2.依据参数名字从request里拿到参数3.将这两个参数绑定在一起 其中,参数绑定器WebDataBinder中,有124个conversionService,名为参数转换器服务,能够将request中的字符串参数转换成以后handler参数须要的参数,如日期,整数,字符串等等。 通过这一系列的参数解析,咱们就将handler参数和申请参数一一对应了起来。 最初应用反射调用handler的办法。 总结:1.springBoot里有对应的参数解析器,用来解析Controller上用注解标注的不同参数。2.springBoot里有若干的参数转换器,用来将申请参数转换成实体类的参数类型。

October 25, 2022 · 1 min · jiezi

关于springboot:记一次springboot项目自定义HandlerMethodArgumentResolver不生效原因与解法

前言本文素材的起源自业务部门技术负责人一次代码走查引发的故事,技术负责人在某次走查成员的代码时,发现他们的业务管制层大量充斥着如下的代码 @PostMapping("add") public User add(@RequestBody User user, HttpServletRequest request){ String tenantId = request.getHeader("x-tenantid"); String appId = request.getHeader("x-appid"); user.setAppId(appId); user.setTenantId(tenantId); return user; }他们的tenantId和appId是作为元数据放在申请头,而业务model又须要tenantId和appId,于是他们团队的成员就写出了形如上的代码,尽管这样的代码是能满足业务要求,然而大面积如上的写法,都是重复性的代码,很不优雅。前面这个技术负责人项通过自定义HandlerMethodArgumentResolver的形式来优雅解决这问题,他的代码形如下 @Datapublic class MetaInfo { private String tenantId; private String appId; }public class MetaInfoHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { private RequestResponseBodyMethodProcessor handlerMethodArgumentResolver; public MetaInfoHandlerMethodArgumentResolver(RequestResponseBodyMethodProcessor handlerMethodArgumentResolver) { this.handlerMethodArgumentResolver = handlerMethodArgumentResolver; } @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class) && MetaInfo.class.isAssignableFrom(parameter.getParameterType()); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { MetaInfo metaInfo = (MetaInfo) handlerMethodArgumentResolver.resolveArgument(parameter,mavContainer,webRequest,binderFactory); HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); metaInfo.setAppId(request.getHeader("x-appid")); metaInfo.setTenantId(request.getHeader("x-tenantid")); return metaInfo; }}@Configurationpublic class WebConfig implements WebMvcConfigurer { @Autowired private MetaInfoHandlerMethodArgumentResolver metaInfoHandlerMethodArgumentResolver; @Bean @ConditionalOnMissingBean public MetaInfoHandlerMethodArgumentResolver metaInfoHandlerMethodArgumentResolver(List<HttpMessageConverter<?>> httpMessageConverters){ RequestResponseBodyMethodProcessor handlerMethodArgumentResolver = new RequestResponseBodyMethodProcessor(httpMessageConverters); return new MetaInfoHandlerMethodArgumentResolver(handlerMethodArgumentResolver); } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(metaInfoHandlerMethodArgumentResolver); }}当他写下如下代码时,按他的想法应该是没问题才对,然而事实上这个HandlerMethodArgumentResolver却无奈失效,他排查了很久,没啥脉络,于是就找我探讨了一下。本文就来聊一下该自定义HandlerMethodArgumentResolver不失效起因 ...

October 25, 2022 · 2 min · jiezi

关于springboot:SpringBoot请求映射原理

1.SpringMVC的执行流程2.SpringBoot申请映射原理 1.SpringMVC的执行流程在咱们刚开始学习springMVC的时候,咱们必定学过springMVC的执行流程: 咱们简述一下SpringMVC的执行流程: 1.客户端发送申请,申请被DispatcherServlet(中央处理器)捕捉。 2.DispatcherServlet对申请URL进行解析,取得资源标识符URI,依据URI调用HandlerMapping(处理器映射器)取得执行链(具体哪个Handler(Controller)执行该办法,以及该Handler的拦截器,参数转换器器等等),以HandlerExecutionChain对象返回给DispatcherServlet(中央处理器)。 3.DispatcherServlet依据取得的Handler,抉择对应的HandlerAdapter(如果胜利取得,就执行拦截器的preHandler(…)办法)。 4.HandlerAdapter对Request参数进行解析,并和Handlerr的参数进行绑定,调用反射执行Handlerr办法。 5.Handlerr执行结束当前,向Dispatcher返回一个ModelAndView对象。 6.依据返回的ModelAndView,抉择一个适合的ViewResolver返回给DispatcherServlet。 7.ViewResolver依据ModelAndView,渲染视图 8.返回后果 以往咱们通过这个流程只能死记硬背,不过咱们明天依据源码来剖析一下它的整体流程,并且着重剖析一下第2步(申请映射)。 2.SpringBoot申请映射原理用一句话解释:申请映射,就是通过拜访门路找到对应Controller的过程! 如何跟踪源码?咱们能够在Controller上打一个断点,并且跟踪断点的堆栈信息,就能够找到DispatcherServlet,并且找到对应的执行办法了。 而后dispatcherServlet的外围办法咱们就找到了doDispatch办法: 刨去一些预处理,查看,参数转换等逻辑,咱们能够看出,这执行逻辑和上图SpringMVC的执行逻辑截然不同。 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. // 依据门路找到Handler mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. //依据Handler找到 HandlerAdapter (处理器适配器) HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. //处理器适配器解决申请 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { // As of 4.3, we're processing Errors thrown from handler methods as well, // making them available for @ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException("Handler dispatch failed", err); } //解决handler的执行后果(视图解析) processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { // Instead of postHandle and afterCompletion if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { // Clean up any resources used by a multipart request. if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }这样咱们就对springMVC的执行逻辑有了一个hello Wrold级别的了解,接下来咱们着重解说一下通过申请是如何获取Handler的,也就是上面这一行,咱们能够在这一行下面打一个断点。 ...

October 24, 2022 · 2 min · jiezi

关于springboot:使用mybatisplus的多数据源方案aop不生效问题的排查

应用的是如下版本的mybatis-plus和多数据源,发现在service上加上@DS注解不会切换数据源,通过排查发现是aop没有失效 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency> <!-- 多数据源 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.3.3</version> </dependency>在DynamicDataSourceAutoConfiguration类里有定义一个bean @Role(value = BeanDefinition.ROLE_INFRASTRUCTURE) @Bean @ConditionalOnMissingBean public Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) { DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(properties.isAllowedPublicOnly(), dsProcessor); DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor); advisor.setOrder(properties.getOrder()); return advisor; }在这个配置类里定义的其余bean都有初始化,但唯独这个bean没有初始化,而这个类的作用就是定义aop的切面和切入点 我在本人的配置类里定义这个bean的话能够进行初始化,通过debug,其实次要起因就是应用了该@ConditionalOnMissingBean注解导致的,不晓得为啥dynamic-datasource-spring-boot-starter的作者要加上这个注解,在spring有加载其余的Advisor实现类的时候(我这边次要是有援用到spring的cache依赖,其中的配置类ProxyCachingConfiguration有定义一个BeanFactoryCacheOperationSourceAdvisor),所以该bean就不会加载。 为此,我顺便跑了一遍spring的@Bean加载流程,剖析如下: springboot启动的时候,其中有个回调接口BeanDefinitionRegistryPostProcessor的实现是ConfigurationClassPostProcessor,这个实现类的作用就是加载所有的@Configuration,会应用到ConfigurationClassParser类来进行解析,会解析是否有@ComponentScans()、@ComponentScan(@SpringApplication注解里也是应用到了这个注解)。 而后依据basePackage属性通过ComponentScanAnnotationParser#parse进行进一步的扫描,再应用ClassPathBeanDefinitionScanner#doScan,#findCandidateComponents扫描所有spring的component和config(扫描内容由ClassPathBeanDefinitionScanner的filter判断,这里会扫描所有@Component,@Configuation里也用到了@Component,所以都会扫描到) 而后会持续递归解析,最初失去所有的config(并且通过ConfigurationClassParser#retrieveBeanMethodMetadata获取config里所有的@Bean信息保留到BeanMethod属性)和component;接着应用ConfigurationClassBeanDefinitionReader#loadBeanDefinitions,读取加载的config的bean配置退出到BeanDefinition 这就是整个BeanDefinition的加载过程,能够看到,这加载过程其中是有先后顺序的,首先会加载以后SpringBootApplication的目录的配置,所以在下面那个问题我本人写一个配置@Bean的时候能够加载,因为这时候还没加载其余依赖包和配置,因而Advisor实现类就只有这个,所以加载能够胜利。加载实现之后才会加载classpath目录下的其余配置类。

October 21, 2022 · 1 min · jiezi

关于springboot:spring-boot集成mqtt协议发送和订阅数据

maven的pom.xml引入包 <!--mqtt--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> <version>2.3.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-stream</artifactId> <version>5.3.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>5.3.4.RELEASE</version> </dependency>mqtt.yml配置文件spring: mqtt: username: admin password: beyond_2021 url: tcp://192.168.3.100:1883 client-id: data-clientId server-id: data-serverId data-topic: data/# will-topic: data-will will-content: data server offline completion-timeout: 10000初始化MQTT配置beanpackage com.beyond.config;import org.eclipse.paho.client.mqttv3.MqttConnectOptions;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.event.EventListener;import org.springframework.integration.annotation.IntegrationComponentScan;import org.springframework.integration.annotation.ServiceActivator;import org.springframework.integration.channel.DirectChannel;import org.springframework.integration.core.MessageProducer;import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;import org.springframework.integration.mqtt.core.MqttPahoClientFactory;import org.springframework.integration.mqtt.event.MqttConnectionFailedEvent;import org.springframework.integration.mqtt.event.MqttMessageDeliveredEvent;import org.springframework.integration.mqtt.event.MqttMessageSentEvent;import org.springframework.integration.mqtt.event.MqttSubscribedEvent;import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;import org.springframework.integration.mqtt.support.MqttHeaders;import org.springframework.messaging.Message;import org.springframework.messaging.MessageChannel;import org.springframework.messaging.MessageHandler;import org.springframework.messaging.MessagingException;import java.security.SecureRandom;import java.util.Date;@Configuration@IntegrationComponentScanpublic class MqttConfig { private static final Logger log = LoggerFactory.getLogger(MqttConfig.class); @Value("${spring.mqtt.username}") private String username; @Value("${spring.mqtt.password}") private String password; @Value("${spring.mqtt.url}") private String hostUrl; @Value("${spring.mqtt.client-id}") private String clientId; @Value("${spring.mqtt.server-id}") private String serverId; @Value("${spring.mqtt.data-topic:data/#}") private String dataTopic; @Value("${spring.mqtt.will-topic}") private String willTopic; @Value("${spring.mqtt.will-content}") private String willContent; /** * @desc 连贯超时 */ @Value("${spring.mqtt.completion-timeout}") private int completionTimeout ; @Bean public MqttConnectOptions getMqttConnectOptions(){ // MQTT的连贯设置 MqttConnectOptions mqttConnectOptions = new MqttConnectOptions(); // 设置连贯的用户名 mqttConnectOptions.setUserName(username); // 设置连贯的明码 mqttConnectOptions.setPassword(password.toCharArray()); // 设置是否清空session,这里如果设置为false示意服务器会保留客户端的连贯记录, // 把配置里的 cleanSession 设为false,客户端掉线后 服务器端不会革除session, // 当重连后能够接管之前订阅主题的音讯。当客户端上线后会承受到它离线的这段时间的音讯 mqttConnectOptions.setCleanSession(true); // 设置公布端地址,多个用逗号分隔, 如:tcp://111:1883,tcp://222:1883 // 当第一个111连贯上后,222不会在连,如果111挂掉后,重试连111几次失败后,会主动去连贯222 mqttConnectOptions.setServerURIs(hostUrl.split(",")); // 设置会话心跳工夫 单位为秒 服务器会每隔1.5*20秒的工夫向客户端发送个音讯判断客户端是否在线,但这个办法并没有重连的机制 mqttConnectOptions.setKeepAliveInterval(20); mqttConnectOptions.setAutomaticReconnect(true); // 设置“遗嘱”音讯的话题,若客户端与服务器之间的连贯意外中断,服务器将公布客户端的“遗嘱”音讯。 mqttConnectOptions.setWill(willTopic, willContent.getBytes(), 2, false); mqttConnectOptions.setMaxInflight(1000000); return mqttConnectOptions; } @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); factory.setConnectionOptions(getMqttConnectOptions()); return factory; } /** * @desc 发送通道配置 默认主题 * @date 2021/3/16 */ @Bean @ServiceActivator(inputChannel = "mqttOutboundChannel") public MessageHandler mqttOutbound() { //clientId每个连贯必须惟一,否则,两个雷同的clientId互相挤掉线 String clientIdStr = clientId + new SecureRandom().nextInt(10); MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdStr, mqttClientFactory()); //async如果为true,则调用方不会阻塞。而是在发送音讯时期待传递确认。默认值为false(发送将阻塞,直到确认发送) messageHandler.setAsync(true); messageHandler.setAsyncEvents(true); messageHandler.setDefaultTopic(dataTopic); messageHandler.setDefaultQos(1); return messageHandler; } /** * @desc 发送通道 * @date 2021/3/16 */ @Bean public MessageChannel mqttOutboundChannel() { return new DirectChannel(); } /** * @desc 接管通道 * @date 2021/3/16 */ @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } /** * @desc 配置监听的 topic 反对通配符 * @date 2021/3/16 */ @Bean public MessageProducer inbound() { //clientId每个连贯必须惟一,否则,两个雷同的clientId互相挤掉线 String serverIdStr = serverId + new SecureRandom().nextInt(10); MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(serverIdStr, mqttClientFactory(), dataTopic); adapter.setCompletionTimeout(completionTimeout); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(1); adapter.setOutputChannel(mqttInputChannel()); return adapter; } /** * @desc 通过通道获取数据 订阅的数据 * @date 2021/3/16 */ @Bean @ServiceActivator(inputChannel = "mqttInputChannel") public MessageHandler handler() { return new MessageHandler() { @Override public void handleMessage(Message<?> message) throws MessagingException { String payload = message.getPayload().toString(); String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString(); //////////////////解决订阅topic:(data/#)到的所有的数据 } }; } /** * @desc mqtt连贯失败或者订阅失败时,触发MqttConnectionFailedEvent事件 * @date 2021/7/22 *@param event * @return void */ @EventListener(MqttConnectionFailedEvent.class) public void mqttConnectionFailedEvent(MqttConnectionFailedEvent event) { log.error("mqttConnectionFailedEvent连贯mqtt失败: " + "date={}, hostUrl={}, username={}, error={}", new Date(), hostUrl, username, event.getCause().getMessage()); } /** * @desc 当async和async事件(async-events)都为true时,将收回MqttMessageSentEvent * 它蕴含音讯、主题、客户端库生成的音讯id、clientId和clientInstance(每次连贯客户端时递增) * @date 2021/7/22 *@param event * @return void */ @EventListener(MqttMessageSentEvent.class) public void mqttMessageSentEvent(MqttMessageSentEvent event) { log.info("mqttMessageSentEvent发送信息: date={}, info={}", new Date(), event.toString()); } /** * @desc 当async和async事件(async-events)都为true时,将收回MqttMessageDeliveredEvent * 当客户端库确认传递时,将收回MqttMessageDeliveredEvent。它蕴含messageId、clientId和clientInstance,使传递与发送相干。 * @date 2021/7/22 *@param event * @return void */ @EventListener(MqttMessageDeliveredEvent.class) public void mqttMessageDeliveredEvent(MqttMessageDeliveredEvent event) { log.info("mqttMessageDeliveredEvent发送胜利信息: date={}, info={}", new Date(), event.toString()); } /** * @desc 胜利订阅到主题,MqttSubscribedEvent事件就会被触发(多个主题,屡次触发) * @date 2021/7/22 *@param event * @return void */ @EventListener(MqttSubscribedEvent.class) public void mqttSubscribedEvent(MqttSubscribedEvent event) { log.info("mqttSubscribedEvent订阅胜利信息: date={}, info={}", new Date(), event.toString()); }}mqtt发送数据网关配置package com.beyond.data.component;import org.springframework.integration.annotation.MessagingGateway;import org.springframework.integration.mqtt.support.MqttHeaders;import org.springframework.messaging.handler.annotation.Header;import org.springframework.stereotype.Component;/** * @desc MQTT发送网关 * @date 2021/3/12 */@Component@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")public interface MqttGatewayComponent { void sendToMqtt(String data); void sendToMqtt(String payload, @Header(MqttHeaders.TOPIC) String topic); void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);}发送数据到mqtt伪代码@Autowiredprivate MqttGatewayComponent mqttGatewayComponent;//发送字符串或json字符串,到指定的topicmqttGatewayComponent.sendToMqtt("json string", "data/abcd");参考链接:https://blog.csdn.net/sinat_2...https://blog.csdn.net/qq_2946...https://blog.csdn.net/myinser... ...

October 19, 2022 · 3 min · jiezi

关于springboot:SpringBoot使用异步EnableAsyncAsync

Spring boot通过@EnableAsync、@Async配合来实现异步调用的。 举一个理发店的例子吧,比方3位理发师,5位顾客来理发。 上面上代码 通过@EnableAsync、@Configuration配置一个默认的线程池,充当理发师CorePoolSize(3);即3位理发师 import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;import java.util.concurrent.ThreadPoolExecutor;@EnableAsync@Configurationpublic class ThreadPoolConfig { @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME) public Executor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.setMaxPoolSize(3); executor.setQueueCapacity(10); executor.setKeepAliveSeconds(60); executor.setThreadGroupName("理发师-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; }}理发业务,haircut办法下面增加 @Async(value = "taskExecutor")示意办法异步,异步的办法不能被以后类的办法相互调用,在同一个类外部调用一个异步办法,不会触发异步 import lombok.extern.slf4j.Slf4j;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Service;import java.util.concurrent.CountDownLatch;@Service@Slf4jpublic class DemoServiceImpl { @Async(value = "taskExecutor") public void haircut(CountDownLatch countDownLatch, String user) { log.info("用户:{},开始理发", user); try { //模仿耗时 Thread.sleep(5000L); } catch (InterruptedException e) { } log.info("用户:{},理发实现", user); countDownLatch.countDown(); }}写一个单侧,营业开始,5为顾客来理发,都理发实现,营业完结 ...

October 19, 2022 · 1 min · jiezi

关于springboot:SpringBoot自动配置原理

1.SpringBoot主动配置简介2.SpringBoot主动配置是如何实现的 1.SpringBoot主动配置简介个别状况下,咱们在学习springBoot之前,都会先学习spring和spring MVC,咱们须要手动配置十分多的类,比方注解扫描器,dispatcherServlet等等。然而到咱们学习了SpringBoot当前,发现springBoot是开箱即用的,不须要任何配置,就一个main办法,就能够帮咱们把包扫描进来,且配置好很多的组件,整合其它框架也十分不便。 2.SpringBoot主动配置是如何实现的2.1 @SpringBootApplication当咱们创立一个springBoot我的项目的时候,就会有一个主类,咱们会发现有一个注解@SpringBootApplication @SpringBootApplicationpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}@SpringBootApplication标记的类代表这个类是SpringBoot的主配置类,启动@SpringBootApplication标注的类就能够启动该容器。 咱们往这个注解里看,会发现这个注解是由一系列注解组成的: @Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })public @interface SpringBootApplication {}其中最重要的三个注解是(其余注解是用来润饰注解的原生注解,此处不做论述): @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan接下来咱们围绕这三个注解开展解析,就能够明确SpringBoot主动配置原理了。 2.2 @SpringBootConfiguration@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Configuration@Indexedpublic @interface SpringBootConfiguration { @AliasFor(annotation = Configuration.class) boolean proxyBeanMethods() default true;}此注解非常容易,其成果等同于@Configuration,代表了此类是一个配置类,会被注入到spring容器中。 2.3 @ComponentScan它是一个包扫描注解,能够输出basePackages包名来示意被扫描的包的门路,然而这里并没有这样应用。 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })在@SpringBootApplication注解中,type = FilterType.CUSTOM 示意依照自定义形式排除组件classes = TypeExcludeFilter.class 具体的排除形式实现类 ...

October 18, 2022 · 2 min · jiezi

关于springboot:springBoot-配置拦截器

springBoot 配置拦截器如何配置拦截器拦截器设置容易呈现的问题如何勾销拦挡操作实例:登录验证 如何配置拦截器step1: 自定义拦截器/** * 自定义拦截器 */public class MyInterceptor implements HandlerInterceptor { private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class); /** * 在申请匹配controller之前执行,返回true才行进行下一步 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return false; } /** * 曾经执行完controller了,然而还没有进入视图渲染 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /** * 视图也渲染完了,此时我能够做一些清理工作了 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}step2:配置拦截器@Configurationpublic class MyInterceptorConfig extends WebMvcConfigurationSupport { @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**"); // 拦挡所有内容:/** 拦挡局部内容:/admin/** super.addInterceptors(registry); }} 拦截器设置容易呈现的问题动态资源被拦挡MyInterceptorConfig 继承 WebMvcConfigurationSupport类时,会导致resources/static下的动态资源也被拦挡,如果咱们不想动态资源被拦挡,能够尝试以下两种办法。 ...

October 18, 2022 · 3 min · jiezi

关于springboot:LoopAuth-2X版本发布这可能是你第一次使用ABAC鉴权

LoopAuth 公布曾经有些时日了,在LoopAuth 1.X版本中,始终应用Rbac的鉴权形式。 LoopAuth 这个我的项目在创立初期,就曾经思考退出ABAC的鉴权模式。 9月份摆烂一个月后,LoopAuth 2.X版本当初已公布: 重构实现,退出ABAC鉴权模式,将原有外围模块拆分为Session、RBAC、ABAC三块。 相干链接GiteeGitHub官网文档ABAC初体验示例我的项目增加依赖${version}请查看版本历史,请应用最新正式版,且版本与其余拓展最好保持一致<!-- LoopAuth的Springboot插件 --><dependency> <groupId>com.sobercoding</groupId> <artifactId>LoopAuth-spring-boot-starter</artifactId> <version>${version}</version></dependency>实现AbacInterface接口LoopAuthHttpMode为申请类型的枚举,包含GET、PUT、POST或ALL等等所有常见的申请类型public class AbacInterFaceImpl implements AbacInterface { /** * 获取一个或多个路由/权限代码所属的 规定 * @param route 路由 * @param loopAuthHttpMode 申请形式 * @return 去重后的汇合 */ @Override public Set<Policy> getPolicySet(String route, LoopAuthHttpMode loopAuthHttpMode) { // 这里只做演示,自行编写的时候,请依据本人存储abac规定的形式查问获取 Set<Policy> set = new HashSet<>(); // 依据路由地址及申请形式查问 插入 if (route.equals("/test/abac") && loopAuthHttpMode.equals(LoopAuthHttpMode.GET)){ set.add(new Policy() // 规定名称 .setName("test") // 规定中的属性名称 及 属性值 用于后续进行 规定匹配校验 .setProperty("loginId", "2") ); } return set; }}主动注入在AbacInterface的实现类上加上@Component注解即可@Componentpublic class AbacInterFaceImpl implements AbacInterface { ...}手动注入保障我的项目启动时执行上面语句即可AbacStrategy.setAbacInterface(new PermissionInterfaceImpl());初始化ABAC鉴权规定须要保障我的项目启动时 执行以下代码以下代码以匹配loginId为例请依据本人需要更改AbacStrategy.abacPoAndSuMap = new AbacPolicyFunBuilder() // 自定义登录id校验的鉴权规定 .setPolicyFun("loginId", // 创立规定校验及获取以后值的形式 new AbacPoAndSu() // 创立校验形式 value为以后值即setSupplierMap提供的值 // rule为规定的值即 Policy setProperty 的值 .setMaFunction((value, rule) -> { // 以后用户id须要与规定匹配才可拜访 否则 抛出异样 if (!value.equals(rule)){ throw new LoopAuthPermissionException(LoopAuthExceptionEnum.NO_PERMISSION); } }) // 取得value形式 .setSupplierMap(() -> "2") ).build();注入拦截器@Componentpublic class LoopAuthMvcConfigure implements WebMvcConfigurer { /** * 注册LoopAuth 的拦截器,关上注解式鉴权性能 */ @Override public void addInterceptors(InterceptorRegistry registry) { // abac拦截器 registry.addInterceptor(new InterceptorBuilder().Abac().builder()).addPathPatterns("/**"); }}创立Controller测试一下能够更改setSupplierMap()中的返回值、或申请类型了解 @GetMapping("/test/abac") public String abac1(){ return "检测胜利"; }

October 17, 2022 · 1 min · jiezi

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

download:SpringBoot+Vue3 我的项目实战,打造企业级在线办公零碎圣安地列斯颤振动画剖析动画定义晚期的动画片应用大量的画面疾速切换,以达到看似间断的动画成果。这是最早的帧动画,是人类视觉提早产生的间断成果。其实这也是当初动画的原理。屏幕有法则地同时渲染屡次。渲染的次数越多,动画就会越晦涩,也就是咱们通常所说的屏幕刷新率。目标让大家在应用颤振动画的过程中得心应手,不须要剖析具体的源代码。动画的要害属性:动画持续时间和动画轨迹。实际上,动画是物体在指定工夫内特定的规律性静止的体现。所以咱们须要关怀的外围属性是动画时长和动画轨迹。理解动画实现的原理,所谓“万物变动”和颤振动画是一样的。让咱们先来看看在Flutter中应用的动画的几个要害类。动画外围类:动画控制器动画控制器用于设置动画的持续时间、动画的开始和完结值,以及管制动画的开始和完结。办法和罕用办法:_controller =动画控制器(Vsync: this,//设置跑马灯动画帧的回调函数Duration: Const Duration(毫秒:2000),//正动画的持续时间//2s反向持续时间:常量持续时间(百万:2000),//反向动画持续时间//2sLowerBound: 0,//开始动画数值double类型UpperBound: 1.0,// end动画数值double类型animation behavior:animation behavior . normal,//动画师行为是否反复动画的两个枚举值?DebugLabel:“缩放动画”,//动画过多时调试标签动画不便,toString时显示。//_ controller . tostring;//输入:动画控制器# 9d 121(0.000;用于缩放动画)补间(0.0 → 1.0) 0.0);//罕用办法://监控动画静止_ controller . add listener((){ });//监控动画开始、进行等。_controller.addStatusListener((状态){//遣散动画停在终点。//正向动画正在正向执行。//反向动画正在反向执行。//实现的动画在起点进行if(status = = animation status . completed){_ controller . reverse();//反向执行100-0} else if(status = = animation status . discarded){_ controller . forward();//正向执行0-100}});//开始播放动画//_ controller . forward();//正向启动动画//_ controller . reverse();//反向播放动画_ controller . repeat();//有限循环开始动画 复制代码vsync参数须要类别混合:SingleTickerProviderStateMixin或TickerProviderStateMixin,如果页面中只有一个动画控制器应用第一个,多个控制器应用第二个。AnimatedBuilder实现动画组件的外围类总的来说,咱们须要在须要动画成果的组件上包裹一层AnimatedBuilder来监控动画控制器的更新数据,外部的实现也是通过有状态组件监控一直刷新页面来实现的。施工办法:常量动画生成器({钥匙?钥匙,必须的Listenable动画,///动画控制器必选this.builder,//返回动画This.child,//传递给build的子组件})复制代码以上两个组件能够实现简略的动画成果。让咱们应用AnimationController和AnimatedBuilder来实现一个简略的缩放动画。使FlutterLogo组件大小一直变动。//关上动画_ controller . repeat();//有限循环开始动画 核心(child: AnimatedBuilder(child: FlutterLogo(),动画:_控制器,构建器:(上下文,子级){返回容器(宽度:100 * _controller.value,高度:100 * _controller.value,孩子:孩子,);}),)

October 16, 2022 · 1 min · jiezi

关于springboot:83springboot-多模块打包成jar

主我的项目: <packaging>pom</packaging>打包配置: <!--指定应用maven打包--> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.19.1</version> <configuration> <skipTests>true</skipTests> <!--默认关掉单元测试 --> </configuration> </plugin> </plugins> </build>子项目,boot入口模块: <packaging>jar</packaging><parent> <groupId>com.kuma.platform</groupId> <artifactId>kuma-boot</artifactId> <version>1.0.0</version> <relativePath>../pom.xml</relativePath> </parent> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <!-- 指定该Main Class为全局的惟一入口 --> <mainClass>com.kuma.platform.MainApplication</mainClass> <layout>ZIP</layout> <!--fork:如果没有该项配置,整个devtools不会起作用--> <fork>true</fork> </configuration> <executions> <execution> <goals> <goal>repackage</goal><!--能够把依赖的包都打包到生成的Jar包中--> </goals> </execution> </executions> </plugin> </plugins> </build>其余子模块: <packaging>jar</packaging> <parent> //.... <version>1.0.0</version> <relativePath>../pom.xml</relativePath> </parent>

October 16, 2022 · 1 min · jiezi

关于springboot:初步对SSM框架中Dao层Mapper层service等层的理解

    大家好啊,我是司空,最近在工作空闲之余正在学springBoot,学到了对于mybatis的配置,外面波及到几个不同层之间的应用让我有点摸不着头脑,没法,公司用的还是十年前的老框架,对于当初这些框架真没啥理解,不过MVC机制是没有变了,我也就联合我所学的内容和工作中的理论教训,谈一谈我对这几个层之间的理解吧。 根本理解 上图用的是我整顿思路的时的草图,不具备专业性,大家别当真了哈,看看思路就好dao层:用于定义操作数据库的接口办法,须要怎么调数据库就定义什么办法在这 mapper层:用于间接对数据库进行操作,sql语句就写这 service层:用于定义业务实现的接口办法,须要实现什么业务就定义什么办法在这 serviceImpl层:用于实现业务接口,能够操作治理dao层获取想要的数据 其余几个层:如controller,view,model,entity就简略过一过,别离负责数据的解决,展现,存储和封装。就不具体说了,我新学的次要是下面四个。 一些思考 依据上图咱们晓得,其实对于实现一个性能来说,只须要model,view,controller层就行了,那为啥还要在model到controller之间插入dao层和service层呢? 让咱们带着这个问题,去看一下应用ssm框架实现一个view渲染的事实门路映射吧。 上图用的是我整顿思路的时的草图,不具备专业性,大家别当真了哈,看看大体门路就好能够看到啊,view层和controller层,controller层和service层,serivce层和dao层,都是多对多的关系。而service层和serviceImpl层,dao层和mapper层,mapper层和model层则是一对一的关系。 问题就出在这对应关系上! 试想一下,这只是一个view,就可能调用多个controller去获取数据,那两个view呢,一堆view呢。如果绕开service层和dao层以及mapper层,间接让controller层与model层进行交互,那很显著会呈现一个问题,代码的大量反复以及耦合性强。如view1须要model1的userId,view2也须要,假使绕开三层间接让controller层与model层进行交互,那么view1须要与model1建设一次连贯,取一次userId。view2也须要与model1建设一次连贯取一次userId,这是很蠢的行为。 一个办法,如果须要写三次以上,就应该封装起来,更别提调数据库这种如此频繁的行为。再说,如果数据库表受到更改,难道你去每个controller外面改一次sql语句吗? 这就引出了咱们第一个问题的答案:为啥还要在model到controller之间插入dao层和service层? 目标就是为理解耦,进步代码的开发效率。这也就是方才咱们提到的多对多关系的由来。 仔细的敌人可能发现了,方才我不仅说到了多对多的关系,还存在着一对一的关系。那么这个一对一的关系又是为了什么呢? 实际上,如果咱们把service层与dao层合起来,在面对一些小型业务量的场景时,是齐全没有问题的。起初我也搞不懂,为啥要用service的接口办法调用另外一个接口的办法去实现。可当业务量下来后,才发现这是一个如许理智的抉择。 我认为建设service层和dao层最间接的益处就是繁多职责化,这也是SOLID准则中的繁多职责准则(Single Responsiblity Principle),十分经典的体现,service只用思考业务如何实现,不思考数据如何获取。dao层和mapper只用思考数据如何获取,不必思考数据要被拿去干什么。在多人合作开发与业务高复杂度场景中这种思维非常好用。 一些疑难:看见网上一些博客,都说dao层个别是对应着某个表。我集体并不认同,一是表太多创立太多接口类难以治理。二是如果遇到那些多表联查的场景时,该把这些接口办法放到哪个类外面呢? 我认为较好的就是将dao层接口类与库绝对应,一个库对应一个dao层接口类,而后把一些比拟重要的表或者几张表独自建设dao层接口类,如用户信息,平安登录表等等。能够在方便管理的状况下又正当地爱护某些重要数据。所有有人晓得为什么dao层接口类要与表绝对应嘛?有的话请在评论区通知我哦。 以上就是我对这几个层的一些肤浅了解,如果有谬误请大家多多斧正,咱们独特学习,共同进步。peace&love❥(^_-)

October 14, 2022 · 1 min · jiezi