master:商品新增及编辑调整图包压缩状态;
parent
cb7d85cfe0
commit
09dbbb2c24
|
|
@ -186,6 +186,13 @@
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- html标签校验 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.16.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</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.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
|
@ -65,21 +63,9 @@ public class StoreProductFile extends XktBaseEntity {
|
||||||
@Excel(name = "排序")
|
@Excel(name = "排序")
|
||||||
private Integer orderNum;
|
private Integer orderNum;
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String toString() {
|
* 图包处理状态 图包状态[1:非图包/不处理 2:待处理 3:处理中 4:已处理 5:处理异常]
|
||||||
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
|
*/
|
||||||
.append("id", getId())
|
private Integer picZipStatus;
|
||||||
.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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.ServiceException;
|
||||||
import com.ruoyi.common.exception.user.CaptchaException;
|
import com.ruoyi.common.exception.user.CaptchaException;
|
||||||
import com.ruoyi.common.exception.user.CaptchaExpireException;
|
import com.ruoyi.common.exception.user.CaptchaExpireException;
|
||||||
|
import com.ruoyi.common.utils.AdValidator;
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
|
import com.ruoyi.common.utils.HtmlValidator;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.framework.es.EsClientWrapper;
|
import com.ruoyi.framework.es.EsClientWrapper;
|
||||||
import com.ruoyi.framework.oss.OSSClientWrapper;
|
import com.ruoyi.framework.oss.OSSClientWrapper;
|
||||||
|
|
@ -202,18 +204,19 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int create(StoreProdDTO createDTO) throws IOException {
|
public int create(StoreProdDTO createDTO) throws IOException {
|
||||||
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
// TODO 富文本标签过滤
|
|
||||||
|
|
||||||
// 用户是否为档口管理者或子账户
|
// 用户是否为档口管理者或子账户
|
||||||
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(createDTO.getStoreId())) {
|
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(createDTO.getStoreId())) {
|
||||||
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
|
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数据
|
||||||
StoreProduct storeProd = BeanUtil.toBean(createDTO, StoreProduct.class).setVoucherDate(DateUtils.getNowDate())
|
StoreProduct storeProd = BeanUtil.toBean(createDTO, StoreProduct.class).setVoucherDate(DateUtils.getNowDate())
|
||||||
|
|
@ -247,17 +250,21 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int update(final Long storeProdId, StoreProdDTO updateDTO) throws IOException {
|
public int update(final Long storeProdId, StoreProdDTO updateDTO) throws IOException {
|
||||||
|
// 用户是否为档口管理者或子账户
|
||||||
|
if (!SecurityUtils.isAdmin() && !SecurityUtils.isStoreManagerOrSub(updateDTO.getStoreId())) {
|
||||||
// TODO 富文本标签过滤
|
throw new ServiceException("当前用户非档口管理者或子账号,无权限操作!", HttpStatus.ERROR);
|
||||||
// TODO 富文本标签过滤
|
}
|
||||||
// TODO 富文本标签过滤
|
// 校验标题中是否包含违禁词
|
||||||
// TODO 富文本标签过滤
|
List<String> prohibitedWords = AdValidator.findProhibitedWords(updateDTO.getProdTitle().trim());
|
||||||
// TODO 富文本标签过滤
|
if (CollectionUtils.isNotEmpty(prohibitedWords)) {
|
||||||
// TODO 富文本标签过滤
|
throw new ServiceException("商品标题含有违禁词: " + String.join(",", prohibitedWords), HttpStatus.ERROR);
|
||||||
// TODO 富文本标签过滤
|
}
|
||||||
|
// 校验富文本标签是否合法
|
||||||
|
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>()
|
StoreProduct storeProd = Optional.ofNullable(this.storeProdMapper.selectOne(new LambdaQueryWrapper<StoreProduct>()
|
||||||
.eq(StoreProduct::getId, storeProdId).eq(StoreProduct::getDelFlag, Constants.UNDELETED)))
|
.eq(StoreProduct::getId, storeProdId).eq(StoreProduct::getDelFlag, Constants.UNDELETED)))
|
||||||
.orElseThrow(() -> new ServiceException("档口商品不存在!", HttpStatus.ERROR));
|
.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));
|
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)
|
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());
|
.collect(Collectors.toList());
|
||||||
this.storeProdFileMapper.insert(prodFileList);
|
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));
|
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)
|
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());
|
.collect(Collectors.toList());
|
||||||
this.storeProdFileMapper.insert(prodFileList);
|
this.storeProdFileMapper.insert(prodFileList);
|
||||||
// 档口类目属性
|
// 档口类目属性
|
||||||
|
|
@ -447,9 +458,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
/**
|
/**
|
||||||
* 新增档口商品颜色等
|
* 新增档口商品颜色等
|
||||||
*
|
*
|
||||||
* @param createDTO 入参
|
* @param createDTO 入参
|
||||||
* @param storeProdId 档口商品ID
|
* @param storeProdId 档口商品ID
|
||||||
* @param storeId 档口ID
|
* @param storeId 档口ID
|
||||||
*/
|
*/
|
||||||
private void createProdColor(StoreProdDTO createDTO, Long storeProdId, Long storeId) {
|
private void createProdColor(StoreProdDTO createDTO, Long storeProdId, Long storeId) {
|
||||||
// 处理档口所有颜色
|
// 处理档口所有颜色
|
||||||
|
|
@ -571,9 +582,9 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
public List<PicPackSimpleDTO> prepareGetPicPackDownloadUrl(Long storeProductId) {
|
public List<PicPackSimpleDTO> prepareGetPicPackDownloadUrl(Long storeProductId) {
|
||||||
Assert.notNull(storeProductId);
|
Assert.notNull(storeProductId);
|
||||||
List<Long> fileIds = storeProdFileMapper.selectList(Wrappers.lambdaQuery(StoreProductFile.class)
|
List<Long> fileIds = storeProdFileMapper.selectList(Wrappers.lambdaQuery(StoreProductFile.class)
|
||||||
.eq(StoreProductFile::getStoreProdId, storeProductId)
|
.eq(StoreProductFile::getStoreProdId, storeProductId)
|
||||||
.in(StoreProductFile::getFileType, FileType.picPackValues())
|
.in(StoreProductFile::getFileType, FileType.picPackValues())
|
||||||
.eq(XktBaseEntity::getDelFlag, UNDELETED))
|
.eq(XktBaseEntity::getDelFlag, UNDELETED))
|
||||||
.stream()
|
.stream()
|
||||||
.map(StoreProductFile::getFileId)
|
.map(StoreProductFile::getFileId)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
|
|
@ -583,7 +594,7 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
}
|
}
|
||||||
List<SysFile> files = fileMapper.selectByIds(fileIds);
|
List<SysFile> files = fileMapper.selectByIds(fileIds);
|
||||||
return files.stream()
|
return files.stream()
|
||||||
.filter(o-> UNDELETED.equals(o.getDelFlag()))
|
.filter(o -> UNDELETED.equals(o.getDelFlag()))
|
||||||
.map(o -> {
|
.map(o -> {
|
||||||
PicPackSimpleDTO dto = BeanUtil.toBean(o, PicPackSimpleDTO.class);
|
PicPackSimpleDTO dto = BeanUtil.toBean(o, PicPackSimpleDTO.class);
|
||||||
dto.setFileId(o.getId());
|
dto.setFileId(o.getId());
|
||||||
|
|
@ -879,7 +890,7 @@ public class StoreProductServiceImpl implements IStoreProductService {
|
||||||
/**
|
/**
|
||||||
* 给设置了所有商品优惠的客户创建优惠
|
* 给设置了所有商品优惠的客户创建优惠
|
||||||
*
|
*
|
||||||
* @param colorList 档口商品颜色列表
|
* @param colorList 档口商品颜色列表
|
||||||
* @param storeProdId 档口商品ID
|
* @param storeProdId 档口商品ID
|
||||||
*/
|
*/
|
||||||
private void createStoreCusDiscount(List<StoreProductColor> colorList, Long storeProdId) {
|
private void createStoreCusDiscount(List<StoreProductColor> colorList, Long storeProdId) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue