二言目 - Spring Boot
type: channel
link: https://blog.piemoe.com/tag/Spring-Boot/
items: 使用SpringCloud Alibaba结合Springboot3.0 搭建一个demo
items: 通过注解或Lambda方式进行数据脱敏
items: Springboot通过注解实现敏感词过滤
items: 使用Lock4j完成一个分布式锁和幂等的Spring Boot Starter组件
items: Spring Boot 项目打包Docker镜像并push到云端镜像仓库
items: 自制Redis的Sringboot Starter

使用SpringCloud Alibaba结合Springboot3.0 搭建一个demo
type: item
date: 2023-02-17T12:51:00+08:00
description: Springboot 3 出来也有一段时间了，Springcloud alibaba也在适配当中，本demo使用Springboot 3 + Spingcloud alibaba + nacos等组件，完成一个项目工程的简单搭建组件Nacos : 2.0.3Maven：3.0.8依赖版本springboot :3.0.4springcloud:2022.0.1springcloud alibaba :2.2.10-RC1spring-cloud-starter-alibaba-nacos-discovery:2022.0.0.0-RC1spring-cloud-starter-alibaba-nacos-config：2022.0.0.0-RC1springcloud alibaba 2.2.10-RC1 版本提供的Nacos依赖，经测试无法成功注册，故使用2022.0.0.0-RC1版本项目POM<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.4</version> <relativePath/> </parent> <groupId>com.piemoe</groupId> <artifactId>springboot3</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot3</name> <description>springboot3</description> <properties> <java.version>17</java.version> </properties> ​ <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2022.0.1</version> <type>pom</type> <scope>import</scope> </dependency> ​ <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.10-RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> ​ <dependencies> ​ <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dep…

