新增es模糊商品图片匹配功能

This commit is contained in:
liyj 2025-07-18 16:14:35 +08:00
parent 54ad2d6d3c
commit 5891ecd7c0
21 changed files with 1489 additions and 44 deletions

View File

@ -31,6 +31,9 @@ public enum DicEnum {
PRIORITY_MODE_2("2", "自动优先","priorityMode","优先方式","更新时不做商品切割"), PRIORITY_MODE_2("2", "自动优先","priorityMode","优先方式","更新时不做商品切割"),
GOODS_UN_SYNC_SX("1", "白条猪","unSyncGoodsSX","思迅非同步商品","白条猪"), GOODS_UN_SYNC_SX("1", "白条猪","unSyncGoodsSX","思迅非同步商品","白条猪"),
ES_SEARCH_TYPE_1("1", "高亮查询","esSearchType","es查询方式","精准查询"),
ES_SEARCH_TYPE_2("2", "模糊查询","esSearchType","es查询方式","模糊查询"),
; ;
; ;
private String code; private String code;

View File

@ -2,12 +2,14 @@ package com.suisung.mall.common.feignService;
import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.modules.product.ShopProductIndex; import com.suisung.mall.common.modules.product.ShopProductIndex;
import com.suisung.mall.common.pojo.dto.ProductImageSearchDTO;
import com.suisung.mall.common.pojo.dto.ProductRecommendDTO; import com.suisung.mall.common.pojo.dto.ProductRecommendDTO;
import com.suisung.mall.common.pojo.dto.ProductSearchDTO; import com.suisung.mall.common.pojo.dto.ProductSearchDTO;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @program: mall-suite * @program: mall-suite
@ -36,4 +38,8 @@ public interface SearchService {
@PostMapping("/esProduct/batchImport") @PostMapping("/esProduct/batchImport")
CommonResult batchImport(@RequestBody List<ShopProductIndex> shopProductIndexList); CommonResult batchImport(@RequestBody List<ShopProductIndex> shopProductIndexList);
@PostMapping("/esProductImage/searchProductImageList")
Map<String,List<ProductImageSearchDTO>> searchProductImageList(@RequestBody List<ProductImageSearchDTO> esProductImages,@RequestParam("esSearchType") String esSearchType);
} }

View File

@ -0,0 +1,53 @@
package com.suisung.mall.common.pojo.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value = "商品图库搜索", description = "商品图库搜索DTO")
public class ProductImageSearchDTO {
@ApiModelProperty(value = "名称索引关键字(DOT)")
private String barcode;
@ApiModelProperty(value = "产品名称:店铺平台先在对用表中检索后通过id检索,检索使用")
private String productName;
@ApiModelProperty(value = "名称索引关键字(DOT)")
private String cleanName;
@ApiModelProperty(value = "关键字")
private String keywords;
@ApiModelProperty(value = "商品简称")
private String productShortName;
@ApiModelProperty(value = "品牌")
private String brand;
@ApiModelProperty(value = "规格")
private String specs;
@ApiModelProperty(value = "分类")
private String category;
@ApiModelProperty(value = "主图")
private String thumb;
@ApiModelProperty(value = "图库地址")
private String imagesUrls;
@ApiModelProperty(value = "商品图库id")
private Long imageId;
@ApiModelProperty(value = "商品id")
private Long productId;
@ApiModelProperty(value = "相似度")
private float similarity;
}

View File

