Commit 3f7d7c3bfac9323ab1725d12fd182e2d0a13cd87
1 parent
bae4502e
同步 SensitiveWord 敏感词代码,原分支未正确关联仓库
Showing
24 changed files
with
1066 additions
and
0 deletions
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/type/StringLiSTTypeHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.mybatis.core.type; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.hutool.core.util.StrUtil; | ||
| 5 | +import org.apache.ibatis.type.JdbcType; | ||
| 6 | +import org.apache.ibatis.type.MappedJdbcTypes; | ||
| 7 | +import org.apache.ibatis.type.MappedTypes; | ||
| 8 | +import org.apache.ibatis.type.TypeHandler; | ||
| 9 | + | ||
| 10 | +import java.sql.CallableStatement; | ||
| 11 | +import java.sql.PreparedStatement; | ||
| 12 | +import java.sql.ResultSet; | ||
| 13 | +import java.sql.SQLException; | ||
| 14 | +import java.util.List; | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * List<String> 的类型转换器实现类,对应数据库的 varchar 类型 | ||
| 18 | + * | ||
| 19 | + * @author 永不言败 | ||
| 20 | + * @since 2022 3/23 12:50:15 | ||
| 21 | + */ | ||
| 22 | +@MappedJdbcTypes(JdbcType.VARCHAR) | ||
| 23 | +@MappedTypes(List.class) | ||
| 24 | +public class StringLiSTTypeHandler implements TypeHandler<List<String>> { | ||
| 25 | + | ||
| 26 | + private static final String COMMA = ","; | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException { | ||
| 30 | + // 设置占位符 | ||
| 31 | + ps.setString(i, CollUtil.join(strings, COMMA)); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public List<String> getResult(ResultSet rs, String columnName) throws SQLException { | ||
| 36 | + String value = rs.getString(columnName); | ||
| 37 | + return getResult(value); | ||
| 38 | + } | ||
| 39 | + | ||
| 40 | + @Override | ||
| 41 | + public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException { | ||
| 42 | + String value = rs.getString(columnIndex); | ||
| 43 | + return getResult(value); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Override | ||
| 47 | + public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException { | ||
| 48 | + String value = cs.getString(columnIndex); | ||
| 49 | + return getResult(value); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + private List<String> getResult(String value) { | ||
| 53 | + if (value == null) { | ||
| 54 | + return null; | ||
| 55 | + } | ||
| 56 | + return StrUtil.splitTrim(value, COMMA); | ||
| 57 | + } | ||
| 58 | +} |
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/sensitiveword/SensitiveWordApi.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.api.sensitiveword; | ||
| 2 | + | ||
| 3 | +import java.util.List; | ||
| 4 | +import java.util.Set; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 大佬,别偷懒:代码千万行,注释第一行! | ||
| 8 | + * | ||
| 9 | + * @author: 永不言败 <向国足学习永不言败> | ||
| 10 | + * @since: 2022/3/23 17:00 | ||
| 11 | + * @description: | ||
| 12 | + * @modification: | ||
| 13 | + */ | ||
| 14 | +public interface SensitiveWordApi { | ||
| 15 | + | ||
| 16 | +} |
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 { | @@ -119,4 +119,8 @@ public interface ErrorCodeConstants { | ||
| 119 | ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定"); | 119 | ErrorCode SOCIAL_USER_UNBIND_NOT_SELF = new ErrorCode(1002018001, "社交解绑失败,非当前用户绑定"); |
| 120 | ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户"); | 120 | ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1002018002, "社交授权失败,找不到对应的用户"); |
| 121 | 121 | ||
| 122 | + // ========== 系统铭感词 1002019000 ========= | ||
| 123 | + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1002019000, "系统敏感词在所有标签中都不存在"); | ||
| 124 | + ErrorCode SENSITIVE_WORD_EXISTS = new ErrorCode(1002019001, "系统敏感词已在标签中存在"); | ||
| 125 | + | ||
| 122 | } | 126 | } |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.http
0 → 100644
| 1 | +GET http://localhost:81/dev-api/admin-api/system/sensitive-word/get-all-tags | ||
| 2 | +Accept: application/json | ||
| 3 | +Authorization: Bearer 385d533b781f44f6bb21ea08afeec47c | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + | ||
| 7 | +### | ||
| 8 | +POST http://localhost:81/dev-api/admin-api/system/sensitive-word/create | ||
| 9 | +Content-Type: application/json | ||
| 10 | +Authorization: Bearer 1649ff1f8b9a4eeeb458fe93a71c78b5 | ||
| 11 | + | ||
| 12 | +{ | ||
| 13 | + "name": "test", | ||
| 14 | + "tags": [ | ||
| 15 | + "bbb,aaa" | ||
| 16 | + ], | ||
| 17 | + "description": "test", | ||
| 18 | + "status": true | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +### |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/SensitiveWordController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 5 | +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; | ||
| 6 | +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.*; | ||
| 8 | +import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; | ||
| 9 | +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||
| 10 | +import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService; | ||
| 11 | +import io.swagger.annotations.Api; | ||
| 12 | +import io.swagger.annotations.ApiImplicitParam; | ||
| 13 | +import io.swagger.annotations.ApiOperation; | ||
| 14 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 15 | +import org.springframework.validation.annotation.Validated; | ||
| 16 | +import org.springframework.web.bind.annotation.*; | ||
| 17 | + | ||
| 18 | +import javax.annotation.Resource; | ||
| 19 | +import javax.servlet.http.HttpServletResponse; | ||
| 20 | +import javax.validation.Valid; | ||
| 21 | +import java.io.IOException; | ||
| 22 | +import java.util.List; | ||
| 23 | +import java.util.Set; | ||
| 24 | + | ||
| 25 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 26 | +import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; | ||
| 27 | + | ||
| 28 | +@Api(tags = "管理后台 - 敏感词") | ||
| 29 | +@RestController | ||
| 30 | +@RequestMapping("/system/sensitive-word") | ||
| 31 | +@Validated | ||
| 32 | +public class SensitiveWordController { | ||
| 33 | + | ||
| 34 | + @Resource | ||
| 35 | + private SensitiveWordService sensitiveWordService; | ||
| 36 | + | ||
| 37 | + @PostMapping("/create") | ||
| 38 | + @ApiOperation("创建敏感词") | ||
| 39 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:create')") | ||
| 40 | + public CommonResult<Long> createSensitiveWord(@Valid @RequestBody SensitiveWordCreateReqVO createReqVO) { | ||
| 41 | + return success(sensitiveWordService.createSensitiveWord(createReqVO)); | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + @PutMapping("/update") | ||
| 45 | + @ApiOperation("更新敏感词") | ||
| 46 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:update')") | ||
| 47 | + public CommonResult<Boolean> updateSensitiveWord(@Valid @RequestBody SensitiveWordUpdateReqVO updateReqVO) { | ||
| 48 | + sensitiveWordService.updateSensitiveWord(updateReqVO); | ||
| 49 | + return success(true); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + @DeleteMapping("/delete") | ||
| 53 | + @ApiOperation("删除敏感词") | ||
| 54 | + @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) | ||
| 55 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:delete')") | ||
| 56 | + public CommonResult<Boolean> deleteSensitiveWord(@RequestParam("id") Long id) { | ||
| 57 | + sensitiveWordService.deleteSensitiveWord(id); | ||
| 58 | + return success(true); | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + @GetMapping("/get") | ||
| 62 | + @ApiOperation("获得敏感词") | ||
| 63 | + @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) | ||
| 64 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") | ||
| 65 | + public CommonResult<SensitiveWordRespVO> getSensitiveWord(@RequestParam("id") Long id) { | ||
| 66 | + SensitiveWordDO sensitiveWord = sensitiveWordService.getSensitiveWord(id); | ||
| 67 | + return success(SensitiveWordConvert.INSTANCE.convert(sensitiveWord)); | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @GetMapping("/page") | ||
| 71 | + @ApiOperation("获得敏感词分页") | ||
| 72 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") | ||
| 73 | + public CommonResult<PageResult<SensitiveWordRespVO>> getSensitiveWordPage(@Valid SensitiveWordPageReqVO pageVO) { | ||
| 74 | + PageResult<SensitiveWordDO> pageResult = sensitiveWordService.getSensitiveWordPage(pageVO); | ||
| 75 | + return success(SensitiveWordConvert.INSTANCE.convertPage(pageResult)); | ||
| 76 | + } | ||
| 77 | + | ||
| 78 | + @GetMapping("/get-tags") | ||
| 79 | + @ApiOperation("获取所有敏感词的标签数组") | ||
| 80 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:query')") | ||
| 81 | + public CommonResult<Set<String>> getSensitiveWordTags() throws IOException { | ||
| 82 | + return success(sensitiveWordService.getSensitiveWordTags()); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + @GetMapping("/export-excel") | ||
| 86 | + @ApiOperation("导出敏感词 Excel") | ||
| 87 | + @PreAuthorize("@ss.hasPermission('system:sensitive-word:export')") | ||
| 88 | + @OperateLog(type = EXPORT) | ||
| 89 | + public void exportSensitiveWordExcel(@Valid SensitiveWordExportReqVO exportReqVO, | ||
| 90 | + HttpServletResponse response) throws IOException { | ||
| 91 | + List<SensitiveWordDO> list = sensitiveWordService.getSensitiveWordList(exportReqVO); | ||
| 92 | + // 导出 Excel | ||
| 93 | + List<SensitiveWordExcelVO> datas = SensitiveWordConvert.INSTANCE.convertList02(list); | ||
| 94 | + ExcelUtils.write(response, "敏感词.xls", "数据", SensitiveWordExcelVO.class, datas); | ||
| 95 | + } | ||
| 96 | + | ||
| 97 | +// @GetMapping("/is-sensitive-word-by-text-and-tag") | ||
| 98 | +// @ApiOperation("通过tag判断传入text是否含有敏感词") | ||
| 99 | +// @PreAuthorize("@ss.hasPermission('system:sensitive-word:checkbytextandtag')") | ||
| 100 | +// public CommonResult<Boolean> isSensitiveWordByTextAndTag(@NotBlank String text, @NotBlank String tag) throws IOException { | ||
| 101 | +// return success(sensitiveWordApi.isSensitiveWordByTextAndTag(text,tag)); | ||
| 102 | +// } | ||
| 103 | + | ||
| 104 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordBaseVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModelProperty; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotNull; | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | +* 敏感词 Base VO,提供给添加、修改、详细的子 VO 使用 | ||
| 11 | +* 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 | ||
| 12 | +*/ | ||
| 13 | +@Data | ||
| 14 | +public class SensitiveWordBaseVO { | ||
| 15 | + | ||
| 16 | + @ApiModelProperty(value = "敏感词", required = true, example = "敏感词") | ||
| 17 | + @NotNull(message = "敏感词不能为空") | ||
| 18 | + private String name; | ||
| 19 | + | ||
| 20 | + @ApiModelProperty(value = "标签", required = true, example = "短信,评论") | ||
| 21 | + @NotNull(message = "标签不能为空") | ||
| 22 | + private List<String> tags; | ||
| 23 | + | ||
| 24 | + @ApiModelProperty(value = "状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举类") | ||
| 25 | + @NotNull(message = "状态不能为空") | ||
| 26 | + private Integer status; | ||
| 27 | + | ||
| 28 | + @ApiModelProperty(value = "描述", example = "污言秽语") | ||
| 29 | + private String description; | ||
| 30 | + | ||
| 31 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordCreateReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.EqualsAndHashCode; | ||
| 6 | +import lombok.ToString; | ||
| 7 | + | ||
| 8 | +@ApiModel("管理后台 - 敏感词创建 Request VO") | ||
| 9 | +@Data | ||
| 10 | +@EqualsAndHashCode(callSuper = true) | ||
| 11 | +@ToString(callSuper = true) | ||
| 12 | +public class SensitiveWordCreateReqVO extends SensitiveWordBaseVO { | ||
| 13 | + | ||
| 14 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExcelVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import com.alibaba.excel.annotation.ExcelProperty; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import java.util.Date; | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 敏感词 Excel VO | ||
| 11 | + * | ||
| 12 | + * @author 永不言败 | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +public class SensitiveWordExcelVO { | ||
| 16 | + | ||
| 17 | + @ExcelProperty("编号") | ||
| 18 | + private Long id; | ||
| 19 | + | ||
| 20 | + @ExcelProperty("敏感词") | ||
| 21 | + private String name; | ||
| 22 | + | ||
| 23 | + @ExcelProperty("标签") | ||
| 24 | + private List<String> tags; | ||
| 25 | + | ||
| 26 | + @ExcelProperty("状态,true-启用,false-禁用") | ||
| 27 | + private Integer status; | ||
| 28 | + | ||
| 29 | + @ExcelProperty("描述") | ||
| 30 | + private String description; | ||
| 31 | + | ||
| 32 | + @ExcelProperty("创建时间") | ||
| 33 | + private Date createTime; | ||
| 34 | + | ||
| 35 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordExportReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 7 | + | ||
| 8 | +import java.util.Date; | ||
| 9 | + | ||
| 10 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||
| 11 | + | ||
| 12 | +@ApiModel(value = "管理后台 - 敏感词 Excel 导出 Request VO", description = "参数和 SensitiveWordPageReqVO 是一致的") | ||
| 13 | +@Data | ||
| 14 | +public class SensitiveWordExportReqVO { | ||
| 15 | + | ||
| 16 | + @ApiModelProperty(value = "敏感词", example = "敏感词") | ||
| 17 | + private String name; | ||
| 18 | + | ||
| 19 | + @ApiModelProperty(value = "标签", example = "短信,评论") | ||
| 20 | + private String tag; | ||
| 21 | + | ||
| 22 | + @ApiModelProperty(value = "状态", example = "true-启用,false-禁用") | ||
| 23 | + private Integer status; | ||
| 24 | + | ||
| 25 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 26 | + @ApiModelProperty(value = "开始创建时间") | ||
| 27 | + private Date beginCreateTime; | ||
| 28 | + | ||
| 29 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 30 | + @ApiModelProperty(value = "结束创建时间") | ||
| 31 | + private Date endCreateTime; | ||
| 32 | + | ||
| 33 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||
| 4 | +import io.swagger.annotations.ApiModel; | ||
| 5 | +import io.swagger.annotations.ApiModelProperty; | ||
| 6 | +import lombok.Data; | ||
| 7 | +import lombok.EqualsAndHashCode; | ||
| 8 | +import lombok.ToString; | ||
| 9 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 10 | + | ||
| 11 | +import java.util.Date; | ||
| 12 | + | ||
| 13 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||
| 14 | + | ||
| 15 | +@ApiModel("管理后台 - 敏感词分页 Request VO") | ||
| 16 | +@Data | ||
| 17 | +@EqualsAndHashCode(callSuper = true) | ||
| 18 | +@ToString(callSuper = true) | ||
| 19 | +public class SensitiveWordPageReqVO extends PageParam { | ||
| 20 | + | ||
| 21 | + @ApiModelProperty(value = "敏感词", example = "敏感词") | ||
| 22 | + private String name; | ||
| 23 | + | ||
| 24 | + @ApiModelProperty(value = "标签", example = "短信,评论") | ||
| 25 | + private String tag; | ||
| 26 | + | ||
| 27 | + @ApiModelProperty(value = "状态", example = "true-启用,true-禁用") | ||
| 28 | + private Integer status; | ||
| 29 | + | ||
| 30 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 31 | + @ApiModelProperty(value = "开始创建时间") | ||
| 32 | + private Date beginCreateTime; | ||
| 33 | + | ||
| 34 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 35 | + @ApiModelProperty(value = "结束创建时间") | ||
| 36 | + private Date endCreateTime; | ||
| 37 | + | ||
| 38 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.EqualsAndHashCode; | ||
| 7 | +import lombok.ToString; | ||
| 8 | + | ||
| 9 | +import java.util.Date; | ||
| 10 | + | ||
| 11 | +@ApiModel("管理后台 - 敏感词 Response VO") | ||
| 12 | +@Data | ||
| 13 | +@EqualsAndHashCode(callSuper = true) | ||
| 14 | +@ToString(callSuper = true) | ||
| 15 | +public class SensitiveWordRespVO extends SensitiveWordBaseVO { | ||
| 16 | + | ||
| 17 | + @ApiModelProperty(value = "编号", required = true, example = "1") | ||
| 18 | + private Long id; | ||
| 19 | + | ||
| 20 | + @ApiModelProperty(value = "创建时间", required = true) | ||
| 21 | + private Date createTime; | ||
| 22 | + | ||
| 23 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sensitiveword/vo/SensitiveWordUpdateReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import lombok.EqualsAndHashCode; | ||
| 7 | +import lombok.ToString; | ||
| 8 | + | ||
| 9 | +import javax.validation.constraints.NotNull; | ||
| 10 | + | ||
| 11 | +@ApiModel("管理后台 - 敏感词更新 Request VO") | ||
| 12 | +@Data | ||
| 13 | +@EqualsAndHashCode(callSuper = true) | ||
| 14 | +@ToString(callSuper = true) | ||
| 15 | +public class SensitiveWordUpdateReqVO extends SensitiveWordBaseVO { | ||
| 16 | + | ||
| 17 | + @ApiModelProperty(value = "编号", required = true, example = "1") | ||
| 18 | + @NotNull(message = "编号不能为空") | ||
| 19 | + private Long id; | ||
| 20 | + | ||
| 21 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/convert/sensitiveword/SensitiveWordConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.convert.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExcelVO; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordRespVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||
| 9 | +import org.mapstruct.Mapper; | ||
| 10 | +import org.mapstruct.factory.Mappers; | ||
| 11 | + | ||
| 12 | +import java.util.List; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 敏感词 Convert | ||
| 16 | + * | ||
| 17 | + * @author 永不言败 | ||
| 18 | + */ | ||
| 19 | +@Mapper | ||
| 20 | +public interface SensitiveWordConvert { | ||
| 21 | + | ||
| 22 | + SensitiveWordConvert INSTANCE = Mappers.getMapper(SensitiveWordConvert.class); | ||
| 23 | + | ||
| 24 | + SensitiveWordDO convert(SensitiveWordCreateReqVO bean); | ||
| 25 | + | ||
| 26 | + SensitiveWordDO convert(SensitiveWordUpdateReqVO bean); | ||
| 27 | + | ||
| 28 | + SensitiveWordRespVO convert(SensitiveWordDO bean); | ||
| 29 | + | ||
| 30 | + List<SensitiveWordRespVO> convertList(List<SensitiveWordDO> list); | ||
| 31 | + | ||
| 32 | + PageResult<SensitiveWordRespVO> convertPage(PageResult<SensitiveWordDO> page); | ||
| 33 | + | ||
| 34 | + List<SensitiveWordExcelVO> convertList02(List<SensitiveWordDO> list); | ||
| 35 | + | ||
| 36 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sensitiveword/SensitiveWordDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.type.StringLiSTTypeHandler; | ||
| 6 | +import com.baomidou.mybatisplus.annotation.TableField; | ||
| 7 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 8 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 9 | +import lombok.*; | ||
| 10 | + | ||
| 11 | +import java.util.List; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * 敏感词 DO | ||
| 15 | + * | ||
| 16 | + * @author 永不言败 | ||
| 17 | + */ | ||
| 18 | +@TableName(value = "system_sensitive_word", autoResultMap = true) | ||
| 19 | +@Data | ||
| 20 | +@EqualsAndHashCode(callSuper = true) | ||
| 21 | +@ToString(callSuper = true) | ||
| 22 | +@Builder | ||
| 23 | +@NoArgsConstructor | ||
| 24 | +@AllArgsConstructor | ||
| 25 | +public class SensitiveWordDO extends BaseDO { | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 编号 | ||
| 29 | + */ | ||
| 30 | + @TableId | ||
| 31 | + private Long id; | ||
| 32 | + /** | ||
| 33 | + * 敏感词 | ||
| 34 | + */ | ||
| 35 | + private String name; | ||
| 36 | + /** | ||
| 37 | + * 描述 | ||
| 38 | + */ | ||
| 39 | + private String description; | ||
| 40 | + /** | ||
| 41 | + * 标签数组 | ||
| 42 | + * | ||
| 43 | + * 用于实现不同的业务场景下,需要使用不同标签的敏感词。 | ||
| 44 | + * 例如说,tag 有短信、论坛两种,敏感词 "推广" 在短信下是敏感词,在论坛下不是敏感词。 | ||
| 45 | + * 此时,我们会存储一条敏感词记录,它的 name 为"推广",tag 为短信。 | ||
| 46 | + */ | ||
| 47 | + @TableField(typeHandler = StringLiSTTypeHandler.class) | ||
| 48 | + private List<String> tags; | ||
| 49 | + /** | ||
| 50 | + * 状态 | ||
| 51 | + * | ||
| 52 | + * 枚举 {@link CommonStatusEnum} | ||
| 53 | + */ | ||
| 54 | + private Integer status; | ||
| 55 | + | ||
| 56 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sensitiveword/SensitiveWordMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.dal.mysql.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||
| 9 | +import org.apache.ibatis.annotations.Mapper; | ||
| 10 | +import org.apache.ibatis.annotations.Select; | ||
| 11 | + | ||
| 12 | +import java.util.Date; | ||
| 13 | +import java.util.List; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * 敏感词 Mapper | ||
| 17 | + * | ||
| 18 | + * @author 永不言败 | ||
| 19 | + */ | ||
| 20 | +@Mapper | ||
| 21 | +public interface SensitiveWordMapper extends BaseMapperX<SensitiveWordDO> { | ||
| 22 | + | ||
| 23 | + default PageResult<SensitiveWordDO> selectPage(SensitiveWordPageReqVO reqVO) { | ||
| 24 | + return selectPage(reqVO, new LambdaQueryWrapperX<SensitiveWordDO>() | ||
| 25 | + .likeIfPresent(SensitiveWordDO::getName, reqVO.getName()) | ||
| 26 | + .likeIfPresent(SensitiveWordDO::getTags, reqVO.getTag()) | ||
| 27 | + .eqIfPresent(SensitiveWordDO::getStatus, reqVO.getStatus()) | ||
| 28 | + .betweenIfPresent(SensitiveWordDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) | ||
| 29 | + .orderByDesc(SensitiveWordDO::getId)); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + default List<SensitiveWordDO> selectList(SensitiveWordExportReqVO reqVO) { | ||
| 33 | + return selectList(new LambdaQueryWrapperX<SensitiveWordDO>() | ||
| 34 | + .likeIfPresent(SensitiveWordDO::getName, reqVO.getName()) | ||
| 35 | + .likeIfPresent(SensitiveWordDO::getTags, reqVO.getTag()) | ||
| 36 | + .eqIfPresent(SensitiveWordDO::getStatus, reqVO.getStatus()) | ||
| 37 | + .betweenIfPresent(SensitiveWordDO::getCreateTime, reqVO.getBeginCreateTime(), reqVO.getEndCreateTime()) | ||
| 38 | + .orderByDesc(SensitiveWordDO::getId)); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + default SensitiveWordDO selectByName(String name) { | ||
| 42 | + return selectOne(SensitiveWordDO::getName, name); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + @Select("SELECT id FROM system_sensitive_word WHERE update_time > #{maxUpdateTime} LIMIT 1") | ||
| 46 | + SensitiveWordDO selectExistsByUpdateTimeAfter(Date maxUpdateTime); | ||
| 47 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sensitiveword/SensitiveWordRefreshConsumer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.consumer.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener; | ||
| 4 | +import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage; | ||
| 5 | +import cn.iocoder.yudao.module.system.service.sensitiveword.SensitiveWordService; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +import javax.annotation.Resource; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 针对 {@link SensitiveWordRefreshMessage} 的消费者 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +@Component | ||
| 17 | +@Slf4j | ||
| 18 | +public class SensitiveWordRefreshConsumer extends AbstractChannelMessageListener<SensitiveWordRefreshMessage> { | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private SensitiveWordService sensitiveWordService; | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + public void onMessage(SensitiveWordRefreshMessage message) { | ||
| 25 | + log.info("[onMessage][收到 SensitiveWord 刷新消息]"); | ||
| 26 | + sensitiveWordService.initLocalCache(); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/message/sensitiveword/SensitiveWordRefreshMessage.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.message.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage; | ||
| 4 | +import lombok.Data; | ||
| 5 | +import lombok.EqualsAndHashCode; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 敏感词的刷新 Message | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +@EqualsAndHashCode(callSuper = true) | ||
| 12 | +public class SensitiveWordRefreshMessage extends AbstractChannelMessage { | ||
| 13 | + | ||
| 14 | + @Override | ||
| 15 | + public String getChannel() { | ||
| 16 | + return "system.sensitive-word.refresh"; | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sensitiveword/SensitiveWordProducer.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.mq.producer.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate; | ||
| 4 | +import cn.iocoder.yudao.module.system.mq.message.sensitiveword.SensitiveWordRefreshMessage; | ||
| 5 | +import org.springframework.stereotype.Component; | ||
| 6 | + | ||
| 7 | +import javax.annotation.Resource; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 敏感词相关的 Producer | ||
| 11 | + */ | ||
| 12 | +@Component | ||
| 13 | +public class SensitiveWordProducer { | ||
| 14 | + | ||
| 15 | + @Resource | ||
| 16 | + private RedisMQTemplate redisMQTemplate; | ||
| 17 | + | ||
| 18 | + /** | ||
| 19 | + * 发送 {@link SensitiveWordRefreshMessage} 消息 | ||
| 20 | + */ | ||
| 21 | + public void sendSensitiveWordRefreshMessage() { | ||
| 22 | + SensitiveWordRefreshMessage message = new SensitiveWordRefreshMessage(); | ||
| 23 | + redisMQTemplate.send(message); | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 4 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; | ||
| 5 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||
| 9 | + | ||
| 10 | +import javax.validation.Valid; | ||
| 11 | +import java.util.List; | ||
| 12 | +import java.util.Set; | ||
| 13 | + | ||
| 14 | +/** | ||
| 15 | + * 敏感词 Service 接口 | ||
| 16 | + * | ||
| 17 | + * @author 永不言败 | ||
| 18 | + */ | ||
| 19 | +public interface SensitiveWordService { | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * 初始化本地缓存 | ||
| 23 | + */ | ||
| 24 | + void initLocalCache(); | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * 创建敏感词 | ||
| 28 | + * | ||
| 29 | + * @param createReqVO 创建信息 | ||
| 30 | + * @return 编号 | ||
| 31 | + */ | ||
| 32 | + Long createSensitiveWord(@Valid SensitiveWordCreateReqVO createReqVO); | ||
| 33 | + | ||
| 34 | + /** | ||
| 35 | + * 更新敏感词 | ||
| 36 | + * | ||
| 37 | + * @param updateReqVO 更新信息 | ||
| 38 | + */ | ||
| 39 | + void updateSensitiveWord(@Valid SensitiveWordUpdateReqVO updateReqVO); | ||
| 40 | + | ||
| 41 | + /** | ||
| 42 | + * 删除敏感词 | ||
| 43 | + * | ||
| 44 | + * @param id 编号 | ||
| 45 | + */ | ||
| 46 | + void deleteSensitiveWord(Long id); | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 获得敏感词 | ||
| 50 | + * | ||
| 51 | + * @param id 编号 | ||
| 52 | + * @return 敏感词 | ||
| 53 | + */ | ||
| 54 | + SensitiveWordDO getSensitiveWord(Long id); | ||
| 55 | + | ||
| 56 | + /** | ||
| 57 | + * 获得敏感词列表 | ||
| 58 | + * | ||
| 59 | + * @return 敏感词列表 | ||
| 60 | + */ | ||
| 61 | + List<SensitiveWordDO> getSensitiveWordList(); | ||
| 62 | + | ||
| 63 | + /** | ||
| 64 | + * 获得敏感词分页 | ||
| 65 | + * | ||
| 66 | + * @param pageReqVO 分页查询 | ||
| 67 | + * @return 敏感词分页 | ||
| 68 | + */ | ||
| 69 | + PageResult<SensitiveWordDO> getSensitiveWordPage(SensitiveWordPageReqVO pageReqVO); | ||
| 70 | + | ||
| 71 | + /** | ||
| 72 | + * 获得敏感词列表, 用于 Excel 导出 | ||
| 73 | + * | ||
| 74 | + * @param exportReqVO 查询条件 | ||
| 75 | + * @return 敏感词列表 | ||
| 76 | + */ | ||
| 77 | + List<SensitiveWordDO> getSensitiveWordList(SensitiveWordExportReqVO exportReqVO); | ||
| 78 | + | ||
| 79 | + /** | ||
| 80 | + * 获得所有敏感词的标签数组 | ||
| 81 | + * | ||
| 82 | + * @return 标签数组 | ||
| 83 | + */ | ||
| 84 | + Set<String> getSensitiveWordTags(); | ||
| 85 | + | ||
| 86 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/service/sensitiveword/SensitiveWordServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.service.sensitiveword; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 6 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordCreateReqVO; | ||
| 7 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordExportReqVO; | ||
| 8 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordPageReqVO; | ||
| 9 | +import cn.iocoder.yudao.module.system.controller.admin.sensitiveword.vo.SensitiveWordUpdateReqVO; | ||
| 10 | +import cn.iocoder.yudao.module.system.convert.sensitiveword.SensitiveWordConvert; | ||
| 11 | +import cn.iocoder.yudao.module.system.dal.dataobject.sensitiveword.SensitiveWordDO; | ||
| 12 | +import cn.iocoder.yudao.module.system.dal.mysql.sensitiveword.SensitiveWordMapper; | ||
| 13 | +import cn.iocoder.yudao.module.system.mq.producer.sensitiveword.SensitiveWordProducer; | ||
| 14 | +import lombok.Getter; | ||
| 15 | +import lombok.extern.slf4j.Slf4j; | ||
| 16 | +import org.springframework.context.annotation.Lazy; | ||
| 17 | +import org.springframework.scheduling.annotation.Scheduled; | ||
| 18 | +import org.springframework.stereotype.Service; | ||
| 19 | +import org.springframework.validation.annotation.Validated; | ||
| 20 | + | ||
| 21 | +import javax.annotation.PostConstruct; | ||
| 22 | +import javax.annotation.Resource; | ||
| 23 | +import java.util.*; | ||
| 24 | + | ||
| 25 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 26 | +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * 敏感词 Service 实现类 | ||
| 30 | + * | ||
| 31 | + * @author 永不言败 | ||
| 32 | + */ | ||
| 33 | +@Service | ||
| 34 | +@Slf4j | ||
| 35 | +@Validated | ||
| 36 | +public class SensitiveWordServiceImpl implements SensitiveWordService { | ||
| 37 | + | ||
| 38 | + /** | ||
| 39 | + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 | ||
| 40 | + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 | ||
| 41 | + */ | ||
| 42 | + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; | ||
| 43 | + | ||
| 44 | + /** | ||
| 45 | + * 敏感词缓存 | ||
| 46 | + * key:敏感词编号 {@link SensitiveWordDO#getId()} | ||
| 47 | + * <p> | ||
| 48 | + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 | ||
| 49 | + */ | ||
| 50 | + @Getter | ||
| 51 | + private volatile Map<Long, SensitiveWordDO> sensitiveWordCache = Collections.emptyMap(); | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * 敏感词标签缓存 | ||
| 55 | + * key:敏感词编号 {@link SensitiveWordDO#getId()} | ||
| 56 | + * <p> | ||
| 57 | + * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 | ||
| 58 | + */ | ||
| 59 | + @Getter | ||
| 60 | + private volatile Set<String> sensitiveWordTagsCache = Collections.emptySet(); | ||
| 61 | + | ||
| 62 | + /** | ||
| 63 | + * 缓存敏感词的最大更新时间,用于后续的增量轮询,判断是否有更新 | ||
| 64 | + */ | ||
| 65 | + @Getter | ||
| 66 | + private volatile Date maxUpdateTime; | ||
| 67 | + | ||
| 68 | + @Resource | ||
| 69 | + private SensitiveWordMapper sensitiveWordMapper; | ||
| 70 | + | ||
| 71 | + @Resource | ||
| 72 | + private SensitiveWordProducer sensitiveWordProducer; | ||
| 73 | + | ||
| 74 | + @Resource | ||
| 75 | + @Lazy | ||
| 76 | + private SensitiveWordService self; | ||
| 77 | + | ||
| 78 | + /** | ||
| 79 | + * 初始化 {@link #sensitiveWordCache} 缓存 | ||
| 80 | + */ | ||
| 81 | + @Override | ||
| 82 | + @PostConstruct | ||
| 83 | + public void initLocalCache() { | ||
| 84 | + // 获取敏感词列表,如果有更新 | ||
| 85 | + List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime); | ||
| 86 | + if (CollUtil.isEmpty(sensitiveWordList)) { | ||
| 87 | + return; | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + // 写入 sensitiveWordTagsCache 缓存 | ||
| 91 | + Set<String> tags = new HashSet<>(); | ||
| 92 | + sensitiveWordList.forEach(word -> tags.addAll(word.getTags())); | ||
| 93 | + sensitiveWordTagsCache = tags; | ||
| 94 | + // 写入 sensitiveWordCache 缓存 | ||
| 95 | + sensitiveWordCache = CollectionUtils.convertMap(sensitiveWordList, SensitiveWordDO::getId); | ||
| 96 | + maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime); | ||
| 97 | + log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size()); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | ||
| 101 | + public void schedulePeriodicRefresh() { | ||
| 102 | + self.initLocalCache(); | ||
| 103 | + } | ||
| 104 | + | ||
| 105 | + /** | ||
| 106 | + * 如果敏感词发生变化,从数据库中获取最新的全量敏感词。 | ||
| 107 | + * 如果未发生变化,则返回空 | ||
| 108 | + * | ||
| 109 | + * @param maxUpdateTime 当前敏感词的最大更新时间 | ||
| 110 | + * @return 敏感词列表 | ||
| 111 | + */ | ||
| 112 | + private List<SensitiveWordDO> loadSensitiveWordIfUpdate(Date maxUpdateTime) { | ||
| 113 | + // 第一步,判断是否要更新。 | ||
| 114 | + // 如果更新时间为空,说明 DB 一定有新数据 | ||
| 115 | + if (maxUpdateTime == null) { | ||
| 116 | + log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]"); | ||
| 117 | + } else { // 判断数据库中是否有更新的敏感词 | ||
| 118 | + if (sensitiveWordMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) { | ||
| 119 | + return null; | ||
| 120 | + } | ||
| 121 | + log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]"); | ||
| 122 | + } | ||
| 123 | + // 第二步,如果有更新,则从数据库加载所有敏感词 | ||
| 124 | + return sensitiveWordMapper.selectList(); | ||
| 125 | + } | ||
| 126 | + | ||
| 127 | + @Override | ||
| 128 | + public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) { | ||
| 129 | + // 校验唯一性 | ||
| 130 | + checkSensitiveWordNameUnique(null, createReqVO.getName()); | ||
| 131 | + // 插入 | ||
| 132 | + SensitiveWordDO sensitiveWord = SensitiveWordConvert.INSTANCE.convert(createReqVO); | ||
| 133 | + sensitiveWordMapper.insert(sensitiveWord); | ||
| 134 | + // 发送消息,刷新缓存 | ||
| 135 | + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); | ||
| 136 | + return sensitiveWord.getId(); | ||
| 137 | + } | ||
| 138 | + | ||
| 139 | + @Override | ||
| 140 | + public void updateSensitiveWord(SensitiveWordUpdateReqVO updateReqVO) { | ||
| 141 | + // 校验唯一性 | ||
| 142 | + checkSensitiveWordExists(updateReqVO.getId()); | ||
| 143 | + checkSensitiveWordNameUnique(updateReqVO.getId(), updateReqVO.getName()); | ||
| 144 | + // 更新 | ||
| 145 | + SensitiveWordDO updateObj = SensitiveWordConvert.INSTANCE.convert(updateReqVO); | ||
| 146 | + sensitiveWordMapper.updateById(updateObj); | ||
| 147 | + // 发送消息,刷新缓存 | ||
| 148 | + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); | ||
| 149 | + } | ||
| 150 | + | ||
| 151 | + @Override | ||
| 152 | + public void deleteSensitiveWord(Long id) { | ||
| 153 | + // 校验存在 | ||
| 154 | + checkSensitiveWordExists(id); | ||
| 155 | + // 删除 | ||
| 156 | + sensitiveWordMapper.deleteById(id); | ||
| 157 | + // 发送消息,刷新缓存 | ||
| 158 | + sensitiveWordProducer.sendSensitiveWordRefreshMessage(); | ||
| 159 | + } | ||
| 160 | + | ||
| 161 | + private void checkSensitiveWordNameUnique(Long id, String name) { | ||
| 162 | + SensitiveWordDO word = sensitiveWordMapper.selectByName(name); | ||
| 163 | + if (word == null) { | ||
| 164 | + return; | ||
| 165 | + } | ||
| 166 | + // 如果 id 为空,说明不用比较是否为相同 id 的敏感词 | ||
| 167 | + if (id == null) { | ||
| 168 | + throw exception(SENSITIVE_WORD_EXISTS); | ||
| 169 | + } | ||
| 170 | + if (!word.getId().equals(id)) { | ||
| 171 | + throw exception(SENSITIVE_WORD_EXISTS); | ||
| 172 | + } | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + private void checkSensitiveWordExists(Long id) { | ||
| 176 | + if (sensitiveWordMapper.selectById(id) == null) { | ||
| 177 | + throw exception(SENSITIVE_WORD_NOT_EXISTS); | ||
| 178 | + } | ||
| 179 | + } | ||
| 180 | + | ||
| 181 | + @Override | ||
| 182 | + public SensitiveWordDO getSensitiveWord(Long id) { | ||
| 183 | + return sensitiveWordMapper.selectById(id); | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + @Override | ||
| 187 | + public List<SensitiveWordDO> getSensitiveWordList() { | ||
| 188 | + return sensitiveWordMapper.selectList(); | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + @Override | ||
| 192 | + public PageResult<SensitiveWordDO> getSensitiveWordPage(SensitiveWordPageReqVO pageReqVO) { | ||
| 193 | + return sensitiveWordMapper.selectPage(pageReqVO); | ||
| 194 | + } | ||
| 195 | + | ||
| 196 | + @Override | ||
| 197 | + public List<SensitiveWordDO> getSensitiveWordList(SensitiveWordExportReqVO exportReqVO) { | ||
| 198 | + return sensitiveWordMapper.selectList(exportReqVO); | ||
| 199 | + } | ||
| 200 | + | ||
| 201 | + @Override | ||
| 202 | + public Set<String> getSensitiveWordTags() { | ||
| 203 | + return sensitiveWordTagsCache; | ||
| 204 | + } | ||
| 205 | + | ||
| 206 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/collection/SimpleTrie.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.system.util.collection; | ||
| 2 | + | ||
| 3 | +import java.util.*; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 基于前缀树,实现敏感词的校验 | ||
| 7 | + * <p> | ||
| 8 | + * 相比 Apache Common 提供的 PatriciaTrie 来说,性能可能会更加好一些。 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +@SuppressWarnings("unchecked") | ||
| 13 | +public class SimpleTrie { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 一个敏感词结束后对应的 key | ||
| 17 | + */ | ||
| 18 | + private static final Character CHARACTER_END = '\0'; | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 使用敏感词,构建的前缀树 | ||
| 22 | + */ | ||
| 23 | + private final Map<Character, Object> children; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 基于字符串,构建前缀树 | ||
| 27 | + * | ||
| 28 | + * @param strs 字符串数组 | ||
| 29 | + */ | ||
| 30 | + public SimpleTrie(List<String> strs) { | ||
| 31 | + children = new HashMap<>(); | ||
| 32 | + // 构建树 | ||
| 33 | + Collections.sort(strs); // 排序,优先使用较短的前缀 | ||
| 34 | + for (String str : strs) { | ||
| 35 | + Map<Character, Object> child = children; | ||
| 36 | + // 遍历每个字符 | ||
| 37 | + for (Character c : str.toCharArray()) { | ||
| 38 | + // 如果已经到达结束,就没必要在添加更长的敏感词。 | ||
| 39 | + // 例如说,有两个敏感词是:吃饭啊、吃饭。输入一句话是 “我要吃饭啊”,则只要匹配到 “吃饭” 这个敏感词即可。 | ||
| 40 | + if (child.containsKey(CHARACTER_END)) { | ||
| 41 | + break; | ||
| 42 | + } | ||
| 43 | + if (!child.containsKey(c)) { | ||
| 44 | + child.put(c, new HashMap<>()); | ||
| 45 | + } | ||
| 46 | + child = (Map<Character, Object>) child.get(c); | ||
| 47 | + } | ||
| 48 | + // 结束 | ||
| 49 | + child.put(CHARACTER_END, null); | ||
| 50 | + } | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + /** | ||
| 54 | + * 验证文本是否合法,即不包含敏感词 | ||
| 55 | + * | ||
| 56 | + * @param text 文本 | ||
| 57 | + * @return 是否 ok | ||
| 58 | + */ | ||
| 59 | + public boolean isValid(String text) { | ||
| 60 | + // 遍历 text,使用每一个 [i, n) 段的字符串,使用 children 前缀树匹配,是否包含敏感词 | ||
| 61 | + for (int i = 0; i < text.length() - 1; i++) { | ||
| 62 | + Map<Character, Object> child = (Map<Character, Object>) children.get(text.charAt(i)); | ||
| 63 | + if (child == null) { | ||
| 64 | + continue; | ||
| 65 | + } | ||
| 66 | + boolean ok = recursion(text, i + 1, child); | ||
| 67 | + if (!ok) { | ||
| 68 | + return false; | ||
| 69 | + } | ||
| 70 | + } | ||
| 71 | + return true; | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * 验证文本从指定位置开始,是否包含某个敏感词 | ||
| 76 | + * | ||
| 77 | + * @param text 文本 | ||
| 78 | + * @param index 开始位置 | ||
| 79 | + * @param child 节点(当前遍历到的) | ||
| 80 | + * @return 是否包含 | ||
| 81 | + */ | ||
| 82 | + private boolean recursion(String text, int index, Map<Character, Object> child) { | ||
| 83 | + if (index == text.length()) { | ||
| 84 | + return true; | ||
| 85 | + } | ||
| 86 | + child = (Map<Character, Object>) child.get(text.charAt(index)); | ||
| 87 | + return child == null || !child.containsKey(CHARACTER_END) && recursion(text, ++index, child); | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + /** | ||
| 91 | + * 获得文本所包含的不合法的敏感词 | ||
| 92 | + * | ||
| 93 | + * 注意,才当即最短匹配原则。例如说:当敏感词存在 “煞笔”,“煞笔二货 ”时,只会返回 “煞笔”。 | ||
| 94 | + * | ||
| 95 | + * @param text 文本 | ||
| 96 | + * @return 匹配的敏感词 | ||
| 97 | + */ | ||
| 98 | + public List<String> validate(String text) { | ||
| 99 | + Set<String> results = new HashSet<>(); | ||
| 100 | + for (int i = 0; i < text.length() - 1; i++) { | ||
| 101 | + Character c = text.charAt(i); | ||
| 102 | + Map<Character, Object> child = (Map<Character, Object>) children.get(c); | ||
| 103 | + if (child == null) { | ||
| 104 | + continue; | ||
| 105 | + } | ||
| 106 | + StringBuilder result = new StringBuilder().append(c); | ||
| 107 | + boolean ok = recursionWithResult(text, i + 1, child, result); | ||
| 108 | + if (!ok) { | ||
| 109 | + results.add(result.toString()); | ||
| 110 | + } | ||
| 111 | + } | ||
| 112 | + return new ArrayList<>(results); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + /** | ||
| 116 | + * 返回文本从 index 开始的敏感词,并使用 StringBuilder 参数进行返回 | ||
| 117 | + * | ||
| 118 | + * 逻辑和 {@link #recursion(String, int, Map)} 是一致,只是多了 result 返回结果 | ||
| 119 | + * | ||
| 120 | + * @param text 文本 | ||
| 121 | + * @param index 开始未知 | ||
| 122 | + * @param child 节点(当前遍历到的) | ||
| 123 | + * @param result 返回敏感词 | ||
| 124 | + * @return 是否有敏感词 | ||
| 125 | + */ | ||
| 126 | + @SuppressWarnings("unchecked") | ||
| 127 | + private static boolean recursionWithResult(String text, int index, Map<Character, Object> child, StringBuilder result) { | ||
| 128 | + if (index == text.length()) { | ||
| 129 | + return true; | ||
| 130 | + } | ||
| 131 | + Character c = text.charAt(index); | ||
| 132 | + child = (Map<Character, Object>) child.get(c); | ||
| 133 | + if (child == null) { | ||
| 134 | + return true; | ||
| 135 | + } | ||
| 136 | + if (child.containsKey(CHARACTER_END)) { | ||
| 137 | + result.append(c); | ||
| 138 | + return false; | ||
| 139 | + } | ||
| 140 | + return recursionWithResult(text, ++index, child, result.append(c)); | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | +} |
yudao-module-system/yudao-module-system-impl/src/main/java/cn/iocoder/yudao/module/system/util/package-info.java
0 → 100644
yudao-module-system/yudao-module-system-impl/src/test/resources/sql/clean.sql
| @@ -17,3 +17,4 @@ DELETE FROM "system_error_code"; | @@ -17,3 +17,4 @@ DELETE FROM "system_error_code"; | ||
| 17 | DELETE FROM "system_social_user"; | 17 | DELETE FROM "system_social_user"; |
| 18 | DELETE FROM "system_tenant"; | 18 | DELETE FROM "system_tenant"; |
| 19 | DELETE FROM "system_tenant_package"; | 19 | DELETE FROM "system_tenant_package"; |
| 20 | +DELETE FROM "system_sensitive_word"; |
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" ( | @@ -426,3 +426,17 @@ CREATE TABLE IF NOT EXISTS "system_tenant_package" ( | ||
| 426 | "deleted" bit NOT NULL DEFAULT FALSE, | 426 | "deleted" bit NOT NULL DEFAULT FALSE, |
| 427 | PRIMARY KEY ("id") | 427 | PRIMARY KEY ("id") |
| 428 | ) COMMENT '租户套餐表'; | 428 | ) COMMENT '租户套餐表'; |
| 429 | + | ||
| 430 | +CREATE TABLE IF NOT EXISTS "system_sensitive_word" ( | ||
| 431 | + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, | ||
| 432 | + "name" varchar(255) NOT NULL, | ||
| 433 | + "tags" varchar(1024) NOT NULL, | ||
| 434 | + "status" bit NOT NULL DEFAULT FALSE, | ||
| 435 | + "description" varchar(512), | ||
| 436 | + "creator" varchar(64) DEFAULT '', | ||
| 437 | + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| 438 | + "updater" varchar(64) DEFAULT '', | ||
| 439 | + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, | ||
| 440 | + "deleted" bit NOT NULL DEFAULT FALSE, | ||
| 441 | + PRIMARY KEY ("id") | ||
| 442 | +) COMMENT '系统敏感词'; |