二言目 - Spring
type: channel
link: https://blog.piemoe.com/tag/Spring/
items: Springboot通过注解实现敏感词过滤
items: Bean Validation 分组校验
items: 自制Redis的Sringboot Starter

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…

Bean Validation 分组校验
type: item
date: 2022-02-25T14:54:00+08:00
description: Bean Validation 分组校验分组校验是指同一个Bean中，通过分组来实现组隔离校验，而不用重复建立Bean，可以有效减少代码的编写量，也减少了阅读的障碍。首先举个例子@Data @AllArgsConstructor @NoArgsConstructor @Schema(name = "统一下单 request dto") @SuppressWarnings("all") public class PayOrderUnifiedReqDTO { /** * 用户 IP */ @NotEmpty(message = "用户 IP 不能为空") private String userIp; // ========== 业务相关字段 ========== /** * 业务订单编号 */ @NotEmpty(message = "业务订单编号不能为空") private String merchantOrderId; /** * 商品标题 */ @NotEmpty(message = "业务商品标题不能为空") @Length(max = 32, message = "商品标题不能超过 32") private String subject; /** * 商品描述信息 */ @NotEmpty(message = "业务商品描述信息不能为空") @Length(max = 128, message = "商品描述信息长度不能超过128") private String body; /** * 业务商品类型 */ @NotNull(message = "业务商品类型") private Integer bizType; /** * 支付结果的 notify 回调地址 */ @NotEmpty(message = "支付结果的回调地址不能为空") @URL(message = "支付结果的 notify 回调地址必须是 URL 格式") private String notifyUrl; // /** // * 支付结果的 return 回调地址 // */ // @URL(message = "支付结果的 return 回调地址必须是 URL 格式") // private String returnUrl; // ========== 订单相关字段 ========== /** * 支付金额，单位：分 */ @NotNull(message = "支付金额不能为空") @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零") private Integer amount; /** * 支付过期时间 */ @NotNull(message = "支付过期时间不能为空") private LocalDateTime expireTime; // ========== 拓展参数 ========== /** * 支付渠道的额外参数 * 例如说，微信公众号需要传递 openid 参数 */ private Map<String, String> channelExtras; @NotNull(message = "设备类型不能为空", groups = {H5_WX.class, ANDROID_H5_WX.class, IOS_H5_WX.class}) private PayDeviceTypeEnum deviceType; @NotNull(message = "iOS平台BundleID", groups = {H5_WX.class, IOS_H5_WX.class}) private String bundleId; @NotNull(message = "Android平台PackageName", groups = {H5_WX.class, ANDROID_H5_WX.class}) private String packageName; public interface WX_APP { } public interface H5_WX { } public interface IOS_H5_WX extends H5_WX { } public interface ANDROID_H5_WX extends H5_WX { } public interface PC_ALIPAY { }其中中的groups = {H5_WX.class, IOS_H5_WX.class} 代表bundleId这个参数只有在属于H5_WX或者IOS_H5_WX分组时，才会校验参数，对于没有添加groups的属性参数，无论分组如何都全局校验。@NotN…

自制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"?> framework-common com.xxx 2.0-SNAPSHOT 4.0.0 ​ framework-common-redis org.springframework.boot spring-boot-starter-data-redis 配置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.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(7)); return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration); } }代码解释@AutoConfigureBefore注解是Spring Boot的一个注解，用于指定一个配置类在另外一个配置类之前自动配置，以确保它们的顺序正确。它的作用是控制自动配置的顺序，以便在自动配置时确定配置类的优先级。使用@AutoConfigureBefore注解需要在配置类上加上该注解，并指定目标配置类的全限定名或Class对象。例如，我们可以在自定义的Redis配置类上使用@AutoConfigureBefore注解，指定目标为框架提供的默认Redis配置类，使自定义Redis配置类在框架默认Redis配置类之前进行自动配置。示例代码如下所示：@Configuration @AutoC…