@ -9,11 +9,22 @@
package com.suisung.mall.common.utils; package com.suisung.mall.common.utils;
import com.huaban.analysis.jieba.JiebaSegmenter; import com.huaban.analysis.jieba.JiebaSegmenter;
import com.huaban.analysis.jieba.WordDictionary;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List; import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.suisung.mall.common.utils.ProductTitleUtil.*;
/** /**
* 结巴分词工具类 * 结巴分词工具类
*/ */
@ -21,14 +32,195 @@ import java.util.stream.Collectors;
public class JiebaUtils { public class JiebaUtils {
private final JiebaSegmenter segmenter = new JiebaSegmenter(); private final JiebaSegmenter segmenter = new JiebaSegmenter();
// 中文保护标记不会被分割
private static final String PROTECT_START = "开始";
private static final String PROTECT_END = "结束";
// 正则模式匹配数字+单位的组合
private static final String PROTECT_UNIT = "__UNIT__";
private static final String PROTECT_SPEC = "__SPEC__";
private static final String PROTECT_REGEX = "__MIXED__";
private static final String PROTECT_DIMENSION = "__DIMENSION__";
private static final String UNIT_CHINISE = "__UNIT_CHINISE__";
private static final String UNIT_EVERY = "__UNIT_EVERY__";
// 正则模式匹配数字+单位的组合
private static final Pattern UNIT_REGEX = Pattern.compile("\\d+[.]?\\d*[a-zA-Z]+"); // 匹配500ml/1.8L
private static final Pattern SPEC_REGEX = Pattern.compile("\\d+[a-zA-Z]+[*]\\d+"); // 匹配500L*10
private static final Pattern MIXED_REGEX = Pattern.compile("[a-zA-Z]+[-]?\\d+"); // 匹配RSCW-1949
private static final Pattern DIMENSION_REGEX = Pattern.compile("\\d+(?:\\\\.\\\\d+)?[\\\\u4e00-\\\\u9fa5a-zA-Z]+"); // 匹配维度如2.0*2.3
private static final Pattern UNIT_CHN_REGEX = Pattern.compile("([0-9零一二三四五六七八九十百千万亿]+)(条|个|卷)\\b");//匹配只有数字+单位的如牛油果2个
private static final Pattern UNIT_EVERY_REGEX = Pattern.compile("([0-9零一二三四五六七八九十百千万亿]+)([个条份根盒包])(?:\\s*([0-9]+)(g|克|ml|毫升)|\\s*/([袋箱盒份]))");//匹配商品名称+数量+数量单位+重量的 如牛油果2个150克
//private static final Pattern DIMENSION_REGEX = Pattern.compile("([\\u4e00-\\u9fa5]+)(\\d+\\.?\\d*\\*\\d+\\.?\\d*(?:米)?)([\\u4e00-\\u9fa5]+)");
private static final Map<String,Pattern> PROTECT_PATTERNS = new HashMap<String,Pattern>(){{
put(PROTECT_UNIT,UNIT_REGEX);
put(PROTECT_SPEC,SPEC_REGEX);
put(PROTECT_REGEX,MIXED_REGEX);
put(PROTECT_DIMENSION,DIMENSION_REGEX);
put(UNIT_CHINISE,UNIT_CHN_REGEX);
put(UNIT_EVERY,UNIT_EVERY_REGEX);
}};
private static final Map<String,String> spectialrestoreMap = new HashMap<String,String>(){{
put("enDash","-");
put("start","\\*");
put("dot","\\.");
}};
private static void loadUserDict() {
// 方法1通过文件加载
WordDictionary dictionary = WordDictionary.getInstance();
Path path = null;
try {
path = Paths.get(Objects.requireNonNull(JiebaUtils.class.getClassLoader()
.getResource("dic/userdict.txt")).toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
if(path.toFile().exists()){
dictionary.loadUserDict(path);
}
public static void main(String[] args) {
JiebaUtils jiebaUtils = new JiebaUtils();
String text = "农行桂平";
List<String> words = jiebaUtils.segment(text);
System.out.println(words);
} }
/**
* 是否为特色单位
* @param text
*/
private static boolean isSpecialUnitShop(String text){
AtomicBoolean isSpecialUnitShop = new AtomicBoolean(false);
SPECIAL_UNIT_CHN.forEach(str -> {
if(text.contains(str)){
isSpecialUnitShop.set(true);
return;
}
});
return isSpecialUnitShop.get();
}
/**
* 是否为特色单位
* @param text
*/
private static boolean isUnitShop(String text){
AtomicBoolean isUnitShop = new AtomicBoolean(false);
UNITS.forEach(str -> {
if(text.contains(str)){
isUnitShop.set(true);
return;
}
});
return isUnitShop.get();
}
/**
* 是否包好两个单位
* @param text
*/
private static boolean isSpecailAndUnit(String text){
return isSpecialUnitShop(text) && isUnitShop(text);
}
// 预处理方法保护特定模式不被分割
private static String protectPatterns(String text) {
StringBuffer sb = new StringBuffer();
boolean special = isSpecialUnitShop(text);
boolean unit = isUnitShop(text);
if(special&&unit){
Matcher matcher = UNIT_EVERY_REGEX.matcher(text);
if(matcher.find()) {
String original = matcher.group();
// 使用中文保护标记包裹原始值
String protectedText = PROTECT_START + original + PROTECT_END;
matcher.appendReplacement(sb, Matcher.quoteReplacement(protectedText));
matcher.appendTail(sb);
}
}else if(special){
Matcher matcher = UNIT_CHN_REGEX.matcher(text);
if(matcher.find()) {
String original = matcher.group();
// 使用中文保护标记包裹原始值
String protectedText = PROTECT_START + original + PROTECT_END;
matcher.appendReplacement(sb, Matcher.quoteReplacement(protectedText));
matcher.appendTail(sb);
}
} else {
for (Map.Entry<String, Pattern> entry : PROTECT_PATTERNS.entrySet()) {
Matcher matcher = entry.getValue().matcher(text);
while (matcher.find()) {
String original = matcher.group();
// 使用中文保护标记包裹原始值
String protectedText = PROTECT_START + original + PROTECT_END;
matcher.appendReplacement(sb, Matcher.quoteReplacement(protectedText));
matcher.appendTail(sb);
break;
}
if (sb.length() > 0) {
break;
}
}
}
String result = sb.toString();
if (result.isEmpty()){
result=text;
}
if(result.contains("*")){
result = result.replaceAll(spectialrestoreMap.get("start"),"start");
}
if(result.contains("-")){
result = result.replaceAll(spectialrestoreMap.get("enDash"),"enDash");
}
if(result.contains(".")){
result = result.replaceAll(spectialrestoreMap.get("dot"),"dot");
}
return result;
}
// 后处理方法恢复被保护的原始值
private static List<String> restorePatterns(List<String> tokens) {
List<String> result = new ArrayList<>();
StringBuilder protectedPart = new StringBuilder();
boolean inProtected = false;
for (String token : tokens) {
token=token.replaceAll("enDash",spectialrestoreMap.get("enDash"));
token=token.replaceAll("start",spectialrestoreMap.get("start"));
token=token.replaceAll("dot",spectialrestoreMap.get("dot"));
// 检查保护标记开始
if (token.contains(PROTECT_START)) {
inProtected = true;
// 只移除标记部分保留内容
protectedPart.append(token.replace(PROTECT_START, ""));
continue;
}
// 检查保护标记结束
if (token.contains(PROTECT_END)) {
inProtected = false;
// 只移除标记部分保留内容
protectedPart.append(token.replace(PROTECT_END, ""));
result.add(protectedPart.toString());
protectedPart.setLength(0);
continue;
}
if (inProtected) {
protectedPart.append(token);
} else {
result.add(token);
}
}
// 处理未结束的保护块
if (inProtected && protectedPart.length() > 0) {
result.add(protectedPart.toString());
}
return result;
}
/** /**
* 精确模式分词 * 精确模式分词
*/ */
@ -48,4 +240,185 @@ public class JiebaUtils {
.map(token -> token.word) .map(token -> token.word)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* 搜索模式分词更细粒度字典分词
*/
public List<String> segmentForDicSearch(String text) {
loadUserDict();
return segmenter.process(text, JiebaSegmenter.SegMode.SEARCH)
.stream()
.map(token -> token.word)
.collect(Collectors.toList());
}
/**
* 搜索模式分词更细粒度字典分词+排除识别商品
*/
public static List<String> extractKeywords(String text) {
JiebaSegmenter segmenter = new JiebaSegmenter();
loadUserDict();
String protectedText = protectPatterns(text);
System.out.println("protectedText: " + protectedText);
List<String> tokens = segmenter.sentenceProcess(protectedText).stream()
//.filter(word -> word.length() > 1) // 过滤单字
// .sorted(Comparator.reverseOrder()) // 按词典词频降序
.distinct()
.collect(Collectors.toList());
return restorePatterns(tokens);
}
/**
* 传入清洗商品名称通过分词获取商品 品牌规格名称分类关键字等数据
* @param cleanProductName
* @return
*/
public static Map<String,String> getShopDetails(String cleanProductName){
Map<String, String> fields = new HashMap<>(4);
//分词
List<String> words = extractKeywords(cleanProductName);
// resultMap.put("productShortName",productName);
// resultMap.put("brand",productName);
// resultMap.put("specs",productName);
// resultMap.put("category",productName);
// resultMap.put("keywords",productName);
List<String> remainingWords = new ArrayList<>(words.size());
for (String word : words) {
if (BRAND_LIBRARY.contains(word)) {
fields.putIfAbsent("brand", word);
} else if (CATEGORY_LIBRARY.contains(word)) {
fields.putIfAbsent("category", word);
} else if (startsWithDigit(word)) {
fields.putIfAbsent("specs", word.toLowerCase());
}else if(SPECIAL_NAME.contains(word)){
fields.putIfAbsent("productShortName", word);
}else {
remainingWords.add(word);
}
}
String specialWords = specialWordMapping(cleanProductName);
if(StringUtils.isNotEmpty(specialWords)){//加入字段清洗
List<String> specialWordsList = new ArrayList<>(Arrays.asList(specialWords.split(",")));
if(!remainingWords.isEmpty()){
specialWordsList.addAll(remainingWords);
remainingWords= specialWordsList.stream().distinct().collect(Collectors.toList());//去重
}else {
remainingWords=specialWordsList;
}
}
fields.put("keywords", StringUtils.join(remainingWords, ","));
if(StringUtils.isNotEmpty(fields.get("productShortName"))){
return fields;
}
/**
*
*/
if(words.size()>1){
int index=words.size()-1;
if(!startsWithLetterOrDigitRegex(words.get(index))){
fields.putIfAbsent("productShortName", words.get(index));
}else {
fields.putIfAbsent("productShortName", words.get(index-1));
}
}else {
fields.putIfAbsent("productShortName", words.get(0));
}
return fields;
}
/**
* 特色字段匹配
* @return
*/
private static String specialWordMapping(String text){
AtomicReference<String> specialWord = new AtomicReference<>("");
for (Map.Entry<String, String> entry : SHOP_NAME_MAPPING.entrySet()) {
String k = entry.getKey();
String val = entry.getValue();
if (text.contains(k)) {
specialWord.set(val);
return specialWord.get();
}
}
return specialWord.get();
}
/**
* 以字母或者数字开头
* @param str
* @return
*/
public static boolean startsWithLetterOrDigitRegex(String str) {
return str != null && !str.isEmpty() &&
str.matches("^[a-zA-Z\\d].*");
}
/**
* 以字母开头
* @param input
* @return
*/
public boolean startsWithLetter(String input) {
if (input == null || input.isEmpty()) {
return false;
}
return Character.isLetter(input.charAt(0));
}
/**
* 以数字开头
* @param input
* @return
*/
public static boolean startsWithDigit(String input) {
if (input == null || input.isEmpty()) {
return false;
}
return Character.isDigit(input.charAt(0));
}
/**
* 去除所有的字母和数字
* @param text
* @return
*/
public static String cleanNumberAndDigit(String text) {
return text.replaceAll("[a-zA-Z0-9]", "");
}
/**
* 替换文字
* @param text
* @return
*/
public static String repalceText(String text,String repalceTex) {
if(StringUtils.isNotEmpty(repalceTex)){
return text.replaceAll(repalceTex, "");
}
return text;
}
public static void main(String[] args) {
// JiebaUtils jiebaUtils = new JiebaUtils();
//String text = "云计算和区块链是热门技术";
// String text = "新鲜牛肉饺子500g";
//String text = "志高1.8L电热水壶";
// String text = "单充数据线2m";
// String text = "雅安利2.0*2.3四件套";
// String text = "RSCW-1949剃须刀";
System.out.println(cleanNumberAndDigit("日本豆腐3条"));
String text="六指鼠童袜001";
List<String> words = JiebaUtils.extractKeywords(text);
System.out.println(words);
Map<String,String> shopMap= JiebaUtils.getShopDetails(ProductTitleUtil.cleanTitle2(text));
System.out.println(shopMap);
}
} }

View File

@ -26,7 +26,7 @@ public class ProductTitleUtil {
"特价", "折扣", "优惠", "促销", "限时", "秒杀", "抢购", "直降", "满减", "特价", "折扣", "优惠", "促销", "限时", "秒杀", "抢购", "直降", "满减",
"赠品", "包邮", "新品", "热卖", "爆款", "推荐", "精选", "特惠", "清仓", "赠品", "包邮", "新品", "热卖", "爆款", "推荐", "精选", "特惠", "清仓",
"正品", "原装", "官方", "正版", "品牌", "优质", "好用", "新款", "老款", "正品", "原装", "官方", "正版", "品牌", "优质", "好用", "新款", "老款",
"", "", "", "", "[]", "()", "", "", "", "", "??", "?" "", "", "", "", "[]", "()", "", "", "", "", "??", "?","袋装","盒装",""
))); )));
/** /**
@ -40,22 +40,26 @@ public class ProductTitleUtil {
/** /**
* 预编译正则表达式提升性能 * 预编译正则表达式提升性能
*/ */
private static final Pattern NUMERIC_PATTERN = Pattern.compile(".*\\d+.*"); public static final Pattern NUMERIC_PATTERN = Pattern.compile(".*\\d+.*");
private static final Pattern UNIT_PATTERN = Pattern.compile("(\\d+\\.?\\d*)([a-zA-Z]+)"); private static final Pattern UNIT_PATTERN = Pattern.compile("(\\d+\\.?\\d*)([a-zA-Z]+)");
private static final Pattern TITLE_FILTER_PATTERN = Pattern.compile("[^a-zA-Z0-9\u4e00-\u9fa5]"); private static final Pattern TITLE_FILTER_PATTERN = Pattern.compile("[^a-zA-Z0-9\u4e00-\u9fa5]");
private static final Pattern SYMBOL_PATTERN = Pattern.compile("[\\p{P}\\p{S}\\s]+"); // 符号和空格 // private static final Pattern SYMBOL_PATTERN = Pattern.compile("[\\p{P}\\p{S}\\s]+"); // 符号和空格
private static final Pattern SYMBOL_PATTERN = Pattern.compile("[^\\p{L}\\d.*/]|_");//去除.,*以外的符号和所有空格
/** /**
* 品牌词库初始化后不可变 * 品牌词库初始化后不可变
*/ */
private static final Set<String> BRAND_LIBRARY = Collections.unmodifiableSet(new HashSet<>( public static final Set<String> BRAND_LIBRARY = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("华为", "苹果", "小米", "三星", "美的", "格力", "耐克", "阿迪达斯", "海尔") Arrays.asList("华为", "苹果", "小米", "三星", "美的", "格力", "耐克", "阿迪达斯", "海尔","雀巢","伊利","蒙牛","达能","乐事","多力多滋","三只松鼠","良品铺子","可口可乐",
"农夫山泉","元气森林","红牛","雅诗兰黛","欧莱雅","玉兰油","科颜氏","宝洁","汰渍","帮宝适","联合利华","Unilever","含多芬","清扬","大窑","谢村桥牌阡糯","谢村桥牌阡",
"六个核桃","大豫竹","优乐多","安慕希","纳爱斯","舒客","宜轩", "蓝月亮","海尔","美的","松下","戴森","耐克","安踏","李宁","特仑苏","纯甄","安井","三全","哇哈哈",
"龙江家园","达利园","春光","妙芙","南星","利嘉旺","卡得福","泓一","爱乡亲","思念","得力","中雪","江南点心局","德庄","六指鼠",
"依水塬","乌苏啤酒","阿尔卑斯", "瑞旗","振雷","中狗","宝视达","冷酸灵","骆驼","NIKE","PAMU","康师傅","信智利","双兔","安足莱","新博美","新博","创利")
)); ));
/** /**
* 品类词库初始化后不可变 * 品类词库初始化后不可变
*/ */
private static final Set<String> CATEGORY_LIBRARY = Collections.unmodifiableSet(new HashSet<>( public static final Set<String> CATEGORY_LIBRARY = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("手机", "电脑", "空调", "冰箱", "运动鞋", "T恤", "洗发水", "洗衣液") Arrays.asList("手机", "电脑", "空调", "冰箱", "运动鞋", "T恤", "洗发水", "洗衣液","猪脚")
)); ));
static { static {
@ -69,6 +73,23 @@ public class ProductTitleUtil {
UNIT_NORMAL_MAP = Collections.unmodifiableMap(map); UNIT_NORMAL_MAP = Collections.unmodifiableMap(map);
} }
//特色字段映射
public static final Map<String,String> SHOP_NAME_MAPPING = new HashMap<String,String>(){{
put("番茄","西红柿,番茄");
put("西红柿","西红柿,番茄");
put("","女,美女,女生,女士");
put("","男,男生,男士");
put("猪肉包",",猪肉包,包子");
put("叉烧包","叉烧包,包子");
put("香菇青菜包","香菇青菜包,包子");
}};
//特殊商品
public static final Set<String> SPECIAL_NAME = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("水饺", "面条", "包子","卷纸","卫生纸","紫菜汤","猪肉包","叉烧包","香菇青菜包","面包")
));
static { static {
Map<String, Integer> map = new HashMap<>(4); Map<String, Integer> map = new HashMap<>(4);
map.put("brand", 30); map.put("brand", 30);
@ -77,7 +98,13 @@ public class ProductTitleUtil {
map.put("attribute", 25); map.put("attribute", 25);
FIELD_WEIGHTS = Collections.unmodifiableMap(map); FIELD_WEIGHTS = Collections.unmodifiableMap(map);
} }
//特色的单位
public static final Set<String> SPECIAL_UNIT_CHN = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("","","","","","")
));
public static final Set<String> UNITS = Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("g","","kg","千克","ml","毫升","l","","cm","厘米","mm","毫米","","","","")
));
private ProductTitleUtil() { private ProductTitleUtil() {
} }
@ -148,6 +175,7 @@ public class ProductTitleUtil {
return cleaned; return cleaned;
} }
/* ---------------------------- 私有方法 ---------------------------- */ /* ---------------------------- 私有方法 ---------------------------- */
/** /**
@ -171,10 +199,15 @@ public class ProductTitleUtil {
* 单位归一化优化性能 * 单位归一化优化性能
*/ */
private static String normalizeUnit(String word) { private static String normalizeUnit(String word) {
java.util.regex.Matcher matcher = UNIT_PATTERN.matcher(word); // word=word.toLowerCase();
return matcher.matches() ? word=word.replaceAll("","g");
matcher.group(1) + UNIT_NORMAL_MAP.getOrDefault(matcher.group(2).toLowerCase(), matcher.group(2)) : word=word.replaceAll("毫升","ml");
word; word=word.replaceAll("","l");
return word;
// java.util.regex.Matcher matcher = UNIT_PATTERN.matcher(word);
// return matcher.matches() ?
// matcher.group(1) + UNIT_NORMAL_MAP.getOrDefault(matcher.group(2).toLowerCase(), matcher.group(2)) :
// word;
} }
/** /**
@ -200,6 +233,7 @@ public class ProductTitleUtil {
return fields; return fields;
} }
/** /**
* 加权得分计算优化分支预测 * 加权得分计算优化分支预测
*/ */
@ -283,12 +317,17 @@ public class ProductTitleUtil {
String[] testTitles = { String[] testTitles = {
"【限时秒杀】三只松鼠开心果100g特价促销", "【限时秒杀】三只松鼠开心果100g特价促销",
"华为Mate60 Pro 512G手机 官方旗舰店正品", "华为Mate60 Pro 512G手机 官方旗舰店正品",
"Nike Air Max 运动鞋 男款 42码 新品热卖" "Nike Air Max 运动鞋 男款 42码 新品热卖",
"香港63g烧猪肉脯",
"自然派蜜汁猪肉脯",
"三全702克芥菜猪肉水饺",
"志高GB18电热水壶",
"雅安利2.0*2.3四件套"
}; };
for (String title : testTitles) { for (String title : testTitles) {
System.out.println("原始标题: " + title); System.out.println("原始标题: " + title);
System.out.println("清洗后: " + cleanTitle(title)); System.out.println("清洗后: " + cleanTitle2(title));
System.out.println("-------------------"); System.out.println("-------------------");
} }

View File

@ -0,0 +1,42 @@
package com.suisung.mall.search.controller;
import com.suisung.mall.common.api.CommonResult;
import com.suisung.mall.common.pojo.dto.ProductImageSearchDTO;
import com.suisung.mall.search.service.EsProductImageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 搜索商品管理Controller
*/
@RestController
@Api(tags = "EsProductController", description = "搜索商品图库管理")
@RequestMapping("/esProductImage")
public class EsProductImageController {
@Autowired
private EsProductImageService esProductImageService;
@ApiOperation(value = "导入所有数据库中商品到ES")
@RequestMapping(value = "/importAll", method = RequestMethod.POST)
public CommonResult importAllList() {
int count = esProductImageService.importAll();
return CommonResult.success(count);
}
@RequestMapping(value = "/search", method = RequestMethod.POST)
public CommonResult search(@RequestBody ProductImageSearchDTO esProductImage) {
List<ProductImageSearchDTO> search = esProductImageService.search(esProductImage);
return CommonResult.success(search);
}
@RequestMapping(value = "/searchProductImageList", method = RequestMethod.POST)
public Map<String,List<ProductImageSearchDTO>> searchProductImageList(@RequestBody List<ProductImageSearchDTO> esProductImages,@RequestParam("esSearchType") String esSearchType) {
Map<String,List<ProductImageSearchDTO>> search = esProductImageService.searchList(esProductImages,esSearchType);
return search;
}
}

View File

@ -0,0 +1,31 @@
package com.suisung.mall.search.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.suisung.mall.search.domain.EsProduct;
import com.suisung.mall.search.domain.EsProductImage;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 搜索商品管理自定义Dao
*/
@Repository
public interface EsProductImageDao extends BaseMapper<EsProductImage> {
/**
* 获取指定ID的搜索商品
*/
List<EsProductImage> getAllEsProductList(@Param("productId") Long productId);
/**
* 分页查询数据
* @param start
* @param row
* @return
*/
List<EsProductImage> getPageEsProductList(@Param("start") Integer start,@Param("row") Integer row);
Integer getPageTotal();
}

View File

@ -0,0 +1,67 @@
package com.suisung.mall.search.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;
import java.io.Serializable;
/**
* 搜索商品的信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "index_product_image",shards = 3,replicas = 1)
public class EsProductImage implements Serializable {
private static final long serialVersionUID = -1L;
@Id
@Field(type = FieldType.Keyword)
private Long libId;
@Field(type = FieldType.Keyword)
@ApiModelProperty(value = "名称索引关键字(DOT)")
private String barcode;
@Field(analyzer = "product_analyzer",searchAnalyzer="product_analyzer",type = FieldType.Text)
@ApiModelProperty(value = "产品名称:店铺平台先在对用表中检索后通过id检索,检索使用")
private String productName;
@ApiModelProperty(value = "清洗名称")
@Field(analyzer = "product_analyzer",type = FieldType.Text)
private String cleanName;
@ApiModelProperty(value = "关键字")
@Field(analyzer = "product_analyzer",type = FieldType.Text)
private String keywords;
@ApiModelProperty(value = "商品简称")
@Field(analyzer = "product_analyzer",type = FieldType.Text)
private String productShortName;
@ApiModelProperty(value = "品牌")
@Field(type = FieldType.Keyword)
private String brand;
@ApiModelProperty(value = "规格")
@Field(analyzer = "product_analyzer",type = FieldType.Text)
private String specs;
@ApiModelProperty(value = "分类")
@Field(analyzer = "product_analyzer",type = FieldType.Text)
private String category;
@ApiModelProperty(value = "主图")
@Field(type = FieldType.Keyword)
private String thumb;
@ApiModelProperty(value = "附图")
@Field(type = FieldType.Keyword)
private String imagesUrls;
}

View File

@ -0,0 +1,22 @@
package com.suisung.mall.search.repository;
import com.suisung.mall.search.domain.EsProduct;
import com.suisung.mall.search.domain.EsProductImage;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* 搜索商品ES操作类
*/
public interface EsProductImageRepository extends ElasticsearchRepository<EsProductImage, Long> {
/**
* 搜索查询
*
* @param productName 商品名称
* @param productNameIndex 商品索引
* @param page 分页信息
*/
Page<EsProduct> findByProductName(String productName, String productNameIndex, Pageable page);
}

View File

@ -0,0 +1,19 @@
package com.suisung.mall.search.service;
import com.suisung.mall.common.pojo.dto.ProductImageSearchDTO;
import java.util.List;
import java.util.Map;
/**
* 商品搜索管理Service
*/
public interface EsProductImageService {
/**
* 从数据库中导入所有商品到ES
*/
int importAll();
List<ProductImageSearchDTO> search(ProductImageSearchDTO productImageSearchDTO);
Map<String,List<ProductImageSearchDTO>> searchList(List<ProductImageSearchDTO> productImageSearchDTOS, String esSearchType);
}

View File

@ -0,0 +1,389 @@
package com.suisung.mall.search.service.impl;
import cn.hutool.core.util.PageUtil;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.suisung.mall.common.pojo.dto.ProductImageSearchDTO;
import com.suisung.mall.common.utils.JiebaUtils;
import com.suisung.mall.common.utils.ProductTitleUtil;
import com.suisung.mall.search.dao.EsProductImageDao;
import com.suisung.mall.search.domain.EsProductImage;
import com.suisung.mall.search.repository.EsProductImageRepository;
import com.suisung.mall.search.service.EsProductImageService;
import com.suisung.mall.search.utils.CommonUtil;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
/**
* 商品搜索管理Service实现类
*/
@Service
public class EsProductImageServiceImpl implements EsProductImageService {
private static final Logger logger = LoggerFactory.getLogger(EsProductImageServiceImpl.class);
@Autowired
private EsProductImageDao esProductImageDao;
@Autowired
private EsProductImageRepository esProductImageRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
private static final Integer BATCH_SIZE=1000;
@Override
public int importAll() {
Integer total = esProductImageDao.getPageTotal();
if(Objects.isNull(total)||Objects.equals(total,0)){
return 0;
}
esProductImageRepository.deleteAll();
Integer pages= CommonUtil.getPagesCount(total,BATCH_SIZE);
for (int i = 1; i <= pages; i++) {
List<EsProductImage> esProductList= esProductImageDao.getPageEsProductList((i-1)*BATCH_SIZE,BATCH_SIZE);
esProductList.forEach(item->{
String cleanTitle=ProductTitleUtil.cleanTitle2(item.getProductName());
item.setCleanName(cleanTitle);//清理数据
Map<String,String> shopDetailMap= JiebaUtils.getShopDetails(cleanTitle);//分解数据
if(StringUtil.isEmpty(item.getProductShortName())){
item.setProductShortName(shopDetailMap.get("productShortName"));
}
if(StringUtil.isEmpty(item.getSpecs())){
item.setSpecs(shopDetailMap.get("specs"));
}
if(StringUtil.isEmpty(item.getBrand())){
item.setBrand(shopDetailMap.get("brand"));
}
item.setKeywords(shopDetailMap.get("keywords"));
});
saveBatchEsProduct(esProductList);
}
return total;
}
private int saveBatchEsProduct(List<EsProductImage> esProductImages){
Iterable<EsProductImage> esProductImageIterable = esProductImageRepository.saveAll(esProductImages);
Iterator<EsProductImage> iterator = esProductImageIterable.iterator();
int result = 0;
while (iterator.hasNext()) {
result++;
iterator.next();
}
return result;
}
@Override
public List<ProductImageSearchDTO> search(ProductImageSearchDTO esProductImage) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
if(StringUtil.isNotEmpty(esProductImage.getCleanName())){
boolQueryBuilder.filter(QueryBuilders.matchPhraseQuery("cleanName",esProductImage.getCleanName()));
nativeSearchQueryBuilder.withFilter(boolQueryBuilder);
}else {
List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>();
filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("productName", esProductImage.getProductName()),
ScoreFunctionBuilders.weightFactorFunction(20)));
FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()];
filterFunctionBuilders.toArray(builders);
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders)
.scoreMode(FunctionScoreQuery.ScoreMode.SUM)
.setMinScore(2);
nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder);
// HighlightBuilder highlightBuilder=new HighlightBuilder();
// highlightBuilder.field("productName");
// HighlightQuery highlightQuery=new HighlightQuery(highlightBuilder);
}
// nativeSearchQueryBuilder.withQuery(QueryBuilders.termQuery("productName", esProductImage.getProductName()));
NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build();
SearchHits<EsProductImage> searchHits = elasticsearchRestTemplate.search(searchQuery, EsProductImage.class);
List<SearchHit<EsProductImage>> searchHitList= searchHits.getSearchHits();
List<ProductImageSearchDTO> esProductImages=new ArrayList<>();
searchHitList.forEach(searchHit->{
EsProductImage es=searchHit.getContent();
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
BeanUtils.copyProperties(es,productImageSearchDTO);
esProductImages.add(productImageSearchDTO) ;
});
return esProductImages;
}
@Override
public Map<String,List<ProductImageSearchDTO>> searchList(List<ProductImageSearchDTO> esProductImage,String esSearchType) {
// List<String> productNameIndexList = esProductImage.stream().map(ProductImageSearchDTO::getCleanName).collect(Collectors.toList());
// if(DicEnum.ES_SEARCH_TYPE_1.getCode().equals(esSearchType)){
// return queryEsProductImageByKeyWord(productNameIndexList);
// }
return batchSearchProducts(esProductImage,1);
}
private List<ProductImageSearchDTO> queryEsProductImageByKeyWord(List<String> productNameIndexList) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
boolQueryBuilder.filter(QueryBuilders.termsQuery("cleanName", productNameIndexList));
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
SearchHits<EsProductImage> searchHits = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), EsProductImage.class);
List<SearchHit<EsProductImage>> searchHitList= searchHits.getSearchHits();
List<ProductImageSearchDTO> esProductImages=new ArrayList<>();
searchHitList.forEach(searchHit->{
EsProductImage esProductImage=searchHit.getContent();
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
BeanUtils.copyProperties(esProductImage,productImageSearchDTO);
esProductImages.add(productImageSearchDTO) ;
});
return esProductImages;
}
private List<ProductImageSearchDTO> queryEsProductImage(List<String> productNameIndexList) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
//List<String> productNameIndexList = esProductImage.stream().map(EsProductImage::getProductNameIndex).collect(Collectors.toList());
boolQueryBuilder.filter(QueryBuilders.termsQuery("cleanName", productNameIndexList));
nativeSearchQueryBuilder.withQuery(boolQueryBuilder);
SearchHits<EsProductImage> searchHits = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), EsProductImage.class);
List<SearchHit<EsProductImage>> searchHitList= searchHits.getSearchHits();
List<ProductImageSearchDTO> esProductImages=new ArrayList<>();
searchHitList.forEach(searchHit->{
EsProductImage esProductImage=searchHit.getContent();
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
BeanUtils.copyProperties(esProductImage,productImageSearchDTO);
esProductImages.add(productImageSearchDTO) ;
});
return esProductImages;
}
public List<ProductImageSearchDTO> searchSimilarProducts(String keyword, int size) {
// 1. 预处理搜索关键词过滤无用词
String cleanKeyword = ProductTitleUtil.cleanTitle2(keyword);
// 构建ES查询
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
// 1. 基础匹配查询匹配productName字段
MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("productName", cleanKeyword)
.boost(1.0f); // 基础匹配权重
// 2. 模糊查询匹配cleanName.keyword字段
QueryBuilder fuzzyQuery = QueryBuilders.fuzzyQuery("cleanName.keyword", cleanKeyword)
.fuzziness(Fuzziness.AUTO) // 自动确定模糊度
.maxExpansions(50) // 最大扩展项数
.prefixLength(1) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.5f); // 模糊查询权重
// 3. 组合查询布尔查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.should(matchQuery) // 基础匹配
.should(fuzzyQuery); // 模糊匹配
QueryBuilder shortNameFuzzyQuery = null;
MatchQueryBuilder brandMatchQuery =null;
QueryBuilder specsFuzzyQuery =null;
QueryBuilder keyWordsFuzzyQuery =null;
Map<String,String> shopDetailMap= JiebaUtils.getShopDetails(cleanKeyword);//分解数据
if(StringUtil.isNotEmpty(shopDetailMap.get("productShortName"))){
shortNameFuzzyQuery = QueryBuilders.fuzzyQuery("productShortName.keyword", shopDetailMap.get("productShortName"))
.fuzziness(Fuzziness.AUTO) // 自动确定模糊度
.maxExpansions(50) // 最大扩展项数
.prefixLength(1) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.5f); // 模糊查询权重
boolQuery.should(shortNameFuzzyQuery);
}
if(StringUtil.isNotEmpty(shopDetailMap.get("brand"))){
brandMatchQuery = QueryBuilders.matchQuery("brand", shopDetailMap.get("brand"))
.boost(0.5f);
boolQuery.should(brandMatchQuery);
}
if(StringUtil.isNotEmpty(shopDetailMap.get("specs"))){
specsFuzzyQuery = QueryBuilders.fuzzyQuery("specs.keyword", shopDetailMap.get("specs"))
.fuzziness(Fuzziness.AUTO) // 自动确定模糊度
.maxExpansions(50) // 最大扩展项数
.prefixLength(1) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.2f); // 模糊查询权重
boolQuery.should(specsFuzzyQuery);
}
if(StringUtil.isNotEmpty(shopDetailMap.get("keywords"))){
keyWordsFuzzyQuery = QueryBuilders.fuzzyQuery("keywords", shopDetailMap.get("keywords"))
.fuzziness(Fuzziness.ONE) // 自动确定模糊度
.maxExpansions(20) // 最大扩展项数
.prefixLength(2) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.2f); // 模糊查询权重
boolQuery.should(keyWordsFuzzyQuery);
}
// 4. 构建最终查询
nativeSearchQueryBuilder.withQuery(boolQuery);
nativeSearchQueryBuilder.withPageable(PageRequest.of(0, size));
// 5. 执行查询
SearchHits<EsProductImage> searchHits = elasticsearchRestTemplate.search(
nativeSearchQueryBuilder.build(),
EsProductImage.class
);
List<SearchHit<EsProductImage>> searchHitList= searchHits.getSearchHits();
List<ProductImageSearchDTO> esProductImages=new ArrayList<>();
searchHitList.forEach(searchHit->{
EsProductImage esProductImage=searchHit.getContent();
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
BeanUtils.copyProperties(esProductImage,productImageSearchDTO);
esProductImages.add(productImageSearchDTO) ;
});
//SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//List<EsProductImage> results = parseResponse(response);
return esProductImages;
}
/**
* 模糊批量匹配
* @param esProductImage
* @param sizePerKeyword
* @return
*/
public Map<String, List<ProductImageSearchDTO>> batchSearchProducts(List<ProductImageSearchDTO> esProductImage, int sizePerKeyword) {
List<String> keywords = esProductImage.stream().map(ProductImageSearchDTO::getCleanName).collect(Collectors.toList());
// 1. 准备批量查询
List<QueryBuilder> queries = new ArrayList<>();
Map<String, String> keywordToCleanMap = new HashMap<>();
// 2. 为每个关键词构建查询
for (String keyword : keywords) {
String cleanKeyword = ProductTitleUtil.cleanTitle2(keyword);
keywordToCleanMap.put(cleanKeyword, keyword); // 保留原始关键词映射
// 构建组合查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
//.should(QueryBuilders.matchQuery("barcode", cleanKeyword).boost(1.0f))
.should(QueryBuilders.matchQuery("productName", JiebaUtils.cleanNumberAndDigit(cleanKeyword)).boost(1.0f))
.should(QueryBuilders.fuzzyQuery("cleanName.keyword", JiebaUtils.cleanNumberAndDigit(cleanKeyword))
.fuzziness(Fuzziness.ONE)
.maxExpansions(20)
.prefixLength(1)
.transpositions(true)
.boost(0.5f))
;
int count =2;
Map<String,String> shopDetailMap= JiebaUtils.getShopDetails(cleanKeyword);//分解数据
if(StringUtil.isNotEmpty(shopDetailMap.get("productShortName"))){
boolQuery.should( QueryBuilders.fuzzyQuery("productShortName.keyword", shopDetailMap.get("productShortName")) // 模糊查询权重
.fuzziness(Fuzziness.AUTO) // 自动确定模糊度
.maxExpansions(50) // 最大扩展项数
.prefixLength(2) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.5f));
count+=1;
}
if(StringUtil.isNotEmpty(shopDetailMap.get("brand"))){
boolQuery.should( QueryBuilders.matchQuery("brand", shopDetailMap.get("brand"))
.boost(0.5f));
count+=1;
}
if(StringUtil.isNotEmpty(shopDetailMap.get("specs"))){
boolQuery.should(QueryBuilders.fuzzyQuery("specs.keyword", shopDetailMap.get("specs")) // 模糊查询权重
.fuzziness(Fuzziness.TWO) // 自动确定模糊度
.maxExpansions(50) // 最大扩展项数
.prefixLength(1) // 必须匹配的前缀长度
.transpositions(true) // 允许字符调换
.boost(0.01f));
count+=1;
}
if(StringUtil.isNotEmpty(shopDetailMap.get("keywords"))){
String keywordsValue=shopDetailMap.get("keywords");
boolQuery.should(QueryBuilders.matchQuery("keywords", keywordsValue)
.boost(0.9f)); // 提高权重
// List<String> words=Arrays.asList(keywordsValue.split(","));
// words.forEach(word -> {
// boolQuery.should(QueryBuilders.fuzzyQuery("keywords.keyword", word)// 模糊查询权重
// .fuzziness(Fuzziness.ONE) // 自动确定模糊度
// .maxExpansions(100) // 最大扩展项数
// .prefixLength(1) // 必须匹配的前缀长度
// .transpositions(true) // 允许字符调换
// .boost(0.1f));
// });
// boolQuery.should(QueryBuilders.fuzzyQuery("keywords", keywordsValue)// 模糊查询权重
// .fuzziness(Fuzziness.ONE) // 自动确定模糊度
// .maxExpansions(100) // 最大扩展项数
// .prefixLength(1) // 必须匹配的前缀长度
// .transpositions(true) // 允许字符调换
// .boost(0.8f));
count+=1;
}
boolQuery.minimumShouldMatch("50%");
if(count>3){
boolQuery.minimumShouldMatch("70%");
}
queries.add(boolQuery);
}
// 3. 构建批量请求
NativeSearchQueryBuilder bulkQueryBuilder = new NativeSearchQueryBuilder()
.withPageable(PageRequest.of(0, sizePerKeyword));
// 4. 使用MultiSearch执行批量查询
List<Query> searchQueries = queries.stream()
.map(query -> bulkQueryBuilder.withQuery(query).build())
.collect(Collectors.toList());
IndexCoordinates indexCoordinates = IndexCoordinates.of("index_product_image");
List<SearchHits<EsProductImage>> results = elasticsearchRestTemplate.multiSearch(
searchQueries,
EsProductImage.class,
indexCoordinates
);
// 5. 整理结果按原始关键词分组
Map<String, List<ProductImageSearchDTO>> resultMap = new HashMap<>();
for (int i = 0; i < results.size(); i++) {
String cleanKeyword = ProductTitleUtil.cleanTitle2(keywords.get(i));
String originalKeyword = keywordToCleanMap.get(cleanKeyword);
int finalI = i;
List<ProductImageSearchDTO> products = results.get(i).getSearchHits().stream()
.filter(searchHit->originalKeyword.contains(searchHit.getContent().getProductShortName()))//过非商品
.map(searchHit->{
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
BeanUtils.copyProperties(searchHit.getContent(),productImageSearchDTO);
productImageSearchDTO.setProductId(esProductImage.get(finalI).getProductId());
productImageSearchDTO.setImageId(esProductImage.get(finalI).getImageId());
productImageSearchDTO.setSimilarity(searchHit.getScore());
return productImageSearchDTO;
})
.collect(Collectors.toList());
resultMap.put(originalKeyword, products);
}
return resultMap;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
* Vestibulum commodo. Ut rhoncus gravida arcu.
*/
package com.suisung.mall.search.utils;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
public class CommonUtil {
private final static String apiUrl = "http://4ei8850868ux.vicp.fun";
public static JSONObject sendPostRequestToSiXun(String urlPath, JSONObject params) {
String resp = HttpUtil.post(apiUrl + urlPath, params.toString());
if (StrUtil.isBlank(resp)) {
return null;
}
JSONObject respObj = JSONUtil.parseObj(resp);
return respObj;
}
/**
* 根据总条数和分页大小求页数
*
* @param total
* @param pageSize
* @return
*/
public static Integer getPagesCount(Integer total, Integer pageSize) {
if (total == null || pageSize == null || pageSize <= 0 || total <= 0) {
return 0;
}
int pagesCount = 0;
pagesCount = total / pageSize;
if (total % pageSize > 0) {
// 有余数
pagesCount++;
} else {
if (pagesCount == 0) {
pagesCount = 1;
}
}
return pagesCount;
}
/**
* 接口是否成功执行返回
*
* @param jsonObject
* @return
*/
public static Boolean isSuccess(JSONObject jsonObject) {
if (jsonObject == null) {
return false;
}
return jsonObject.get("code") != null && jsonObject.getStr("code").equals("0");
}
/**
* 接口是否成功执行返回
*
* @param jsonObject
* @return
*/
public static Boolean hasSuccessData(JSONObject jsonObject) {
if (jsonObject == null) {
return false;
}
return jsonObject.get("code") != null && jsonObject.getStr("code").equals("0") && jsonObject.get("data") != null;
}
/**
* 通过json节点表达式获取节点json字符串驼峰命名改成下划线命名
*
* @param jsonObject
* @param expression json 节点表达式比如 data.list, msg, code
* @return
*/
public static String toUnderlineJson(JSONObject jsonObject, String expression) {
return StrUtil.toUnderlineCase(jsonObject.getByPath(expression, String.class));
}
}

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.suisung.mall.search.dao.EsProductImageDao">
<resultMap id="shop_product_index" type="com.suisung.mall.search.domain.EsProductImage">
<result property="libId" column="id"/>
<result property="barcode" column="barcode"/>
<result property="category" column="category"/>
<result property="productName" column="name"/>
<result property="productShortName" column="product_short_name"/>
<result property="thumb" column="thumb"/>
<result property="imagesUrls" column="merged_image_url"/>
</resultMap>
<select id="getAllEsProductList" resultMap="shop_product_index">
SELECT
lp.id,
lp.barcode,
lp.name,
lp.product_short_name,
lp.category,
lp.thumb,
(
SELECT GROUP_CONCAT(image_url SEPARATOR ',')
FROM library_product_image
WHERE product_id = lp.id
) AS merged_image_url
FROM library_product lp
<where>
<if test="productId!=null">
lp.id=#{productId}
</if>
</where>
</select>
<select id="getPageEsProductList" resultMap="shop_product_index">
SELECT
lp.id,
lp.barcode,
lp.name,
lp.product_short_name,
lp.category,
lp.thumb,
(
SELECT GROUP_CONCAT(image_url SEPARATOR ',')
FROM library_product_image
WHERE product_id = lp.id
) AS merged_image_url
FROM library_product lp
limit #{start},#{row}
</select>
<select id="getPageTotal" resultType="Integer">
select count(1)
from
(
SELECT
lp.id,
lp.barcode,
lp.name,
lp.product_short_name,
lp.category,
lp.thumb,
(
SELECT GROUP_CONCAT(image_url SEPARATOR ',')
FROM library_product_image
WHERE product_id = lp.id
) AS merged_image_url
FROM library_product lp
)temp
</select>
</mapper>