通过注解或Lambda方式进行数据脱敏
type: item
date: 2022-11-10T10:51:00+08:00
description: 前言数据脱敏是一种数据保护技术，它可以对敏感数据进行处理，使其失去原始意义，从而保护数据隐私。通俗地说，就是将原始数据中敏感信息的部分替换或删除，例如：手机号码中的一部分数字替换为星号，或是姓名中的一些字母用其他字母代替等等。数据脱敏可以保护个人隐私，防止个人信息被泄露、滥用，同时也符合现代法律法规对个人信息保护的要求，是网络安全和信息保护中不可或缺的一环。本文将通过使用注解完成对字段的自定义脱敏功能。正文本文将介绍2种脱敏的方式，分别为：注解方式脱敏和lambda方式脱敏注解脱敏定义脱敏注解@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = SensitiveSerialize.class) public @interface Dm { /** * 脱敏信息类型 */ DmTypeEnum type(); /** * 前置不需要打码的长度 * 只有 {@link DmTypeEnum} 为 {@link DmTypeEnum#CUSTOMER} 时可用 */ int prefixNoMaskLen() default 0; /** * 后置不需要打码的长度 * 只有 {@link DmTypeEnum} 为 {@link DmTypeEnum#CUSTOMER} 时可用 */ int suffixNoMaskLen() default 0; /** * 用什么打码 * 只有 {@link DmTypeEnum} 为 {@link DmTypeEnum#CUSTOMER} 时可用 */ char symbol() default '*'; }type()方法为可以支持实现的脱敏信息类型，通过使用hutool工具，可以看到它支持以下枚举，故拉取出来形成类型枚举：public enum DmTypeEnum { /** * 用户自定义 */ CUSTOMER, /** * 用户id */ BACK_ID, /** * 中文名 */ CHINESE_NAME, /** * 身份证号 */ ID_CARD, /** * 座机号 */ FIXED_PHONE, /** * 手机号 */ MOBILE_PHONE, /** * 地址 */ ADDRESS, /** * 电子邮件 */ EMAIL, /** * 密码 */ PASSWORD, /** * 中国大陆车牌，包含普通车辆、新能源车辆 */ CAR_LICENSE, /** * 银行卡 */ BANK_CARD }关键语句在@JsonSerialize(using = SensitiveSerialize.class)上，@JsonSerialize 是一个注解，它可以用来指定 Java 对象序列化成 JSON 格式时的序列化规则。具体而言，它可以指定序列化时属性名的命名方式、日期格式化等等。它的作用是使得 Java 对象可以方便地转化为 JSON 格式，这对于 Web 开发、移动应用开发等场景非常有用，故此我们在这里自定义一个实现，并在注解中引用。@NoArgsConstructor @AllArgsConstructor @Slf4j public class SensitiveSerialize<T> extends JsonSerializer<T> implements ContextualSerializer { /** * 脱敏类型 */ private DmTypeEnum sensitiveTypeEnum; /** * 前几位不脱敏 */ private Integer prefixNoMaskLen; /** * 最后几位不脱敏 */ private Integer suffixNoMaskLen; /** * 打码字符 */ private char symbol; @Override public void serialize(final T value, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException { Object desensitized = DmUtils.desensitized(value.toString(), sensitiveTypeEnum, prefixNoMaskLen, suffixNoMaskLen, symbol); if (desensitized instanceof String) { jsonGene…

Springboot通过注解实现敏感词过滤
type: item
date: 2022-05-11T15:59:00+08:00
description: 前言项目用到了敏感词过滤（数据清洗），之前是通过查询敏感词库进行模糊匹配然后过滤的，但这样的查表的效率很低，且跨微服务调用时时间损耗会更高，故此记录改造正文首先想到的是将敏感词从库表里放入redis缓存，这样可以有效避免重复查库，其次通过redis可以较少微服务调用，直接从redis缓存使用。但是一段语句中，是否存在敏感词，直接匹配redis缓存中的敏感词库的话，效率依旧不高，故此我们可以引入知名的DFA算法。DFA算法介绍DFA(确定有限状态自动机) 算法是一种用于解决文本匹配问题的算法。它通过比较输入文本和预定义的状态机的状态转换路径，来判断是否存在匹配。DFA 通常用于字符串匹配。它利用预先创建好的有限状态自动机，自动机包含一个有限的状态集合和一个输入符号集合，自动机从一个状态转移到另一个状态，根据输入符号，自动机判断出输入符号是否与预定义的字符串匹配。 其主要思想是将匹配过程建模为状态机，并使用状态机的转移过程进行匹配。下面是一些使用 DFA 的例子：字符串匹配：在一个输入文本中查找一个特定的字符串是否存在。如果它存在，就返回它在文本中的位置。如果不存在，则返回-1。词法分析：将输入的字符流识别成包含令牌、标记或关键字的单词流。例如，在编译器中，它用于将代码解析成令牌流。自然语言处理：用于命名实体识别等自然语言处理任务。在这些任务中，它可以使用 DFA 对文本进行分词，并构建一个有限状态自动机，以识别关键字或语法结构。过滤器：用于过滤输入文本中的某些内容。在这些任务中，它可以使用 DFA 对文本进行匹配和替换，以过滤或修改输入文本中的内容。更多的介绍可以看这篇文章：基于DFA敏感词查询的算法简析 - 李晓晖 - 博客园 (cnblogs.com)DFA实现站在巨人的肩膀上才能看的更高，故此直接使用hutools工具的DFA算法！DFA查找 (hutool.cn)敏感词过滤实现本人根据自己日常编码的使用需求，大致分为2种使用场景：方法内过滤这个很好理解，就是方法体内进行过滤，这个直接使用hutool提供的方法结合redis使用即可，不再赘述。方法调用时过滤作为入参参数过滤，即方法调用时过滤根据2个场景又可以分成2种风格：遇到敏感词即禁止（截止模式）替换敏感词为其他符号（置换模式）敏感词首先定义一个敏感词库，使用hash的方式将其存入redis，这部分实现很简单，不再赘述。截止模式此模式为遇到敏感词即报错提示，通过统一异常管理可以快捷通知用户内容包含敏感词首先定义一个注解：/** * 自定义敏感词过滤注解 * 若包含敏感词，则返回false 不替换敏感词 效率更高 * @author mc */ @Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = SwfValidator.class) public @interface SwfParam { String message() default "内容含有敏感词汇，请检查！"; }这个注解可以在方法、字段、构造器和入参字段字段使用，message为报错的信息，@Constraint(validatedBy = SwfValidator.class)为检验方法，是具体的过滤方法。/** * 自定义敏感词校验注解实现 * * @author mc */ public class SwfValidator implements ConstraintValidator<SwfParam, String> { @Resource ISwfService swfService; @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { return containsKeyWord(value); } public boolean containsKeyWord(String value) { WordTree sensitiveWordTree = new WordTree(); Set<String> sensitiveWordSet = swfService.getRemoteSensitiveWordMap(true); sensitiveWordTree.addWords(sensitiveWordSet); String ma…

使用Lock4j完成一个分布式锁和幂等的Spring Boot Starter组件
type: item
date: 2022-04-24T12:35:00+08:00
description: 前言分布式锁​ 分布式锁是在分布式系统中保证同步和协调的一种机制。它通常用于协调多个进程或节点之间的访问和资源使用，以防止并发冲突和数据损坏。​ 分布式锁的实现通常采用一种称为互斥体的同步原语。互斥体是一种锁定机制，可以用来确保一个进程或节点在任何时候都具有对某个资源的独占访问权限。在分布式环境中，互斥体必须经过适当的设计和实现，以确保它能够正确地工作和协调不同的节点和进程。​ 常见的分布式锁实现方式包括：基于Redis的分布式锁、ZooKeeper的分布式锁、数据库乐观锁等。这些实现方式中，Redis和ZooKeeper常常被使用在大规模的分布式系统中，因为它们提供了可靠的数据存储和高效的访问控制机制。而数据库乐观锁则主要用于小规模分布式系统中，因为它比较简单直接，但是更加依赖于网络的性能。幂等​ 幂等是指对同一操作，多次执行所产生的影响与执行一次的影响相同。也就是说，即使同一操作被执行多次，也不会对系统产生额外的影响。在计算机科学中，幂等性是一种非常重要的概念，常常用于设计和实现高可靠性、可复用性、可扩展性的分布式系统和网络服务。常见的幂等操作包括查询数据库、修改状态、发送邮件等。选型[scode type='info']本次使用lock4j+redisson实现分布式锁和幂等组件，其中，redisson的版本不应该过高，要和Spring Boot版本对应，否则无法正常启动。[/scode]Spring Boot：2.6lock4j: 2.2.3官方仓库：lock4j: 基于Spring AOP 的声明式和编程式分布式锁，支持RedisTemplate、Redisson、Zookeeper (gitee.com)正文首先搭建一个Spring Boot 2.6.3 的项目，搭建过程省略。引入lock4j+redisson依赖<dependency> <groupId>com.baomidou</groupId> <artifactId>lock4j-redisson-spring-boot-starter</artifactId> <version>2.2.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>分布式锁首先对lock4j进行一些配置，默认的lock4j在缓存中是以lock4j为前缀的key，但是为了保持项目的一致（比如某个微服务就以它的项目名称为开头）就需要修改其默认的key前缀。@AutoConfigureBefore(LockAutoConfiguration.class) public class MyLock4jConfig { // 引入默认的配置 private final Lock4jProperties properties; // 修改lock4j前缀 自定义默认使用key 项目名称:lock4j:*** public MyLock4jConfig(Lock4jProperties properties, ApplicationName applicationName) { properties.setLockKeyPrefix(applicationName.getServiceName() + ":lock4j"); this.properties = properties; } @Bean @ConditionalOnMissingBean @SuppressWarnings("all") // 这里加入 @ConditionalOnMissingBean注解，目的是为了项目代码里可以灵活重写 // @SuppressWarnings("all")可以抑制idea的警告 public LockTemplate lockTemplate(List<LockExecutor> executors) { LockTemplate lockTemplate = new LockTemplate(); lockTemplate.setProperties(properties); lockTemplate.setExecutors(executors); return lockTemplate; } // 默认的锁失败策略 这里进行自定义 @Bean public DefaultLockFailureStrategy lockFailureStrategy() { return new D…

Spring Boot 项目打包Docker镜像并push到云端镜像仓库
type: item
date: 2022-03-02T08:56:00+08:00
description: 前言首先来了解下什么是Dockerfile：Dockerfile是一个包含命令和指令的脚本文件，用于构建Docker镜像。它包含了构建镜像的全部指令，可以使得从头开始构建一个完整的Docker镜像变得十分容易。以下是Dockerfile的使用方法：创建Dockerfile文件：在项目目录中使用文本编辑器创建Dockerfile文件并编写相应的指令。编写Dockerfile指令：使用Dockerfile指令来构建镜像，例如FROM、RUN、COPY、CMD等等。构建镜像：使用docker build命令构建镜像。例如：docker build -t imagename .运行容器：使用docker run命令运行容器。例如：docker run -d -p 8080:8080 imagename。打包和分享镜像：可以使用docker save和docker load命令来打包和分享镜像。以上是对dockerfile的一般理解和使用方法。前提一个正常可打包的Spring Bootweb项目一个可用的云端镜像仓库一个可以本地连接使用的docker正文创建Dockerfile首先在项目的根目录里创建Dockerfile文件，通过这个文件我们可以将我们的jar包打包进Docker镜像，随着镜像启动的同时，将我们的jar包也启动# 该镜像需要依赖的基础镜像 FROM openjdk:8u322-oracle # 将当前目录下的jar包复制到docker容器的/目录下 ADD target/my-demo.jar /my-demo.jar # 运行过程中创建一个my-demo.jar文件 RUN bash -c 'touch /my-demo.jar' # 声明服务运行在10316端口 EXPOSE 10318 # 指定docker容器启动时运行jar包 ENTRYPOINT ["java","-jar","/my-demo.jar","-Dfile.encoding=utf-8"] # 指定维护者的名字 MAINTAINER xinwei以上的操作是使用jdk8版本的程序，如果使用其他如jdk17版本，可以直接改变jdk的镜像版本即可。这样我们就可以利用命令docker build -t my-demo .将程序打包在本地里，随着容器启动，容器内的jar包也会启动。但是这只是在我们的本地打包成docker镜像，如果手动迁移镜像到云服务器上，或者项目运行的服务器和镜像服务器不是同一台机器，手动操作就很繁琐了，故此继续改造maven，使得maven可以直接打包成镜像并上传到仓库。改造Maven首先介绍一个打包docker镜像的maven插件——Docker Maven Plugin，这时一个旨在为Java应用程序的开发人员提供方便和高效的Docker容器化过程的插，它允许Maven用户使用标准Maven构建方式来创建和构建Docker镜像，并支持将配置文件和资源文件打包到Docker镜像中，以便于部署和运行Java应用程序。Docker Maven Plugin提供了一系列目标和插件，包括：docker:build：用于构建Docker镜像的目标。docker:start和docker:stop：用于启动和停止Docker容器的目标。docker:push：用于将Docker镜像推送到远程仓库的目标。dockerfile:build：使用自定义Dockerfile文件构建Docker镜像的目标。dockerfile:tag：用于将Docker镜像标记为指定的标签的目标。dockerfile:push：将Docker镜像推送到指定的Docker仓库的目标。Docker Maven Plugin还提供了一些其他功能，如能够自动化地在Docker容器中运行Java应用程序，并提供了一些命令行参数和配置选项，以帮助开发人员定制和管理Docker镜像和容器。github地址：fabric8io/docker-maven-plugin: Maven plugin for running and creating Docker images (github.com)将其改造在pom文件里： <build> <finalName>${project.artifactId}</finalName> <resources> <resource> <directory>src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <ar…

自制Redis的Sringboot Starter
type: item
date: 2022-01-18T09:25:00+08:00
description: 前言springcloud项目通常会遇到以下2个问题：配置项目redis组件时，有时会遇到一些序列化的问题，如当使用jackson的时，使用@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)注解，会报这个实体不是一个数组的问题集成通用redis依赖组件，可以让所有的项目共同依赖默认一个redis过期时间，更好的利用redis资源正文项目依赖Springboot : 2.6.3Springcloud: 2021.0.1Springcoud Alibaba: 2021.0.1.0redis依赖<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>framework-common</artifactId> <groupId>com.xxx</groupId> <version>2.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> ​ <artifactId>framework-common-redis</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> </project>配置redis序列@Configuration @AutoConfigureBefore(RedisAutoConfiguration.class) public class BaseRedisConfig { ​ @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(RedisSerializer.json()); redisTemplate.setHashValueSerializer(RedisSerializer.json()); return redisTemplate; } ​ } ​ /** * redis 默认缓存时间 */ @Bean public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); //设置Redis缓存有效期为7天 RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() // 使的reids注解缓存的key中 "::"2个冒号转换成":"一个冒号，统一redis key的风格 .computePrefixWith(name -> name + ":") .serializeValuesWith(RedisSerializationContext.Serializ…