From 3f7d7c3bfac9323ab1725d12fd182e2d0a13cd87 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 9 Apr 2022 01:00:20 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20SensitiveWord=20=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E8=AF=8D=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=8E=9F=E5=88=86?= =?UTF-8?q?=E6=94=AF=E6=9C=AA=E6=AD=A3=E7=A1=AE=E5=85=B3=E8=81=94=E4=BB=93?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/type/StringLiSTTypeHandler.java | 58 +++++ .../api/sensitiveword/SensitiveWordApi.java | 16 ++ .../system/enums/ErrorCodeConstants.java | 4 + .../SensitiveWordController.http | 22 ++ .../SensitiveWordController.java | 104 +++++++++ .../sensitiveword/vo/SensitiveWordBaseVO.java | 31 +++ .../vo/SensitiveWordCreateReqVO.java | 14 ++ .../vo/SensitiveWordExcelVO.java | 35 +++ .../vo/SensitiveWordExportReqVO.java | 33 +++ .../vo/SensitiveWordPageReqVO.java | 38 ++++ .../sensitiveword/vo/SensitiveWordRespVO.java | 23 ++ .../vo/SensitiveWordUpdateReqVO.java | 21 ++ .../sensitiveword/SensitiveWordConvert.java | 36 +++ .../sensitiveword/SensitiveWordDO.java | 56 +++++ .../sensitiveword/SensitiveWordMapper.java | 47 ++++ .../SensitiveWordRefreshConsumer.java | 29 +++ .../SensitiveWordRefreshMessage.java | 19 ++ .../sensitiveword/SensitiveWordProducer.java | 26 +++ .../sensitiveword/SensitiveWordService.java | 86 ++++++++ .../SensitiveWordServiceImpl.java | 206 ++++++++++++++++++ .../system/util/collection/SimpleTrie.java | 143 ++++++++++++ .../module/system/util/package-info.java | 4 + .../src/test/resources/sql/clean.sql | 1 + .../src/test/resources/sql/create_tables.sql | 14 ++ 24 files changed, 1066 insertions(+) create mode 100644 yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApi.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.http create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordCreateReqVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExcelVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExportReqVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordPageReqVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordRespVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordUpdateReqVO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/sensitiveword/SensitiveWordConvert.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/collection/SimpleTrie.java create mode 100644 yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/package-info.java diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java new file mode 100644 index 000000000..f9811c418 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.framework.mybatis.core.type; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.apache.ibatis.type.TypeHandler; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +/** + * List 的类型转换器实现类,对应数据库的 varchar 类型 + * + * @author 永不言败 + * @since 2022 3/23 12:50:15 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes(List.class) +public class StringLiSTTypeHandler implements TypeHandler> { + + private static final String COMMA = ","; + + @Override + public void setParameter(PreparedStatement ps, int i, List strings, JdbcType jdbcType) throws SQLException { + // 设置占位符 + ps.setString(i, CollUtil.join(strings, COMMA)); + } + + @Override + public List getResult(ResultSet rs, String columnName) throws SQLException { + String value = rs.getString(columnName); + return getResult(value); + } + + @Override + public List getResult(ResultSet rs, int columnIndex) throws SQLException { + String value = rs.getString(columnIndex); + return getResult(value); + } + + @Override + public List getResult(CallableStatement cs, int columnIndex) throws SQLException { + String value = cs.getString(columnIndex); + return getResult(value); + } + + private List getResult(String value) { + if (value == null) { + return null; + } + return StrUtil.splitTrim(value, COMMA); + } +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApi.java new file mode 100644 index 000000000..d31d974e4 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApi.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.system.api.sensitiveword; + +import java.util.List; +import java.util.Set; + +/** + * 大佬,别偷懒:代码千万行,注释第一行! + * + * @author: 永不言败 <向国足学习永不言败> + * @since: 2022/3/23 17:00 + * @description: + * @modification: + */ +public interface SensitiveWordApi { + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 6b44615ce..e72c75e6e 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -119,4 +119,8 @@ public interface ErrorCodeConstants { ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定"); ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户"); + // ========== 系统铭感词 1002019000 ========= + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); + ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); + } diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.http b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.http new file mode 100644 index 000000000..16ded28f7 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.http @@ -0,0 +1,22 @@ +GET http://localhost:81/dev-api/admin-api/system/sensitive-word/get-all-tags +Accept: application/json +Authorization: Bearer 385d533b781f44f6bb21ea08afeec47c + + + +### +POST http://localhost:81/dev-api/admin-api/system/sensitive-word/create +Content-Type: application/json +Authorization: Bearer 1649ff1f8b9a4eeeb458fe93a71c78b5 + +{ + "name": "test", + "tags": [ + "bbb,aaa" + ], + "description": "test", + "status": true +} + + +### diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java new file mode 100644 index 000000000..bfb1c8bfd --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.*; +import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; +import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import javax.validation.Valid; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; + +@Api(tags = "管理后台 - 敏感词") +@RestController +@RequestMapping("/system/sensitive-word") +@Validated +public class SensitiveWordController { + + @Resource + private SensitiveWordService sensitiveWordService; + + @PostMapping("/create") + @ApiOperation("创建敏感词") + @PreAuthorize("@ss.hasPermission('system:sensitive-word:create')") + public CommonResult createSensitiveWord(@Valid @RequestBody SensitiveWordCreateReqVO createReqVO) { + return success(sensitiveWordService.createSensitiveWord(createReqVO)); + } + + @PutMapping("/update") + @ApiOperation("更新敏感词") + @PreAuthorize("@ss.hasPermission('system:sensitive-word:update')") + public CommonResult updateSensitiveWord(@Valid @RequestBody SensitiveWordUpdateReqVO updateReqVO) { + sensitiveWordService.updateSensitiveWord(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @ApiOperation("删除敏感词") + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('system:sensitive-word:delete')") + public CommonResult deleteSensitiveWord(@RequestParam("id") Long id) { + sensitiveWordService.deleteSensitiveWord(id); + return success(true); + } + + @GetMapping("/get") + @ApiOperation("获得敏感词") + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") + public CommonResult getSensitiveWord(@RequestParam("id") Long id) { + SensitiveWordDO sensitiveWord = sensitiveWordService.getSensitiveWord(id); + return success(SensitiveWordConvert.INSTANCE.convert(sensitiveWord)); + } + + @GetMapping("/page") + @ApiOperation("获得敏感词分页") + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") + public CommonResult> getSensitiveWordPage(@Valid SensitiveWordPageReqVO pageVO) { + PageResult pageResult = sensitiveWordService.getSensitiveWordPage(pageVO); + return success(SensitiveWordConvert.INSTANCE.convertPage(pageResult)); + } + + @GetMapping("/get-tags") + @ApiOperation("获取所有敏感词的标签数组") + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") + public CommonResult> getSensitiveWordTags() throws IOException { + return success(sensitiveWordService.getSensitiveWordTags()); + } + + @GetMapping("/export-excel") + @ApiOperation("导出敏感词 Excel") + @PreAuthorize("@ss.hasPermission('system:sensitive-word:export')") + @OperateLog(type = EXPORT) + public void exportSensitiveWordExcel(@Valid SensitiveWordExportReqVO exportReqVO, + HttpServletResponse response) throws IOException { + List list = sensitiveWordService.getSensitiveWordList(exportReqVO); + // 导出 Excel + List datas = SensitiveWordConvert.INSTANCE.convertList02(list); + ExcelUtils.write(response, "敏感词.xls", "数据", SensitiveWordExcelVO.class, datas); + } + +// @GetMapping("/is-sensitive-word-by-text-and-tag") +// @ApiOperation("通过tag判断传入text是否含有敏感词") +// @PreAuthorize("@ss.hasPermission('system:sensitive-word:checkbytextandtag')") +// public CommonResult isSensitiveWordByTextAndTag(@NotBlank String text, @NotBlank String tag) throws IOException { +// return success(sensitiveWordApi.isSensitiveWordByTextAndTag(text,tag)); +// } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java new file mode 100644 index 000000000..770a22b92 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.util.List; + +/** +* 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用 +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 +*/ +@Data +public class SensitiveWordBaseVO { + + @ApiModelProperty(value = "敏感词", required = true, example = "敏感词") + @NotNull(message = "敏感词不能为空") + private String name; + + @ApiModelProperty(value = "标签", required = true, example = "短信,评论") + @NotNull(message = "标签不能为空") + private List tags; + + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举类") + @NotNull(message = "状态不能为空") + private Integer status; + + @ApiModelProperty(value = "描述", example = "污言秽语") + private String description; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordCreateReqVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordCreateReqVO.java new file mode 100644 index 000000000..bb206652b --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordCreateReqVO.java @@ -0,0 +1,14 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import io.swagger.annotations.ApiModel; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@ApiModel("管理后台 - 敏感词创建 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SensitiveWordCreateReqVO extends SensitiveWordBaseVO { + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExcelVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExcelVO.java new file mode 100644 index 000000000..f259f1f31 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExcelVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +/** + * 敏感词 Excel VO + * + * @author 永不言败 + */ +@Data +public class SensitiveWordExcelVO { + + @ExcelProperty("编号") + private Long id; + + @ExcelProperty("敏感词") + private String name; + + @ExcelProperty("标签") + private List tags; + + @ExcelProperty("状态,true-启用,false-禁用") + private Integer status; + + @ExcelProperty("描述") + private String description; + + @ExcelProperty("创建时间") + private Date createTime; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExportReqVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExportReqVO.java new file mode 100644 index 000000000..3c9076989 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExportReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel(value = "管理后台 - 敏感词 Excel 导出 Request VO", description = "参数和 SensitiveWordPageReqVO 是一致的") +@Data +public class SensitiveWordExportReqVO { + + @ApiModelProperty(value = "敏感词", example = "敏感词") + private String name; + + @ApiModelProperty(value = "标签", example = "短信,评论") + private String tag; + + @ApiModelProperty(value = "状态", example = "true-启用,false-禁用") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordPageReqVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordPageReqVO.java new file mode 100644 index 000000000..1a4d7861f --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordPageReqVO.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.util.Date; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@ApiModel("管理后台 - 敏感词分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SensitiveWordPageReqVO extends PageParam { + + @ApiModelProperty(value = "敏感词", example = "敏感词") + private String name; + + @ApiModelProperty(value = "标签", example = "短信,评论") + private String tag; + + @ApiModelProperty(value = "状态", example = "true-启用,true-禁用") + private Integer status; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "开始创建时间") + private Date beginCreateTime; + + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @ApiModelProperty(value = "结束创建时间") + private Date endCreateTime; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordRespVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordRespVO.java new file mode 100644 index 000000000..35fb8c2c9 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordRespVO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.util.Date; + +@ApiModel("管理后台 - 敏感词 Response VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SensitiveWordRespVO extends SensitiveWordBaseVO { + + @ApiModelProperty(value = "编号", required = true, example = "1") + private Long id; + + @ApiModelProperty(value = "创建时间", required = true) + private Date createTime; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordUpdateReqVO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordUpdateReqVO.java new file mode 100644 index 000000000..f87aa3a62 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordUpdateReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import javax.validation.constraints.NotNull; + +@ApiModel("管理后台 - 敏感词更新 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SensitiveWordUpdateReqVO extends SensitiveWordBaseVO { + + @ApiModelProperty(value = "编号", required = true, example = "1") + @NotNull(message = "编号不能为空") + private Long id; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/sensitiveword/SensitiveWordConvert.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/sensitiveword/SensitiveWordConvert.java new file mode 100644 index 000000000..fde03dbf3 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/sensitiveword/SensitiveWordConvert.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.system.convert.sensitiveword; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExcelVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordRespVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.List; + +/** + * 敏感词 Convert + * + * @author 永不言败 + */ +@Mapper +public interface SensitiveWordConvert { + + SensitiveWordConvert INSTANCE = Mappers.getMapper(SensitiveWordConvert.class); + + SensitiveWordDO convert(SensitiveWordCreateReqVO bean); + + SensitiveWordDO convert(SensitiveWordUpdateReqVO bean); + + SensitiveWordRespVO convert(SensitiveWordDO bean); + + List convertList(List list); + + PageResult convertPage(PageResult page); + + List convertList02(List list); + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java new file mode 100644 index 000000000..dfa729fa7 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.framework.mybatis.core.type.StringLiSTTypeHandler; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.util.List; + +/** + * 敏感词 DO + * + * @author 永不言败 + */ +@TableName(value = "system_sensitive_word", autoResultMap = true) +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class SensitiveWordDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 敏感词 + */ + private String name; + /** + * 描述 + */ + private String description; + /** + * 标签数组 + * + * 用于实现不同的业务场景下,需要使用不同标签的敏感词。 + * 例如说,tag 有短信、论坛两种,敏感词 "推广" 在短信下是敏感词,在论坛下不是敏感词。 + * 此时,我们会存储一条敏感词记录,它的 name 为"推广",tag 为短信。 + */ + @TableField(typeHandler = StringLiSTTypeHandler.class) + private List tags; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java new file mode 100644 index 000000000..9360e7fbe --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.system.dal.mysql.sensitiveword; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +import java.util.Date; +import java.util.List; + +/** + * 敏感词 Mapper + * + * @author 永不言败 + */ +@Mapper +public interface SensitiveWordMapper extends BaseMapperX { + + default PageResult selectPage(SensitiveWordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(SensitiveWordDO::getName, reqVO.getName()) + .likeIfPresent(SensitiveWordDO::getTags, reqVO.getTag()) + .eqIfPresent(SensitiveWordDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(SensitiveWordDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc(SensitiveWordDO::getId)); + } + + default List selectList(SensitiveWordExportReqVO reqVO) { + return selectList(new LambdaQueryWrapperX() + .likeIfPresent(SensitiveWordDO::getName, reqVO.getName()) + .likeIfPresent(SensitiveWordDO::getTags, reqVO.getTag()) + .eqIfPresent(SensitiveWordDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(SensitiveWordDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) + .orderByDesc(SensitiveWordDO::getId)); + } + + default SensitiveWordDO selectByName(String name) { + return selectOne(SensitiveWordDO::getName, name); + } + + @Select("SELECT id FROM system_sensitive_word WHERE update_time > #{maxUpdateTime} LIMIT 1") + SensitiveWordDO selectExistsByUpdateTimeAfter(Date maxUpdateTime); +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java new file mode 100644 index 000000000..dc3a06236 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword; + +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; +import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage; +import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 针对 {@link SensitiveWordRefreshMessage} 的消费者 + * + * @author 芋道源码 + */ +@Component +@Slf4j +public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener { + + @Resource + private SensitiveWordService sensitiveWordService; + + @Override + public void onMessage(SensitiveWordRefreshMessage message) { + log.info("[onMessage][收到 SensitiveWord 刷新消息]"); + sensitiveWordService.initLocalCache(); + } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java new file mode 100644 index 000000000..13ebf425f --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.system.mq.message.sensitiveword; + +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 敏感词的刷新 Message + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class SensitiveWordRefreshMessage extends AbstractChannelMessage { + + @Override + public String getChannel() { + return "system.sensitive-word.refresh"; + } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java new file mode 100644 index 000000000..3c43eca3b --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.system.mq.producer.sensitiveword; + +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; +import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * 敏感词相关的 Producer + */ +@Component +public class SensitiveWordProducer { + + @Resource + private RedisMQTemplate redisMQTemplate; + + /** + * 发送 {@link SensitiveWordRefreshMessage} 消息 + */ + public void sendSensitiveWordRefreshMessage() { + SensitiveWordRefreshMessage message = new SensitiveWordRefreshMessage(); + redisMQTemplate.send(message); + } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java new file mode 100644 index 000000000..7a82e2914 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.system.service.sensitiveword; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; + +import javax.validation.Valid; +import java.util.List; +import java.util.Set; + +/** + * 敏感词 Service 接口 + * + * @author 永不言败 + */ +public interface SensitiveWordService { + + /** + * 初始化本地缓存 + */ + void initLocalCache(); + + /** + * 创建敏感词 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createSensitiveWord(@Valid SensitiveWordCreateReqVO createReqVO); + + /** + * 更新敏感词 + * + * @param updateReqVO 更新信息 + */ + void updateSensitiveWord(@Valid SensitiveWordUpdateReqVO updateReqVO); + + /** + * 删除敏感词 + * + * @param id 编号 + */ + void deleteSensitiveWord(Long id); + + /** + * 获得敏感词 + * + * @param id 编号 + * @return 敏感词 + */ + SensitiveWordDO getSensitiveWord(Long id); + + /** + * 获得敏感词列表 + * + * @return 敏感词列表 + */ + List getSensitiveWordList(); + + /** + * 获得敏感词分页 + * + * @param pageReqVO 分页查询 + * @return 敏感词分页 + */ + PageResult getSensitiveWordPage(SensitiveWordPageReqVO pageReqVO); + + /** + * 获得敏感词列表, 用于 Excel 导出 + * + * @param exportReqVO 查询条件 + * @return 敏感词列表 + */ + List getSensitiveWordList(SensitiveWordExportReqVO exportReqVO); + + /** + * 获得所有敏感词的标签数组 + * + * @return 标签数组 + */ + Set getSensitiveWordTags(); + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java new file mode 100644 index 000000000..08cb45ebe --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java @@ -0,0 +1,206 @@ +package cn.iocoder.yudao.module.system.service.sensitiveword; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; +import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; +import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; +import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; + +/** + * 敏感词 Service 实现类 + * + * @author 永不言败 + */ +@Service +@Slf4j +@Validated +public class SensitiveWordServiceImpl implements SensitiveWordService { + + /** + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 + */ + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; + + /** + * 敏感词缓存 + * key:敏感词编号 {@link SensitiveWordDO#getId()} + *

+ * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 + */ + @Getter + private volatile Map sensitiveWordCache = Collections.emptyMap(); + + /** + * 敏感词标签缓存 + * key:敏感词编号 {@link SensitiveWordDO#getId()} + *

+ * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 + */ + @Getter + private volatile Set sensitiveWordTagsCache = Collections.emptySet(); + + /** + * 缓存敏感词的最大更新时间,用于后续的增量轮询,判断是否有更新 + */ + @Getter + private volatile Date maxUpdateTime; + + @Resource + private SensitiveWordMapper sensitiveWordMapper; + + @Resource + private SensitiveWordProducer sensitiveWordProducer; + + @Resource + @Lazy + private SensitiveWordService self; + + /** + * 初始化 {@link #sensitiveWordCache} 缓存 + */ + @Override + @PostConstruct + public void initLocalCache() { + // 获取敏感词列表,如果有更新 + List sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime); + if (CollUtil.isEmpty(sensitiveWordList)) { + return; + } + + // 写入 sensitiveWordTagsCache 缓存 + Set tags = new HashSet<>(); + sensitiveWordList.forEach(word -> tags.addAll(word.getTags())); + sensitiveWordTagsCache = tags; + // 写入 sensitiveWordCache 缓存 + sensitiveWordCache = CollectionUtils.convertMap(sensitiveWordList, SensitiveWordDO::getId); + maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime); + log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size()); + } + + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) + public void schedulePeriodicRefresh() { + self.initLocalCache(); + } + + /** + * 如果敏感词发生变化,从数据库中获取最新的全量敏感词。 + * 如果未发生变化,则返回空 + * + * @param maxUpdateTime 当前敏感词的最大更新时间 + * @return 敏感词列表 + */ + private List loadSensitiveWordIfUpdate(Date maxUpdateTime) { + // 第一步,判断是否要更新。 + // 如果更新时间为空,说明 DB 一定有新数据 + if (maxUpdateTime == null) { + log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]"); + } else { // 判断数据库中是否有更新的敏感词 + if (sensitiveWordMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) { + return null; + } + log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]"); + } + // 第二步,如果有更新,则从数据库加载所有敏感词 + return sensitiveWordMapper.selectList(); + } + + @Override + public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) { + // 校验唯一性 + checkSensitiveWordNameUnique(null, createReqVO.getName()); + // 插入 + SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO); + sensitiveWordMapper.insert(sensitiveWord); + // 发送消息,刷新缓存 + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); + return sensitiveWord.getId(); + } + + @Override + public void updateSensitiveWord(SensitiveWordUpdateReqVO updateReqVO) { + // 校验唯一性 + checkSensitiveWordExists(updateReqVO.getId()); + checkSensitiveWordNameUnique(updateReqVO.getId(), updateReqVO.getName()); + // 更新 + SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO); + sensitiveWordMapper.updateById(updateObj); + // 发送消息,刷新缓存 + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); + } + + @Override + public void deleteSensitiveWord(Long id) { + // 校验存在 + checkSensitiveWordExists(id); + // 删除 + sensitiveWordMapper.deleteById(id); + // 发送消息,刷新缓存 + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); + } + + private void checkSensitiveWordNameUnique(Long id, String name) { + SensitiveWordDO word = sensitiveWordMapper.selectByName(name); + if (word == null) { + return; + } + // 如果 id 为空,说明不用比较是否为相同 id 的敏感词 + if (id == null) { + throw exception(SENSITIVE_WORD_EXISTS); + } + if (!word.getId().equals(id)) { + throw exception(SENSITIVE_WORD_EXISTS); + } + } + + private void checkSensitiveWordExists(Long id) { + if (sensitiveWordMapper.selectById(id) == null) { + throw exception(SENSITIVE_WORD_NOT_EXISTS); + } + } + + @Override + public SensitiveWordDO getSensitiveWord(Long id) { + return sensitiveWordMapper.selectById(id); + } + + @Override + public List getSensitiveWordList() { + return sensitiveWordMapper.selectList(); + } + + @Override + public PageResult getSensitiveWordPage(SensitiveWordPageReqVO pageReqVO) { + return sensitiveWordMapper.selectPage(pageReqVO); + } + + @Override + public List getSensitiveWordList(SensitiveWordExportReqVO exportReqVO) { + return sensitiveWordMapper.selectList(exportReqVO); + } + + @Override + public Set getSensitiveWordTags() { + return sensitiveWordTagsCache; + } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/collection/SimpleTrie.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/collection/SimpleTrie.java new file mode 100644 index 000000000..b6dc1ed10 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/collection/SimpleTrie.java @@ -0,0 +1,143 @@ +package cn.iocoder.yudao.module.system.util.collection; + +import java.util.*; + +/** + * 基于前缀树,实现敏感词的校验 + *

+ * 相比 Apache Common 提供的 PatriciaTrie 来说,性能可能会更加好一些。 + * + * @author 芋道源码 + */ +@SuppressWarnings("unchecked") +public class SimpleTrie { + + /** + * 一个敏感词结束后对应的 key + */ + private static final Character CHARACTER_END = '\0'; + + /** + * 使用敏感词,构建的前缀树 + */ + private final Map children; + + /** + * 基于字符串,构建前缀树 + * + * @param strs 字符串数组 + */ + public SimpleTrie(List strs) { + children = new HashMap<>(); + // 构建树 + Collections.sort(strs); // 排序,优先使用较短的前缀 + for (String str : strs) { + Map child = children; + // 遍历每个字符 + for (Character c : str.toCharArray()) { + // 如果已经到达结束,就没必要在添加更长的敏感词。 + // 例如说,有两个敏感词是:吃饭啊、吃饭。输入一句话是 “我要吃饭啊”,则只要匹配到 “吃饭” 这个敏感词即可。 + if (child.containsKey(CHARACTER_END)) { + break; + } + if (!child.containsKey(c)) { + child.put(c, new HashMap<>()); + } + child = (Map) child.get(c); + } + // 结束 + child.put(CHARACTER_END, null); + } + } + + /** + * 验证文本是否合法,即不包含敏感词 + * + * @param text 文本 + * @return 是否 ok + */ + public boolean isValid(String text) { + // 遍历 text,使用每一个 [i, n) 段的字符串,使用 children 前缀树匹配,是否包含敏感词 + for (int i = 0; i < text.length() - 1; i++) { + Map child = (Map) children.get(text.charAt(i)); + if (child == null) { + continue; + } + boolean ok = recursion(text, i + 1, child); + if (!ok) { + return false; + } + } + return true; + } + + /** + * 验证文本从指定位置开始,是否包含某个敏感词 + * + * @param text 文本 + * @param index 开始位置 + * @param child 节点(当前遍历到的) + * @return 是否包含 + */ + private boolean recursion(String text, int index, Map child) { + if (index == text.length()) { + return true; + } + child = (Map) child.get(text.charAt(index)); + return child == null || !child.containsKey(CHARACTER_END) && recursion(text, ++index, child); + } + + /** + * 获得文本所包含的不合法的敏感词 + * + * 注意,才当即最短匹配原则。例如说:当敏感词存在 “煞笔”,“煞笔二货 ”时,只会返回 “煞笔”。 + * + * @param text 文本 + * @return 匹配的敏感词 + */ + public List validate(String text) { + Set results = new HashSet<>(); + for (int i = 0; i < text.length() - 1; i++) { + Character c = text.charAt(i); + Map child = (Map) children.get(c); + if (child == null) { + continue; + } + StringBuilder result = new StringBuilder().append(c); + boolean ok = recursionWithResult(text, i + 1, child, result); + if (!ok) { + results.add(result.toString()); + } + } + return new ArrayList<>(results); + } + + /** + * 返回文本从 index 开始的敏感词,并使用 StringBuilder 参数进行返回 + * + * 逻辑和 {@link #recursion(String, int, Map)} 是一致,只是多了 result 返回结果 + * + * @param text 文本 + * @param index 开始未知 + * @param child 节点(当前遍历到的) + * @param result 返回敏感词 + * @return 是否有敏感词 + */ + @SuppressWarnings("unchecked") + private static boolean recursionWithResult(String text, int index, Map child, StringBuilder result) { + if (index == text.length()) { + return true; + } + Character c = text.charAt(index); + child = (Map) child.get(c); + if (child == null) { + return true; + } + if (child.containsKey(CHARACTER_END)) { + result.append(c); + return false; + } + return recursionWithResult(text, ++index, child, result.append(c)); + } + +} diff --git a/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/package-info.java b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/package-info.java new file mode 100644 index 000000000..97ca1f1a6 --- /dev/null +++ b/yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/package-info.java @@ -0,0 +1,4 @@ +/** + * 每个模块的 util 包,放专属当前模块的 Utils 工具类 + */ +package cn.iocoder.yudao.module.system.util; diff --git a/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/clean.sql b/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/clean.sql index c709513b1..6f0e9d384 100644 --- a/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/clean.sql +++ b/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/clean.sql @@ -17,3 +17,4 @@ DELETE FROM "system_error_code"; DELETE FROM "system_social_user"; DELETE FROM "system_tenant"; DELETE FROM "system_tenant_package"; +DELETE FROM "system_sensitive_word"; diff --git a/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/create_tables.sql b/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/create_tables.sql index 5db3692e5..6b00fb1b5 100644 --- a/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/create_tables.sql +++ b/yudao-module-system/yudao-module-system-impl/src/test/resources/sql/create_tables.sql @@ -426,3 +426,17 @@ CREATE TABLE IF NOT EXISTS "system_tenant_package" ( "deleted" bit NOT NULL DEFAULT FALSE, PRIMARY KEY ("id") ) COMMENT '租户套餐表'; + +CREATE TABLE IF NOT EXISTS "system_sensitive_word" ( + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, + "name" varchar(255) NOT NULL, + "tags" varchar(1024) NOT NULL, + "status" bit NOT NULL DEFAULT FALSE, + "description" varchar(512), + "creator" varchar(64) DEFAULT '', + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updater" varchar(64) DEFAULT '', + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + "deleted" bit NOT NULL DEFAULT FALSE, + PRIMARY KEY ("id") +) COMMENT '系统敏感词';