View File

@ -0,0 +1,76 @@
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"product_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": [
"stop"
]
},
"filter_stopwords": {
"type": "stop",
"stopwords": ["特价", "折扣", "优惠", "促销", "限时", "秒杀", "抢购", "直降", "满减",
"赠品", "包邮", "新品", "热卖", "爆款", "推荐", "精选", "特惠", "清仓",
"正品", "原装", "官方", "正版", "品牌", "优质", "好用", "新款", "老款",
"【", "】", "", "", "[]", "()", "「", "」", "", "", "??", "?"]
}
}
}
},
"mappings": {
"properties": {
"id": { "type": "keyword" },
"barcode": { "type": "keyword" },
"productName": {
"type": "text",
"analyzer": "product_analyzer",
"search_analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"keywords": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"productShortName": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"brand": {"type": "keyword" },
"specs": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"category": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"cleanName": {
"type": "text",
"analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"thumb": { "type": "keyword"},
"imagesUrls": { "type": "keyword"}
}
}
}

View File

@ -0,0 +1,19 @@
{
"properties": {
"id": { "type": "keyword" },
"barcode": { "type": "keyword" },
"productName": {
"type": "text",
"analyzer": "product_analyzer",
"search_analyzer": "product_analyzer",
"fields": {
"keyword": { "type": "keyword" }
}
},
"cleanName": {
"type": "text",
"analyzer": "product_analyzer"
},
"imagesUrls": { "type": "keyword" }
}
}

View File

@ -0,0 +1,23 @@
{
"number_of_shards": 3,
"number_of_replicas": 1,
"analysis": {
"analyzer": {
"product_analyzer": {
"type": "custom",
"tokenizer": "ik_max_word",
"filter": [
"lowercase",
"stop",
"word_delimiter"
]
}
},
"filter": {
"filter_stopwords": {
"type": "stop",
"stopwords": ["特价", "折扣", "优惠", "促销", "限时", "秒杀"]
}
}
}
}

View File

@ -68,6 +68,7 @@ public class ShopBaseProductSpecController {
if (CheckUtil.isNotEmpty(shopBaseProductSpec.getSpec_name())) { if (CheckUtil.isNotEmpty(shopBaseProductSpec.getSpec_name())) {
queryWrapper.like("spec_name", shopBaseProductSpec.getSpec_name()); queryWrapper.like("spec_name", shopBaseProductSpec.getSpec_name());
} }
queryWrapper.orderByAsc("spec_order");
Page<ShopBaseProductSpec> pageList= shopBaseProductSpecService.lists(queryWrapper, pageNum, pageSize); Page<ShopBaseProductSpec> pageList= shopBaseProductSpecService.lists(queryWrapper, pageNum, pageSize);
List<ShopBaseProductSpec> shopBaseProductSpecList= pageList.getRecords(); List<ShopBaseProductSpec> shopBaseProductSpecList= pageList.getRecords();
shopBaseProductSpecList=shopStoreBaseService.fixStoreDataShopBaseProductSpec(shopBaseProductSpecList); shopBaseProductSpecList=shopStoreBaseService.fixStoreDataShopBaseProductSpec(shopBaseProductSpecList);

View File

@ -89,7 +89,7 @@ public class SxSyncController {
@ApiOperation(value = "获取思迅商品新增到数据库", notes = "获取思迅商品新增到数据库") @ApiOperation(value = "获取思迅商品新增到数据库", notes = "获取思迅商品新增到数据库")
@RequestMapping(value = "/goods/sync2", method = RequestMethod.POST) @RequestMapping(value = "/goods/sync2", method = RequestMethod.POST)
public CommonResult syncGoods(@RequestParam(name = "storeId", defaultValue = "1") String storeId) { public CommonResult syncGoods(@RequestParam(name = "storeId", defaultValue = "1") String storeId) {
if( shopProductBaseService.syncSxGoodsToShopProductBase(storeId)){ if(shopProductBaseService.syncSxGoodsToShopProductBase(storeId)){
return CommonResult.success(); return CommonResult.success();
} }

View File

@ -169,6 +169,9 @@ public class ProductMappingServiceImpl extends BaseServiceImpl<ProductMappingMap
ShopProductSpecItem shopProductSpecItem=processShopProductSpecItem(shopProductBaseList.get(i),shopProductItems.get(i).getCategory_id(),shopProductSpecItemMap,ShopBaseProductSpecMap,productMappingMap,null); ShopProductSpecItem shopProductSpecItem=processShopProductSpecItem(shopProductBaseList.get(i),shopProductItems.get(i).getCategory_id(),shopProductSpecItemMap,ShopBaseProductSpecMap,productMappingMap,null);
if(shopProductSpecItem!=null){ if(shopProductSpecItem!=null){
shopProductBaseList.get(i).setProduct_state_id(StateCode.PRODUCT_STATE_OFF_THE_SHELF); shopProductBaseList.get(i).setProduct_state_id(StateCode.PRODUCT_STATE_OFF_THE_SHELF);
ShopProductIndex shopProductIndex=new ShopProductIndex();
shopProductIndex.setProduct_id(shopProductBaseList.get(i).getProduct_id());
shopProductIndex.setProduct_state_id(StateCode.PRODUCT_STATE_OFF_THE_SHELF);
shopProductItems.get(i).setItem_enable(StateCode.PRODUCT_STATE_OFF_THE_SHELF); shopProductItems.get(i).setItem_enable(StateCode.PRODUCT_STATE_OFF_THE_SHELF);
shopProductItems.get(i).setItem_is_default(1); shopProductItems.get(i).setItem_is_default(1);
if(shopProductSpecItem.isUpdate()){ if(shopProductSpecItem.isUpdate()){
@ -265,7 +268,7 @@ public class ProductMappingServiceImpl extends BaseServiceImpl<ProductMappingMap
} }
// 使用IN查询优化根据数据库特性可能需要分批 // 使用IN查询优化根据数据库特性可能需要分批
QueryWrapper<ShopProductIndex> query = new QueryWrapper<>(); QueryWrapper<ShopProductIndex> query = new QueryWrapper<>();
query.select("product_id", "product_name", "store_id"); query.select("product_id", "product_name", "store_id","product_state_id");
// 构建OR条件 (store_id=X AND product_number=Y) OR (store_id=A AND product_number=B)... // 构建OR条件 (store_id=X AND product_number=Y) OR (store_id=A AND product_number=B)...
shopProductBaseList.forEach(base -> { shopProductBaseList.forEach(base -> {
@ -274,7 +277,7 @@ public class ProductMappingServiceImpl extends BaseServiceImpl<ProductMappingMap
}); });
List<ShopProductIndex> updateShopProductIndexList= shopProductIndexService.list(query); List<ShopProductIndex> updateShopProductIndexList= shopProductIndexService.list(query);
updateShopProductIndexList.forEach(shopProductIndex -> { updateShopProductIndexList.forEach(shopProductIndex -> {
shopProductIndex.setProduct_state_id(StateCode.PRODUCT_STATE_NORMAL); shopProductIndex.setProduct_state_id(StateCode.PRODUCT_STATE_OFF_THE_SHELF);
}); });
shopProductIndexService.updateBatchById(updateShopProductIndexList); shopProductIndexService.updateBatchById(updateShopProductIndexList);
} }

View File

@ -61,6 +61,7 @@ import java.math.BigDecimal;
import java.text.ParseException; import java.text.ParseException;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -96,6 +97,28 @@ public abstract class SyncBaseThirdSxAbstract{
@Autowired @Autowired
private LibraryProductService libraryProductService; private LibraryProductService libraryProductService;
@Autowired
public static final Set<String> FORBID_CATEGORY= Collections.unmodifiableSet(new HashSet<>(
Arrays.asList("香烟类","香烟","烟类", "")
));
/**
* 是否位禁售目录
* @param category
* @return
*/
private String getForbidCategory(String category){
AtomicReference<String> forbidName= new AtomicReference<>("");
FORBID_CATEGORY.stream().allMatch(forbidCate -> {
if(category.contains(forbidCate)){
forbidName.set(forbidCate);
return false;
}
return true;
});
return forbidName.get();
}
/** /**
* 对商品分类进行保存 * 对商品分类进行保存
* @param list * @param list
@ -108,6 +131,10 @@ public abstract class SyncBaseThirdSxAbstract{
int count = 0; int count = 0;
List<ShopBaseProductType> productTypeList = new ArrayList<>(); List<ShopBaseProductType> productTypeList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
String categoryName=list.get(i).getCategory_name();
if(StringUtils.isNotEmpty(getForbidCategory(categoryName))){
continue;
}
list.get(i).setStore_id(storeId); // app 记录传进来 list.get(i).setStore_id(storeId); // app 记录传进来
list.get(i).setData_source(2); // 思迅数据来源 list.get(i).setData_source(2); // 思迅数据来源
list.get(i).setCategory_is_enable(1); list.get(i).setCategory_is_enable(1);
@ -152,25 +179,30 @@ public abstract class SyncBaseThirdSxAbstract{
Integer firstParentId = 0; Integer firstParentId = 0;
list.get(i).setCategory_parent_id(firstParentId);//设置默认值 list.get(i).setCategory_parent_id(firstParentId);//设置默认值
if (StrUtil.isNotBlank(o.getStr("first_category_name"))) { if (StrUtil.isNotBlank(o.getStr("first_category_name"))) {
String firstCategoryName = o.getStr("first_category_name");
String forbidCategoryName=getForbidCategory(firstCategoryName);
if(StringUtils.isNotEmpty(forbidCategoryName)){
firstCategoryName=firstCategoryName.replace(forbidCategoryName,"");
}
// TODO storeId 不判断一下吗 // TODO storeId 不判断一下吗
ShopBaseProductCategory cate = productCategoryService.getCategoryByName(o.getStr("first_category_name")); ShopBaseProductCategory cate = productCategoryService.getCategoryByName(firstCategoryName);
if (cate != null) { if (cate != null) {
list.get(i).setCategory_parent_id(cate.getCategory_id()); list.get(i).setCategory_parent_id(cate.getCategory_id());
} else{ } else{
// 新增一个第一级父类 // 新增一个第一级父类
ShopBaseProductCategory firstCate = new ShopBaseProductCategory(); ShopBaseProductCategory firstCate = new ShopBaseProductCategory();
firstCate.setCategory_name(o.getStr("first_category_name")); firstCate.setCategory_name(firstCategoryName);
firstCate.setParent_id(0); firstCate.setParent_id(0);
firstCate.setStore_id(storeId); firstCate.setStore_id(storeId);
firstCate.setType_id(typeId); firstCate.setType_id(typeId);
firstCate.setData_source(2); firstCate.setData_source(2);
List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,o.getStr("first_category_name"),new ArrayList<>()); List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,firstCategoryName,new ArrayList<>());
if(CollectionUtil.isNotEmpty(libraryProductDTOS)){ if(CollectionUtil.isNotEmpty(libraryProductDTOS)){
firstCate.setCategory_image(libraryProductDTOS.get(0).getThumb()); firstCate.setCategory_image(libraryProductDTOS.get(0).getThumb());
} }
if (productCategoryService.saveOrUpdate(firstCate)) { if (productCategoryService.saveOrUpdate(firstCate)) {
// 当前子分类的父类id // 当前子分类的父类id
firstParentId = firstCate.getId(); firstParentId = firstCate.getCategory_id();
list.get(i).setCategory_parent_id(firstParentId); list.get(i).setCategory_parent_id(firstParentId);
} }
} }
@ -179,25 +211,30 @@ public abstract class SyncBaseThirdSxAbstract{
// 处理第二级父类字段 产品分类 // 处理第二级父类字段 产品分类
if (StrUtil.isNotBlank(o.getStr("second_category_name"))) { if (StrUtil.isNotBlank(o.getStr("second_category_name"))) {
String secondCategoryName = o.getStr("second_category_name");
String forbidCategoryName=getForbidCategory(secondCategoryName);
if(StringUtils.isNotEmpty(forbidCategoryName)){
secondCategoryName=secondCategoryName.replace(forbidCategoryName,"");
}
// TODO storeId 不判断一下吗 // TODO storeId 不判断一下吗
ShopBaseProductCategory cate = productCategoryService.getCategoryByName(o.getStr("second_category_name")); ShopBaseProductCategory cate = productCategoryService.getCategoryByName(secondCategoryName);
if (cate != null) { if (cate != null) {
list.get(i).setCategory_parent_id(cate.getCategory_id()); list.get(i).setCategory_parent_id(cate.getCategory_id());
} else { } else {
// 新增一个第二级父类 // 新增一个第二级父类
ShopBaseProductCategory secondCate = new ShopBaseProductCategory(); ShopBaseProductCategory secondCate = new ShopBaseProductCategory();
secondCate.setCategory_name(o.getStr("second_category_name")); secondCate.setCategory_name(secondCategoryName);
secondCate.setCategory_parent_id(firstParentId); secondCate.setCategory_parent_id(firstParentId);
secondCate.setStore_id(storeId); secondCate.setStore_id(storeId);
secondCate.setType_id(typeId); secondCate.setType_id(typeId);
secondCate.setData_source(2); secondCate.setData_source(2);
List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,o.getStr("second_category_name"),new ArrayList<>()); List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,secondCategoryName,new ArrayList<>());
if(CollectionUtil.isNotEmpty(libraryProductDTOS)){ if(CollectionUtil.isNotEmpty(libraryProductDTOS)){
secondCate.setCategory_image(libraryProductDTOS.get(0).getThumb()); secondCate.setCategory_image(libraryProductDTOS.get(0).getThumb());
} }
if (productCategoryService.saveOrUpdate(secondCate)) { if (productCategoryService.saveOrUpdate(secondCate)) {
// 当前子分类的第二级父类id // 当前子分类的第二级父类id
list.get(i).setCategory_parent_id(secondCate.getId()); list.get(i).setCategory_parent_id(secondCate.getCategory_id());
} }
} }
} }
@ -215,7 +252,6 @@ public abstract class SyncBaseThirdSxAbstract{
} else { } else {
continue; continue;
} }
} }
List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,list.get(i).getCategory_name(),new ArrayList<>()); List<LibraryProductDTO> libraryProductDTOS=libraryProductService.matchLibraryProducts(null,list.get(i).getCategory_name(),new ArrayList<>());
if(CollectionUtil.isNotEmpty(libraryProductDTOS)){ if(CollectionUtil.isNotEmpty(libraryProductDTOS)){
@ -689,6 +725,8 @@ public abstract class SyncBaseThirdSxAbstract{
String cateGoryId=""; String cateGoryId="";
if(null!=categoryMap.get(jsonObj.get("first_category_name"))){ if(null!=categoryMap.get(jsonObj.get("first_category_name"))){
cateGoryId=categoryMap.get(jsonObj.get("first_category_name")).toString(); cateGoryId=categoryMap.get(jsonObj.get("first_category_name")).toString();
}else {
return;
} }
Integer categoryId = Convert.toInt(cateGoryId, 0); Integer categoryId = Convert.toInt(cateGoryId, 0);
Integer storeIdInt = Convert.toInt(storeId); Integer storeIdInt = Convert.toInt(storeId);

View File

@ -9,12 +9,17 @@
package com.suisung.mall.shop.sync.service.impl; package com.suisung.mall.shop.sync.service.impl;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.api.StateCode; import com.suisung.mall.common.api.StateCode;
import com.suisung.mall.common.enums.DicEnum;
import com.suisung.mall.common.feignService.SearchService;
import com.suisung.mall.common.modules.product.ShopProductBase; import com.suisung.mall.common.modules.product.ShopProductBase;
import com.suisung.mall.common.modules.product.ShopProductImage; import com.suisung.mall.common.modules.product.ShopProductImage;
import com.suisung.mall.common.modules.product.ShopProductIndex; import com.suisung.mall.common.modules.product.ShopProductIndex;
import com.suisung.mall.common.modules.product.ShopProductItem; import com.suisung.mall.common.modules.product.ShopProductItem;
import com.suisung.mall.common.modules.sync.ImageMappingDto; import com.suisung.mall.common.modules.sync.ImageMappingDto;
import com.suisung.mall.common.pojo.dto.ProductImageSearchDTO;
import com.suisung.mall.common.utils.StringUtils; import com.suisung.mall.common.utils.StringUtils;
import com.suisung.mall.shop.product.mapper.ShopProductImageMapper; import com.suisung.mall.shop.product.mapper.ShopProductImageMapper;
@ -31,6 +36,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -59,6 +65,9 @@ public class SyncShopImageServiceImpl implements SyncShopImageService {
@Autowired @Autowired
private ShopProductIndexService shopProductIndexService; private ShopProductIndexService shopProductIndexService;
@Autowired
private SearchService searchService;
@Value("${project.static_domain}") @Value("${project.static_domain}")
private String staticDomain; private String staticDomain;
@ -96,10 +105,13 @@ public class SyncShopImageServiceImpl implements SyncShopImageService {
case MAPPING_PRODUCTNAME: case MAPPING_PRODUCTNAME:
typename="产品名称模糊匹配"; typename="产品名称模糊匹配";
//先把数据查到临时表再查询 //先把数据查到临时表再查询
shopImageMappingTempMapper.deleteShopImageMappingTemp(storeId);//匹配结束删除临时表数据 // shopImageMappingTempMapper.deleteShopImageMappingTemp(storeId);//匹配结束删除临时表数据
shopImageMappingTempMapper.mergeShopImageMappingTemp(storeId); //shopImageMappingTempMapper.mergeShopImageMappingTemp(storeId);
total= shopProductImageMapper.mappingByProductNameCount(storeId); //total= shopProductImageMapper.mappingByProductNameCount(storeId);
QueryWrapper<ShopProductImage> queryWrapper=new QueryWrapper<>();
queryWrapper.eq("store_id",storeId);
queryWrapper.eq("item_image_default","1");
total=(int)shopProductImageService.count(queryWrapper);
break; break;
} }
if(total!=null&&total<1){ if(total!=null&&total<1){
@ -112,11 +124,18 @@ public class SyncShopImageServiceImpl implements SyncShopImageService {
AtomicInteger success = new AtomicInteger(); AtomicInteger success = new AtomicInteger();
AtomicInteger fails = new AtomicInteger(); AtomicInteger fails = new AtomicInteger();
for (int i = 1; i <= pages; i++) { if(MAPPING_PRODUCTNAME.equals(type)){
List<ImageMappingDto> imageMappingDtos = shopProductImageMapper.mappingProductImage(type,storeId,(i-1)*BATCH_SIZE,BATCH_SIZE); QueryWrapper<ShopProductImage> queryWrapper=new QueryWrapper<>();
final int finalI = i; queryWrapper.eq("store_id",storeId);
futures.add(executor.submit(() -> { queryWrapper.eq("item_image_default","1");
for (int i = 1; i <= pages; i++) {
// List<ImageMappingDto> imageMappingDtos = shopProductImageMapper.mappingProductImage(type,storeId,(i-1)*BATCH_SIZE,BATCH_SIZE);
Page<ShopProductImage> pageList= shopProductImageService.lists(queryWrapper,i,BATCH_SIZE);
List<ShopProductImage> list=pageList.getRecords();
final int finalI = i;
futures.add(executor.submit(() -> {
try { try {
List<ImageMappingDto> imageMappingDtos=CovertToShopProductImage(list);
syncBatchShopImage(imageMappingDtos); syncBatchShopImage(imageMappingDtos);
success.getAndIncrement(); success.getAndIncrement();
return "成功" + finalI; return "成功" + finalI;
@ -124,14 +143,31 @@ public class SyncShopImageServiceImpl implements SyncShopImageService {
fails.getAndIncrement(); fails.getAndIncrement();
return "失败"+finalI; return "失败"+finalI;
} }
})); }));
}
}else {
for (int i = 1; i <= pages; i++) {
List<ImageMappingDto> imageMappingDtos = shopProductImageMapper.mappingProductImage(type,storeId,(i-1)*BATCH_SIZE,BATCH_SIZE);
final int finalI = i;
futures.add(executor.submit(() -> {
try {
syncBatchShopImage(imageMappingDtos);
success.getAndIncrement();
return "图库匹配成功" + finalI;
} catch (Exception e) {
fails.getAndIncrement();
return "图库匹配失败"+finalI+":"+e.getMessage();
}
}));
}
} }
// 等待所有任务完成 // 等待所有任务完成
for (Future<?> future : futures) { for (Future<?> future : futures) {
try { try {
log.info("任务结果: " + future.get()); log.info("图库匹配任务结果: " + future.get());
} catch (Exception e) { } catch (Exception e) {
log.error("任务执行异常: " + e.getMessage()); log.error("图库匹配任务执行异常: " + e.getMessage());
} }
} }
executor.shutdown(); executor.shutdown();
@ -199,4 +235,40 @@ public class SyncShopImageServiceImpl implements SyncShopImageService {
} }
} }
/**
* 用es查询转换List<ImageMappingDto>
* @param shopProductImageList
* @return
*/
private List<ImageMappingDto> CovertToShopProductImage(List<ShopProductImage> shopProductImageList){
List<ImageMappingDto> imageMappingDtos=new ArrayList<>();
List<ProductImageSearchDTO> productImageSearchDTOS=new ArrayList<>();
for (ShopProductImage shopProductImage:shopProductImageList){
if(StringUtils.isNotEmpty(shopProductImage.getProduct_name())){
ProductImageSearchDTO productImageSearchDTO=new ProductImageSearchDTO();
productImageSearchDTO.setProductName(shopProductImage.getProduct_name());
productImageSearchDTO.setCleanName(shopProductImage.getProduct_name());
productImageSearchDTO.setImageId(shopProductImage.getProduct_image_id());
productImageSearchDTO.setProductId(shopProductImage.getProduct_id());
productImageSearchDTOS.add(productImageSearchDTO);
}
}
Map<String,List<ProductImageSearchDTO>> productImageList= searchService.searchProductImageList(productImageSearchDTOS, DicEnum.ES_SEARCH_TYPE_2.getCode());
productImageList.forEach((k,v)->{
if(!v.isEmpty()){
ImageMappingDto imageMappingDto=new ImageMappingDto();
ProductImageSearchDTO productImageSearchDTO=v.get(0);
imageMappingDto.setThumb(productImageSearchDTO.getThumb());
imageMappingDto.setMergedImageUrl(productImageSearchDTO.getImagesUrls());
imageMappingDto.setImgProductName(k);
imageMappingDto.setProductId(productImageSearchDTO.getProductId());
imageMappingDto.setStoreId(shopProductImageList.get(0).getStore_id());
imageMappingDto.setProductImageId(productImageSearchDTO.getImageId());
imageMappingDtos.add(imageMappingDto);
}
});
return imageMappingDtos;
}
} }