From 09dbbb2c240b94b5d5bdde69e94bb0829142579b Mon Sep 17 00:00:00 2001
From: liujiang <569804566@qq.com>
Date: Fri, 1 Aug 2025 17:42:42 +0800
Subject: [PATCH] =?UTF-8?q?master=EF=BC=9A=E5=95=86=E5=93=81=E6=96=B0?=
=?UTF-8?q?=E5=A2=9E=E5=8F=8A=E7=BC=96=E8=BE=91=E8=B0=83=E6=95=B4=E5=9B=BE?=
=?UTF-8?q?=E5=8C=85=E5=8E=8B=E7=BC=A9=E7=8A=B6=E6=80=81=EF=BC=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ruoyi-common/pom.xml | 7 ++
.../com/ruoyi/common/utils/AdValidator.java | 52 ++++++++++++++
.../com/ruoyi/common/utils/HtmlValidator.java | 51 ++++++++++++++
.../ruoyi/xkt/domain/StoreProductFile.java | 24 ++-----
.../xkt/enums/StoreProdFileZipStatus.java | 37 ++++++++++
.../service/impl/StoreProductServiceImpl.java | 69 +++++++++++--------
6 files changed, 192 insertions(+), 48 deletions(-)
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/AdValidator.java
create mode 100644 ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlValidator.java
create mode 100644 xkt/src/main/java/com/ruoyi/xkt/enums/StoreProdFileZipStatus.java
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index 92d9535c1..d6fc361e9 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -186,6 +186,13 @@
javax.servlet-api
+
+
+ org.jsoup
+ jsoup
+ 1.16.1
+
+
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/AdValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/AdValidator.java
new file mode 100644
index 000000000..a91a617d0
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/AdValidator.java
@@ -0,0 +1,52 @@
+package com.ruoyi.common.utils;
+
+import java.util.*;
+
+/**
+ * @author liujiang
+ * @version v1.0
+ * @date 2025/8/1 14:35
+ */
+public class AdValidator {
+
+ /**
+ * 检查标题是否包含任意敏感词(全词匹配)
+ *
+ * @param prodTitle 待校验的标题
+ * @return true-包含敏感词, false-不包含
+ */
+ public static boolean containsProhibitedWord(String prodTitle) {
+ for (String word : AdValidator.PROHIBITED_WORDS_SET) {
+ if (prodTitle.contains(word)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * 检查标题并返回匹配到的所有敏感词
+ *
+ * @param prodTitle 待校验的标题
+ * @return 匹配到的敏感词列表(若无则返回空列表)
+ */
+ public static List findProhibitedWords(String prodTitle) {
+ List matchedWords = new ArrayList<>();
+ for (String word : AdValidator.PROHIBITED_WORDS_SET) {
+ if (prodTitle.contains(word)) {
+ matchedWords.add(word);
+ }
+ }
+ return matchedWords;
+ }
+
+ // 使用 HashSet 存储违禁词(全局唯一,避免重复初始化)
+ private static final Set PROHIBITED_WORDS_SET = new HashSet<>(Arrays.asList(
+ "最", "最佳", "最优", "最好", "最大", "最高", "最便宜", "最时尚", "最舒适", "最流行",
+ "最先进", "最顶级", "首选", "唯一", "独家", "全网第一", "行业领先", "销量冠军",
+ "100%", "绝对", "无敌", "完美", "终极", "极致", "巅峰", "史无前例", "遥遥领先",
+ "微信", "QQ", "加V", "私聊", "联系客服", "电话", "下单", "购买",
+ "小红书", "抖音", "微博", "官网", "网址", "二维码"
+ ));
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlValidator.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlValidator.java
new file mode 100644
index 000000000..646d2b048
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/HtmlValidator.java
@@ -0,0 +1,51 @@
+package com.ruoyi.common.utils;
+
+import org.jsoup.Jsoup;
+import org.jsoup.safety.Safelist;
+
+/**
+ * @author liujiang
+ * @version v1.0
+ * @date 2025/8/1 16:25
+ */
+public class HtmlValidator {
+
+ // 定义允许的 HTML 标签和属性(基于 UniApp rich-text 组件规则)
+ private static final Safelist ALLOWED_HTML = Safelist.relaxed()
+ .addTags(
+ "a", "abbr", "address", "article", "aside", "b", "bdi", "bdo", "big", "blockquote",
+ "br", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del", "div",
+ "dl", "dt", "em", "fieldset", "font", "footer", "h1", "h2", "h3", "h4", "h5", "h6",
+ "header", "hr", "i", "img", "ins", "label", "legend", "li", "mark", "nav", "ol", "p",
+ "pre", "q", "rt", "ruby", "s", "section", "small", "span", "strong", "sub", "sup",
+ "table", "tbody", "td", "tfoot", "th", "thead", "tr", "tt", "u", "ul"
+ )
+ .addAttributes("a", "href", "title", "target")
+ .addAttributes("img", "src", "alt", "height", "width")
+ .addAttributes("table", "width", "height", "colspan", "rowspan", "align", "valign")
+ .addAttributes("td", "colspan", "rowspan", "align", "valign")
+ .addAttributes("th", "colspan", "rowspan", "align", "valign")
+ .addAttributes("font", "color", "face", "size")
+ .addAttributes(":all", "class", "id", "style"); // 允许全局属性
+
+ /**
+ * 检查 HTML 是否合法(仅包含允许的标签和属性)
+ *
+ * @param html 输入的富文本
+ * @return true=合法,false=包含非法标签或属性
+ */
+ public static boolean isValidHtml(String html) {
+ return Jsoup.isValid(html, ALLOWED_HTML);
+ }
+
+ /**
+ * 清理 HTML,移除非法标签和属性
+ *
+ * @param html 输入的富文本
+ * @return 清理后的安全 HTML
+ */
+ public static String sanitizeHtml(String html) {
+ return Jsoup.clean(html, ALLOWED_HTML);
+ }
+
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/domain/StoreProductFile.java b/xkt/src/main/java/com/ruoyi/xkt/domain/StoreProductFile.java
index 6d6d450bb..1f724dc6f 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/domain/StoreProductFile.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/domain/StoreProductFile.java
@@ -6,8 +6,6 @@ import com.ruoyi.common.core.domain.XktBaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
import java.math.BigDecimal;
@@ -65,21 +63,9 @@ public class StoreProductFile extends XktBaseEntity {
@Excel(name = "排序")
private Integer orderNum;
- @Override
- public String toString() {
- return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
- .append("id", getId())
- .append("storeProdId", getStoreProdId())
- .append("fileId", getFileId())
- .append("fileType", getFileType())
- .append("fileSize", getFileSize())
- .append("orderNum", getOrderNum())
- .append("version", getVersion())
- .append("delFlag", getDelFlag())
- .append("createBy", getCreateBy())
- .append("createTime", getCreateTime())
- .append("updateBy", getUpdateBy())
- .append("updateTime", getUpdateTime())
- .toString();
- }
+ /**
+ * 图包处理状态 图包状态[1:非图包/不处理 2:待处理 3:处理中 4:已处理 5:处理异常]
+ */
+ private Integer picZipStatus;
+
}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/enums/StoreProdFileZipStatus.java b/xkt/src/main/java/com/ruoyi/xkt/enums/StoreProdFileZipStatus.java
new file mode 100644
index 000000000..5eb225d8a
--- /dev/null
+++ b/xkt/src/main/java/com/ruoyi/xkt/enums/StoreProdFileZipStatus.java
@@ -0,0 +1,37 @@
+package com.ruoyi.xkt.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 商品图包压缩类型
+ *
+ * @author liujiang
+ * @date 2025-04-02 23:42
+ */
+@Getter
+@AllArgsConstructor
+public enum StoreProdFileZipStatus {
+
+ NON_ZIP(1, "非图包/不处理"),
+ PENDING(2, "待处理"),
+ PROCESSING(3, "处理中"),
+ PROCESSED(4, "已处理"),
+ FAILED(5, "处理异常");
+
+ private final Integer value;
+ private final String label;
+
+ // 根据 value 获取对应的枚举实例
+ public static StoreProdFileZipStatus of(Integer value) {
+ if (value == null) {
+ throw new IllegalArgumentException("图包状态值不能为空");
+ }
+ for (StoreProdFileZipStatus status : values()) {
+ if (status.value.equals(value)) {
+ return status;
+ }
+ }
+ throw new IllegalArgumentException("无效的图包状态值: " + value);
+ }
+}
diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java
index ec957750b..6bdee2f35 100644
--- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java
+++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java
@@ -25,7 +25,9 @@ import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
+import com.ruoyi.common.utils.AdValidator;
import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.HtmlValidator;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.es.EsClientWrapper;
import com.ruoyi.framework.oss.OSSClientWrapper;
@@ -202,18 +204,19 @@ public class StoreProductServiceImpl implements IStoreProductService {
@Override
@Transactional
public int create(StoreProdDTO createDTO) throws IOException {
-
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
-
// 用户是否为档口管理者或子账户
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(createDTO.getStoreId())) {
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
+ } // 校验标题中是否包含违禁词
+ List prohibitedWords = AdValidator.findProhibitedWords(createDTO.getProdTitle().trim());
+ if (CollectionUtils.isNotEmpty(prohibitedWords)) {
+ throw new ServiceException("商品标题含有违禁词: " + String.join(",", prohibitedWords), HttpStatus.ERROR);
+ }
+ // 校验富文本标签是否合法
+ boolean isValid = HtmlValidator.isValidHtml(createDTO.getDetail());
+ if (!isValid) {
+ // 清理 HTML
+ createDTO.setDetail(HtmlValidator.sanitizeHtml(createDTO.getDetail()));
}
// 组装StoreProduct数据
StoreProduct storeProd = BeanUtil.toBean(createDTO, StoreProduct.class).setVoucherDate(DateUtils.getNowDate())
@@ -247,17 +250,21 @@ public class StoreProductServiceImpl implements IStoreProductService {
@Override
@Transactional
public int update(final Long storeProdId, StoreProdDTO updateDTO) throws IOException {
-
-
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
- // TODO 富文本标签过滤
-
-
+ // 用户是否为档口管理者或子账户
+ if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(updateDTO.getStoreId())) {
+ throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
+ }
+ // 校验标题中是否包含违禁词
+ List prohibitedWords = AdValidator.findProhibitedWords(updateDTO.getProdTitle().trim());
+ if (CollectionUtils.isNotEmpty(prohibitedWords)) {
+ throw new ServiceException("商品标题含有违禁词: " + String.join(",", prohibitedWords), HttpStatus.ERROR);
+ }
+ // 校验富文本标签是否合法
+ boolean isValid = HtmlValidator.isValidHtml(updateDTO.getDetail());
+ if (!isValid) {
+ // 清理 HTML
+ updateDTO.setDetail(HtmlValidator.sanitizeHtml(updateDTO.getDetail()));
+ }
StoreProduct storeProd = Optional.ofNullable(this.storeProdMapper.selectOne(new LambdaQueryWrapper()
.eq(StoreProduct::getId, storeProdId).eq(StoreProduct::getDelFlag, Constants.UNDELETED)))
.orElseThrow(() -> new ServiceException("档口商品不存在!", HttpStatus.ERROR));
@@ -310,7 +317,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
Map fileMap = fileList.stream().collect(Collectors.toMap(SysFile::getFileName, SysFile::getId));
// 档口文件(商品主图、主图视频、下载的商品详情)
List prodFileList = fileDTOList.stream().map(x -> BeanUtil.toBean(x, StoreProductFile.class)
- .setFileId(fileMap.get(x.getFileName())).setStoreProdId(storeProd.getId()).setStoreId(updateDTO.getStoreId()))
+ .setFileId(fileMap.get(x.getFileName())).setStoreProdId(storeProd.getId()).setStoreId(updateDTO.getStoreId())
+ .setPicZipStatus(Objects.equals(x.getFileType(), FileType.DOWNLOAD.getValue())
+ ? StoreProdFileZipStatus.PENDING.getValue() : StoreProdFileZipStatus.NON_ZIP.getValue()))
.collect(Collectors.toList());
this.storeProdFileMapper.insert(prodFileList);
// 档口类目属性
@@ -425,7 +434,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
Map fileMap = fileList.stream().collect(Collectors.toMap(SysFile::getFileName, SysFile::getId));
// 档口文件(商品主图、主图视频、下载的商品详情)
List prodFileList = fileDTOList.stream().map(x -> BeanUtil.toBean(x, StoreProductFile.class)
- .setFileId(fileMap.get(x.getFileName())).setStoreProdId(storeProd.getId()).setStoreId(createDTO.getStoreId()))
+ .setFileId(fileMap.get(x.getFileName())).setStoreProdId(storeProd.getId()).setStoreId(createDTO.getStoreId())
+ .setPicZipStatus(Objects.equals(x.getFileType(), FileType.DOWNLOAD.getValue())
+ ? StoreProdFileZipStatus.PENDING.getValue() : StoreProdFileZipStatus.NON_ZIP.getValue()))
.collect(Collectors.toList());
this.storeProdFileMapper.insert(prodFileList);
// 档口类目属性
@@ -447,9 +458,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
/**
* 新增档口商品颜色等
*
- * @param createDTO 入参
+ * @param createDTO 入参
* @param storeProdId 档口商品ID
- * @param storeId 档口ID
+ * @param storeId 档口ID
*/
private void createProdColor(StoreProdDTO createDTO, Long storeProdId, Long storeId) {
// 处理档口所有颜色
@@ -571,9 +582,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
public List prepareGetPicPackDownloadUrl(Long storeProductId) {
Assert.notNull(storeProductId);
List fileIds = storeProdFileMapper.selectList(Wrappers.lambdaQuery(StoreProductFile.class)
- .eq(StoreProductFile::getStoreProdId, storeProductId)
- .in(StoreProductFile::getFileType, FileType.picPackValues())
- .eq(XktBaseEntity::getDelFlag, UNDELETED))
+ .eq(StoreProductFile::getStoreProdId, storeProductId)
+ .in(StoreProductFile::getFileType, FileType.picPackValues())
+ .eq(XktBaseEntity::getDelFlag, UNDELETED))
.stream()
.map(StoreProductFile::getFileId)
.filter(Objects::nonNull)
@@ -583,7 +594,7 @@ public class StoreProductServiceImpl implements IStoreProductService {
}
List files = fileMapper.selectByIds(fileIds);
return files.stream()
- .filter(o-> UNDELETED.equals(o.getDelFlag()))
+ .filter(o -> UNDELETED.equals(o.getDelFlag()))
.map(o -> {
PicPackSimpleDTO dto = BeanUtil.toBean(o, PicPackSimpleDTO.class);
dto.setFileId(o.getId());
@@ -879,7 +890,7 @@ public class StoreProductServiceImpl implements IStoreProductService {
/**
* 给设置了所有商品优惠的客户创建优惠
*
- * @param colorList 档口商品颜色列表
+ * @param colorList 档口商品颜色列表
* @param storeProdId 档口商品ID
*/
private void createStoreCusDiscount(List colorList, Long storeProdId) {