master:商品新增及编辑调整图包压缩状态;
parent
cb7d85cfe0
commit
09dbbb2c24
|
|
@ -186,6 +186,13 @@
|
|||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- html标签校验 -->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.16.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -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<String> findProhibitedWords(String prodTitle) {
|
||||
List<String> matchedWords = new ArrayList<>();
|
||||
for (String word : AdValidator.PROHIBITED_WORDS_SET) {
|
||||
if (prodTitle.contains(word)) {
|
||||
matchedWords.add(word);
|
||||
}
|
||||
}
|
||||
return matchedWords;
|
||||
}
|
||||
|
||||
// 使用 HashSet 存储违禁词(全局唯一,避免重复初始化)
|
||||
private static final Set<String> PROHIBITED_WORDS_SET = new HashSet<>(Arrays.asList(
|
||||
"最", "最佳", "最优", "最好", "最大", "最高", "最便宜", "最时尚", "最舒适", "最流行",
|
||||
"最先进", "最顶级", "首选", "唯一", "独家", "全网第一", "行业领先", "销量冠军",
|
||||
"100%", "绝对", "无敌", "完美", "终极", "极致", "巅峰", "史无前例", "遥遥领先",
|
||||
"微信", "QQ", "加V", "私聊", "联系客服", "电话", "下单", "购买",
|
||||
"小红书", "抖音", "微博", "官网", "网址", "二维码"
|
||||
));
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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<String> 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<StoreProduct>()
|
||||
.eq(StoreProduct::getId, storeProdId).eq(StoreProduct::getDelFlag, Constants.UNDELETED)))
|
||||
.orElseThrow(() -> new ServiceException("档口商品不存在!", HttpStatus.ERROR));
|
||||
|
|
@ -310,7 +317,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
|||
Map<String, Long> fileMap = fileList.stream().collect(Collectors.toMap(SysFile::getFileName, SysFile::getId));
|
||||
// 档口文件(商品主图、主图视频、下载的商品详情)
|
||||
List<StoreProductFile> 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<String, Long> fileMap = fileList.stream().collect(Collectors.toMap(SysFile::getFileName, SysFile::getId));
|
||||
// 档口文件(商品主图、主图视频、下载的商品详情)
|
||||
List<StoreProductFile> 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<PicPackSimpleDTO> prepareGetPicPackDownloadUrl(Long storeProductId) {
|
||||
Assert.notNull(storeProductId);
|
||||
List<Long> 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<SysFile> 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<StoreProductColor> colorList, Long storeProdId) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue