master:商品新增及编辑调整图包压缩状态;

pull/1121/head
liujiang 2025-08-01 17:42:42 +08:00
parent cb7d85cfe0
commit 09dbbb2c24
6 changed files with 192 additions and 48 deletions

View File

@ -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>

View File

@ -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", "私聊", "联系客服", "电话", "下单", "购买",
"小红书", "抖音", "微博", "官网", "网址", "二维码"
));
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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) {