diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/PictureSearchController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/PictureSearchController.java index bb13df9dc..5f94bde20 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/PictureSearchController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/xkt/PictureSearchController.java @@ -22,70 +22,9 @@ import java.util.List; * @date 2025-03-26 */ @RestController -@RequestMapping("/rest/v1/pic-searches") +@RequestMapping("/rest/v1/pic-search") public class PictureSearchController extends XktBaseController { @Autowired private IPictureSearchService pictureSearchService; - /** - * 查询以图搜款列表 - */ - // @PreAuthorize("@ss.hasPermi('system:search:list')") - @GetMapping("/list") - public TableDataInfo list(PictureSearch pictureSearch) { - startPage(); - List list = pictureSearchService.selectPictureSearchList(pictureSearch); - return getDataTable(list); - } - - /** - * 导出以图搜款列表 - */ - // @PreAuthorize("@ss.hasPermi('system:search:export')") - @Log(title = "以图搜款", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, PictureSearch pictureSearch) { - List list = pictureSearchService.selectPictureSearchList(pictureSearch); - ExcelUtil util = new ExcelUtil(PictureSearch.class); - util.exportExcel(response, list, "以图搜款数据"); - } - - /** - * 获取以图搜款详细信息 - */ - // @PreAuthorize("@ss.hasPermi('system:search:query')") - @GetMapping(value = "/{picSearchId}") - public R getInfo(@PathVariable("picSearchId") Long picSearchId) { - return success(pictureSearchService.selectPictureSearchByPicSearchId(picSearchId)); - } - - /** - * 新增以图搜款 - */ - // @PreAuthorize("@ss.hasPermi('system:search:add')") - @Log(title = "以图搜款", businessType = BusinessType.INSERT) - @PostMapping - public R add(@RequestBody PictureSearch pictureSearch) { - return success(pictureSearchService.insertPictureSearch(pictureSearch)); - } - - /** - * 修改以图搜款 - */ - // @PreAuthorize("@ss.hasPermi('system:search:edit')") - @Log(title = "以图搜款", businessType = BusinessType.UPDATE) - @PutMapping - public R edit(@RequestBody PictureSearch pictureSearch) { - return success(pictureSearchService.updatePictureSearch(pictureSearch)); - } - - /** - * 删除以图搜款 - */ - // @PreAuthorize("@ss.hasPermi('system:search:remove')") - @Log(title = "以图搜款", businessType = BusinessType.DELETE) - @DeleteMapping("/{picSearchIds}") - public R remove(@PathVariable Long[] picSearchIds) { - return success(pictureSearchService.deletePictureSearchByPicSearchIds(picSearchIds)); - } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java index c75f89da6..c21ecde1e 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/CacheConstants.java @@ -143,4 +143,9 @@ public class CacheConstants */ public static final String APP_ADVERT = "app_advert:"; + /** + * 图片搜索统计 + */ + public static final String IMG_SEARCH_STATISTICS = "img_search_statistics:"; + } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java index cb296e8f5..99850365c 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java @@ -229,5 +229,13 @@ public class Constants * 支付超时最大时间 */ public static final Integer PAY_EXPIRE_MAX_HOURS = 24 * 7; + /** + * 以图搜图图片类目 + */ + public static final int IMG_SEARCH_CATEGORY_ID = 4; + /** + * 以图搜图匹配分数阈值 + */ + public static final float IMG_SEARCH_MATCH_SCORE_THRESHOLD = (float) 0.5; } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java index d5fb5bda7..3872283d7 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java @@ -271,4 +271,8 @@ public class RedisCache public boolean exists(final String key) { return redisTemplate.hasKey(key); } + + public Long valueIncr(final String key) { + return redisTemplate.opsForValue().increment(key); + } } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/ImgSearchClientWrapper.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/ImgSearchClientWrapper.java index f9242fd2c..68d4502cf 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/ImgSearchClientWrapper.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/ImgSearchClientWrapper.java @@ -2,7 +2,6 @@ package com.ruoyi.framework.img; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.lang.Assert; import cn.hutool.core.util.BooleanUtil; import com.aliyun.imagesearch20201214.Client; import com.aliyun.imagesearch20201214.models.*; @@ -12,9 +11,7 @@ import com.ruoyi.framework.img.entity.ImgSearchReq; import com.ruoyi.framework.img.entity.ImgSearchResult; import lombok.extern.slf4j.Slf4j; -import java.io.FileInputStream; import java.io.InputStream; -import java.net.URL; import java.util.List; /** @@ -40,25 +37,22 @@ public class ImgSearchClientWrapper { * @return */ public boolean addImg(ImgAdd imgAdd) { - AddImageAdvanceRequest request = new AddImageAdvanceRequest(); - request.instanceName = instanceName; - request.productId = imgAdd.getProductId(); - request.picName = imgAdd.getPicName(); - request.categoryId = imgAdd.getCategoryId(); - request.customContent = imgAdd.getCustomContent(); - request.crop = true; - Assert.notEmpty(imgAdd.getPicPath()); - try (InputStream inputStream = BooleanUtil.isTrue(imgAdd.getPicLocalFlag()) ? - new FileInputStream(imgAdd.getPicPath()) : new URL(imgAdd.getPicPath()).openStream()) { + try (InputStream inputStream = imgAdd.getPicInputStream()) { + AddImageAdvanceRequest request = new AddImageAdvanceRequest(); + request.instanceName = instanceName; + request.productId = imgAdd.getProductId(); + request.picName = imgAdd.getPicName(); + request.categoryId = imgAdd.getCategoryId(); + request.customContent = imgAdd.getCustomContent(); + request.crop = true; request.picContentObject = inputStream; - RuntimeOptions runtimeOptions = new RuntimeOptions(); - AddImageResponse response = client.addImageAdvance(request, runtimeOptions); + AddImageResponse response = client.addImageAdvance(request, new RuntimeOptions()); if (response.getBody().success) { return true; } log.warn("图片搜索-添加图片失败: {} - {}", imgAdd, response.getBody().toMap()); } catch (Exception e) { - log.error("图片搜索服务异常", e); + log.error("图片搜索服务添加图片异常", e); } return false; } @@ -80,7 +74,7 @@ public class ImgSearchClientWrapper { } log.warn("图片搜索-删除图片失败: {} - {}", productId, response.getBody().toMap()); } catch (Exception e) { - log.error("图片搜索服务异常", e); + log.error("图片搜索服务删除图片异常", e); } return false; } @@ -92,23 +86,21 @@ public class ImgSearchClientWrapper { * @return */ public List searchByPic(ImgSearchReq imgSearchReq) { - SearchImageByPicAdvanceRequest request = new SearchImageByPicAdvanceRequest(); - request.instanceName = instanceName; - request.categoryId = imgSearchReq.getCategoryId(); - request.num = imgSearchReq.getNum(); - request.start = imgSearchReq.getStart(); - request.crop = true; - request.distinctProductId = BooleanUtil.isTrue(imgSearchReq.getDistinctProductId()); - RuntimeOptions runtimeObject = new RuntimeOptions(); - try (InputStream inputStream = BooleanUtil.isTrue(imgSearchReq.getPicLocalFlag()) ? - new FileInputStream(imgSearchReq.getPicPath()) : new URL(imgSearchReq.getPicPath()).openStream()) { + try (InputStream inputStream = imgSearchReq.getPicInputStream()) { + SearchImageByPicAdvanceRequest request = new SearchImageByPicAdvanceRequest(); + request.instanceName = instanceName; + request.categoryId = imgSearchReq.getCategoryId(); + request.num = imgSearchReq.getNum(); + request.start = imgSearchReq.getStart(); + request.crop = true; + request.distinctProductId = BooleanUtil.isTrue(imgSearchReq.getDistinctProductId()); request.picContentObject = inputStream; - SearchImageByPicResponse response = client.searchImageByPicAdvance(request, runtimeObject); + SearchImageByPicResponse response = client.searchImageByPicAdvance(request, new RuntimeOptions()); List auctions = response.getBody() .getAuctions(); return BeanUtil.copyToList(auctions, ImgSearchResult.class); } catch (Exception e) { - log.error("图片搜索服务异常", e); + log.error("图片搜索服务搜索异常", e); } return ListUtil.empty(); } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgAdd.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgAdd.java index 306d8f902..886c4feb7 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgAdd.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgAdd.java @@ -2,6 +2,8 @@ package com.ruoyi.framework.img.entity; import lombok.Data; +import java.io.InputStream; + /** * @author liangyq * @date 2025-04-25 20:08 @@ -32,15 +34,8 @@ public class ImgAdd { */ private String customContent; /** - * 图片路径 - *

- * 本地图片:E:/test/123.jpg - * URL:https://www.example.com/123.jpg + * 图片输入流 */ - private String picPath; - /** - * 是否本地图片 - */ - private Boolean picLocalFlag; + private InputStream picInputStream; } diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgSearchReq.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgSearchReq.java index 1310f8af7..96ee00515 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgSearchReq.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/img/entity/ImgSearchReq.java @@ -2,6 +2,8 @@ package com.ruoyi.framework.img.entity; import lombok.Data; +import java.io.InputStream; + /** * @author liangyq * @date 2025-04-25 20:50 @@ -9,16 +11,9 @@ import lombok.Data; @Data public class ImgSearchReq { /** - * 图片路径 - *

- * 本地图片:E:/test/123.jpg - * URL:https://www.example.com/123.jpg + * 图片输入流 */ - private String picPath; - /** - * 是否本地图片 - */ - private Boolean picLocalFlag; + private InputStream picInputStream; /** * 选填,图片类目 *

diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java index 46cb17647..3e15f755f 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java @@ -242,7 +242,7 @@ public class OSSClientWrapper { return "image/jpeg"; } - public InputStream getObject(String key) throws Exception { + public InputStream getObject(String key) { OSSObject ossObject = client.getObject(configuration.getBucketName(), key); return ossObject.getObjectContent(); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicSyncResultDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicSyncResultDTO.java new file mode 100644 index 000000000..86e5f3eb9 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicSyncResultDTO.java @@ -0,0 +1,22 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author liangyq + * @date 2025-05-20 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PicSyncResultDTO { + + private String productPicKey; + + private Boolean success; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductMatchDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductMatchDTO.java new file mode 100644 index 000000000..301d21057 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductMatchDTO.java @@ -0,0 +1,19 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-05-21 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProductMatchDTO { + + private Long storeProductId; + + private Float score; +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncDTO.java new file mode 100644 index 000000000..a00c387b3 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncDTO.java @@ -0,0 +1,22 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author liangyq + * @date 2025-05-20 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProductPicSyncDTO { + + private Long storeProductId; + + private List productPicKeys; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncResultDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncResultDTO.java new file mode 100644 index 000000000..c2299be03 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/ProductPicSyncResultDTO.java @@ -0,0 +1,22 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * @author liangyq + * @date 2025-05-20 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ProductPicSyncResultDTO { + + private Boolean success; + + private List picSyncResults; + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/SearchRequestDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/SearchRequestDTO.java new file mode 100644 index 000000000..47e06344d --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/SearchRequestDTO.java @@ -0,0 +1,21 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * @author liangyq + * @date 2025-05-21 + */ +@Data +public class SearchRequestDTO { + + private String picKey; + + private BigDecimal picSize; + + private Long userId; + + private Integer num; +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/mapper/PictureSearchMapper.java b/xkt/src/main/java/com/ruoyi/xkt/mapper/PictureSearchMapper.java index 3430f5f3e..522b98add 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/mapper/PictureSearchMapper.java +++ b/xkt/src/main/java/com/ruoyi/xkt/mapper/PictureSearchMapper.java @@ -2,6 +2,7 @@ package com.ruoyi.xkt.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.xkt.domain.PictureSearch; +import org.springframework.stereotype.Repository; import java.util.List; @@ -11,52 +12,6 @@ import java.util.List; * @author ruoyi * @date 2025-03-26 */ +@Repository public interface PictureSearchMapper extends BaseMapper { - /** - * 查询以图搜款 - * - * @param id 以图搜款主键 - * @return 以图搜款 - */ - public PictureSearch selectPictureSearchByPicSearchId(Long id); - - /** - * 查询以图搜款列表 - * - * @param pictureSearch 以图搜款 - * @return 以图搜款集合 - */ - public List selectPictureSearchList(PictureSearch pictureSearch); - - /** - * 新增以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - public int insertPictureSearch(PictureSearch pictureSearch); - - /** - * 修改以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - public int updatePictureSearch(PictureSearch pictureSearch); - - /** - * 删除以图搜款 - * - * @param id 以图搜款主键 - * @return 结果 - */ - public int deletePictureSearchByPicSearchId(Long id); - - /** - * 批量删除以图搜款 - * - * @param picSearchIds 需要删除的数据主键集合 - * @return 结果 - */ - public int deletePictureSearchByPicSearchIds(Long[] picSearchIds); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/mapper/SysFileMapper.java b/xkt/src/main/java/com/ruoyi/xkt/mapper/SysFileMapper.java index a04e05d35..bdd39a2b4 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/mapper/SysFileMapper.java +++ b/xkt/src/main/java/com/ruoyi/xkt/mapper/SysFileMapper.java @@ -2,6 +2,7 @@ package com.ruoyi.xkt.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.xkt.domain.SysFile; +import org.springframework.stereotype.Repository; import java.util.List; @@ -11,6 +12,7 @@ import java.util.List; * @author ruoyi * @date 2025-03-26 */ +@Repository public interface SysFileMapper extends BaseMapper { /** * 查询file diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IPictureSearchService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureSearchService.java index e71bb75a7..70b26dba3 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IPictureSearchService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureSearchService.java @@ -1,6 +1,7 @@ package com.ruoyi.xkt.service; -import com.ruoyi.xkt.domain.PictureSearch; +import com.ruoyi.xkt.dto.picture.ProductMatchDTO; +import com.ruoyi.xkt.dto.picture.SearchRequestDTO; import java.util.List; @@ -12,50 +13,11 @@ import java.util.List; */ public interface IPictureSearchService { /** - * 查询以图搜款 + * 图片搜索商品 * - * @param picSearchId 以图搜款主键 - * @return 以图搜款 + * @param requestDTO + * @return */ - public PictureSearch selectPictureSearchByPicSearchId(Long picSearchId); + List searchProductByPic(SearchRequestDTO requestDTO); - /** - * 查询以图搜款列表 - * - * @param pictureSearch 以图搜款 - * @return 以图搜款集合 - */ - public List selectPictureSearchList(PictureSearch pictureSearch); - - /** - * 新增以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - public int insertPictureSearch(PictureSearch pictureSearch); - - /** - * 修改以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - public int updatePictureSearch(PictureSearch pictureSearch); - - /** - * 批量删除以图搜款 - * - * @param picSearchIds 需要删除的以图搜款主键集合 - * @return 结果 - */ - public int deletePictureSearchByPicSearchIds(Long[] picSearchIds); - - /** - * 删除以图搜款信息 - * - * @param picSearchId 以图搜款主键 - * @return 结果 - */ - public int deletePictureSearchByPicSearchId(Long picSearchId); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java index da211256f..1fd70c81a 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java @@ -1,6 +1,11 @@ package com.ruoyi.xkt.service; import com.ruoyi.xkt.dto.picture.PicZipDTO; +import com.ruoyi.xkt.dto.picture.ProductMatchDTO; +import com.ruoyi.xkt.dto.picture.ProductPicSyncDTO; +import com.ruoyi.xkt.dto.picture.ProductPicSyncResultDTO; + +import java.util.List; /** * @author liangyq @@ -16,4 +21,21 @@ public interface IPictureService { */ PicZipDTO processPicZip(String key); + /** + * 同步图片到搜图服务器 + * + * @param productPicSyncDTO + * @return + */ + ProductPicSyncResultDTO sync2ImgSearchServer(ProductPicSyncDTO productPicSyncDTO); + + /** + * 以图搜商品 + * + * @param picKey + * @param num + * @return + */ + List searchProductByPicKey(String picKey, Integer num); + } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureSearchServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureSearchServiceImpl.java index 9bfc51d8e..584057ad3 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureSearchServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureSearchServiceImpl.java @@ -1,14 +1,25 @@ package com.ruoyi.xkt.service.impl; -import com.ruoyi.common.utils.DateUtils; +import cn.hutool.core.lang.Assert; +import com.ruoyi.common.constant.CacheConstants; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.xkt.domain.PictureSearch; +import com.ruoyi.xkt.domain.SysFile; +import com.ruoyi.xkt.dto.picture.ProductMatchDTO; +import com.ruoyi.xkt.dto.picture.SearchRequestDTO; import com.ruoyi.xkt.mapper.PictureSearchMapper; +import com.ruoyi.xkt.mapper.SysFileMapper; import com.ruoyi.xkt.service.IPictureSearchService; +import com.ruoyi.xkt.service.IPictureService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Date; import java.util.List; +import java.util.Optional; /** * 以图搜款Service业务层处理 @@ -16,82 +27,44 @@ import java.util.List; * @author ruoyi * @date 2025-03-26 */ +@Slf4j @Service public class PictureSearchServiceImpl implements IPictureSearchService { @Autowired private PictureSearchMapper pictureSearchMapper; + @Autowired + private SysFileMapper sysFileMapper; + @Autowired + private IPictureService pictureService; + @Autowired + private RedisCache redisCache; - /** - * 查询以图搜款 - * - * @param picSearchId 以图搜款主键 - * @return 以图搜款 - */ - @Override - @Transactional(readOnly = true) - public PictureSearch selectPictureSearchByPicSearchId(Long picSearchId) { - return pictureSearchMapper.selectPictureSearchByPicSearchId(picSearchId); - } - /** - * 查询以图搜款列表 - * - * @param pictureSearch 以图搜款 - * @return 以图搜款 - */ + @Transactional(rollbackFor = Exception.class) @Override - @Transactional(readOnly = true) - public List selectPictureSearchList(PictureSearch pictureSearch) { - return pictureSearchMapper.selectPictureSearchList(pictureSearch); - } - - /** - * 新增以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - @Override - @Transactional - public int insertPictureSearch(PictureSearch pictureSearch) { - pictureSearch.setCreateTime(DateUtils.getNowDate()); - return pictureSearchMapper.insertPictureSearch(pictureSearch); - } - - /** - * 修改以图搜款 - * - * @param pictureSearch 以图搜款 - * @return 结果 - */ - @Override - @Transactional - public int updatePictureSearch(PictureSearch pictureSearch) { - pictureSearch.setUpdateTime(DateUtils.getNowDate()); - return pictureSearchMapper.updatePictureSearch(pictureSearch); - } - - /** - * 批量删除以图搜款 - * - * @param picSearchIds 需要删除的以图搜款主键 - * @return 结果 - */ - @Override - @Transactional - public int deletePictureSearchByPicSearchIds(Long[] picSearchIds) { - return pictureSearchMapper.deletePictureSearchByPicSearchIds(picSearchIds); - } - - /** - * 删除以图搜款信息 - * - * @param picSearchId 以图搜款主键 - * @return 结果 - */ - @Override - @Transactional - public int deletePictureSearchByPicSearchId(Long picSearchId) { - return pictureSearchMapper.deletePictureSearchByPicSearchId(picSearchId); + public List searchProductByPic(SearchRequestDTO requestDTO) { + Assert.notEmpty(requestDTO.getPicKey()); + SysFile sysFile = new SysFile(); + sysFile.setFileUrl(requestDTO.getPicKey()); + sysFile.setFileSize(requestDTO.getPicSize()); + sysFile.setVersion(0); + sysFile.setDelFlag(Constants.UNDELETED); + sysFileMapper.insert(sysFile); + PictureSearch pictureSearch = new PictureSearch(); + pictureSearch.setSearchFileId(sysFile.getId()); + pictureSearch.setUserId(requestDTO.getUserId()); + pictureSearch.setVoucherDate(new Date()); + pictureSearch.setVersion(0); + pictureSearch.setDelFlag(Constants.UNDELETED); + pictureSearchMapper.insert(pictureSearch); + //搜索 + List results = pictureService.searchProductByPicKey(requestDTO.getPicKey(), + //默认30条 + Optional.ofNullable(requestDTO.getNum()).orElse(30)); + for (ProductMatchDTO result : results) { + //匹配次数+1 + redisCache.valueIncr(CacheConstants.IMG_SEARCH_STATISTICS + result.getStoreProductId()); + } + return results; } } diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java index 0b0f831bc..af1e9198e 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java @@ -1,5 +1,7 @@ package com.ruoyi.xkt.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.file.FileNameUtil; import cn.hutool.core.lang.Assert; @@ -7,13 +9,16 @@ import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.ZipUtil; +import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.text.CharsetKit; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ImgExtUtil; import com.ruoyi.framework.config.properties.OSSProperties; +import com.ruoyi.framework.img.ImgSearchClientWrapper; +import com.ruoyi.framework.img.entity.ImgAdd; +import com.ruoyi.framework.img.entity.ImgSearchReq; import com.ruoyi.framework.oss.OSSClientWrapper; -import com.ruoyi.xkt.dto.picture.DownloadResultDTO; -import com.ruoyi.xkt.dto.picture.PicZipDTO; +import com.ruoyi.xkt.dto.picture.*; import com.ruoyi.xkt.service.IPictureService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -25,7 +30,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; /** * @author liangyq @@ -39,6 +46,8 @@ public class PictureServiceImpl implements IPictureService { private OSSClientWrapper ossClient; @Autowired private OSSProperties ossProperties; + @Autowired + private ImgSearchClientWrapper imgSearchClient; @Override public PicZipDTO processPicZip(String key) { @@ -113,6 +122,59 @@ public class PictureServiceImpl implements IPictureService { } } + @Override + public ProductPicSyncResultDTO sync2ImgSearchServer(ProductPicSyncDTO productPicSyncDTO) { + Assert.notNull(productPicSyncDTO); + String productId = productPicSyncDTO.getStoreProductId().toString(); + //删除商品图片 + boolean allSuccess = imgSearchClient.deleteImg(productId); + List picKeys = CollUtil.emptyIfNull(productPicSyncDTO.getProductPicKeys()); + List picSyncResultList = new ArrayList<>(picKeys.size()); + for (String picKey : picKeys) { + ImgAdd imgAdd = new ImgAdd(); + imgAdd.setProductId(productId); + imgAdd.setPicName(FileUtil.getName(picKey)); + imgAdd.setCategoryId(Constants.IMG_SEARCH_CATEGORY_ID); + try { + imgAdd.setPicInputStream(ossClient.getObject(picKey)); + } catch (Exception e) { + log.error("获取图片流异常: " + picKey, e); + allSuccess = false; + picSyncResultList.add(new PicSyncResultDTO(picKey, false)); + continue; + } + //添加商品图片 + boolean success = imgSearchClient.addImg(imgAdd); + if (!success) { + allSuccess = false; + } + picSyncResultList.add(new PicSyncResultDTO(picKey, success)); + } + return new ProductPicSyncResultDTO(allSuccess, picSyncResultList); + } + + @Override + public List searchProductByPicKey(String picKey, Integer num) { + Assert.notEmpty(picKey); + ImgSearchReq imgSearchReq = new ImgSearchReq(); + imgSearchReq.setCategoryId(Constants.IMG_SEARCH_CATEGORY_ID); + imgSearchReq.setNum(num); + imgSearchReq.setStart(0); + imgSearchReq.setDistinctProductId(true); + try { + imgSearchReq.setPicInputStream(ossClient.getObject(picKey)); + } catch (Exception e) { + log.error("获取图片流异常: " + picKey, e); + return ListUtil.empty(); + } + return imgSearchClient.searchByPic(imgSearchReq) + .stream() + //过滤搜索评分0.5以下的商品 + .filter(o -> o.getScore() >= Constants.IMG_SEARCH_MATCH_SCORE_THRESHOLD) + .map(o -> new ProductMatchDTO(Long.parseLong(o.getProductId()), o.getScore())) + .collect(Collectors.toList()); + } + /** * 解压文件 * TODO 仅支持zip diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java index da2c8ef3f..b8b2d2379 100644 --- a/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/StoreProductServiceImpl.java @@ -1,6 +1,10 @@ package com.ruoyi.xkt.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.json.JSONUtil; import co.elastic.clients.elasticsearch.core.BulkResponse; import co.elastic.clients.elasticsearch.core.CreateResponse; import co.elastic.clients.elasticsearch.core.UpdateResponse; @@ -19,6 +23,8 @@ import com.ruoyi.system.domain.dto.productCategory.ProdCateDTO; import com.ruoyi.system.mapper.SysProductCategoryMapper; import com.ruoyi.xkt.domain.*; import com.ruoyi.xkt.dto.es.ESProductDTO; +import com.ruoyi.xkt.dto.picture.ProductPicSyncDTO; +import com.ruoyi.xkt.dto.picture.ProductPicSyncResultDTO; import com.ruoyi.xkt.dto.storeColor.StoreColorDTO; import com.ruoyi.xkt.dto.storeProdCateAttr.StoreProdCateAttrDTO; import com.ruoyi.xkt.dto.storeProdColor.StoreProdColorDTO; @@ -34,8 +40,10 @@ import com.ruoyi.xkt.enums.EProductStatus; import com.ruoyi.xkt.enums.FileType; import com.ruoyi.xkt.enums.ProductSizeStatus; import com.ruoyi.xkt.mapper.*; +import com.ruoyi.xkt.service.IPictureService; import com.ruoyi.xkt.service.IStoreProductService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -57,6 +65,7 @@ import static com.ruoyi.common.constant.Constants.*; * @author ruoyi * @date 2025-03-26 */ +@Slf4j @Service @RequiredArgsConstructor public class StoreProductServiceImpl implements IStoreProductService { @@ -80,6 +89,7 @@ public class StoreProductServiceImpl implements IStoreProductService { final StoreProductStockMapper prodStockMapper; final StoreCustomerMapper storeCusMapper; final StoreCustomerProductDiscountMapper storeCusProdDiscMapper; + final IPictureService pictureService; /** @@ -210,6 +220,8 @@ public class StoreProductServiceImpl implements IStoreProductService { this.handleStoreProdProperties(storeProd, storeProdDTO); // 向ES索引: product_info 创建文档 this.createESDoc(storeProd, storeProdDTO); + // 搜图服务同步 + sync2ImgSearchServer(storeProd.getId(), storeProdDTO.getFileList()); return count; } @@ -260,6 +272,8 @@ public class StoreProductServiceImpl implements IStoreProductService { this.handleStoreProdColorSizeList(storeProdDTO.getSizeList(), storeProdId, Boolean.FALSE); // 更新索引: product_info 的文档 this.updateESDoc(storeProd, storeProdDTO); + // 搜图服务同步 + sync2ImgSearchServer(storeProd.getId(), storeProdDTO.getFileList()); return count; } @@ -421,10 +435,20 @@ public class StoreProductServiceImpl implements IStoreProductService { // 筛选商品状态为已下架,则删除ES文档 if (Objects.equals(prodStatusDTO.getProdStatus(), EProductStatus.OFF_SALE.getValue())) { this.deleteESDoc(prodStatusDTO.getStoreProdIdList()); + // 搜图服务同步 + prodStatusDTO.getStoreProdIdList().forEach(spId -> sync2ImgSearchServer(spId, ListUtil.empty())); } // 已下架的商品重新上架 if (CollectionUtils.isNotEmpty(reSaleList)) { this.reSaleCreateESDoc(reSaleList); + // 搜图服务同步 + Map> picKeyGroupMap = storeProdFileMapper.selectMainPicByStoreProdIdList( + reSaleList.stream().map(StoreProduct::getId).collect(Collectors.toList()), + FileType.MAIN_PIC.getValue(), null + ).stream().collect(Collectors.groupingBy(StoreProdMainPicDTO::getStoreProdId, + Collectors.mapping(StoreProdMainPicDTO::getFileUrl, Collectors.toList()))); + prodStatusDTO.getStoreProdIdList() + .forEach(spId -> sync2ImgSearchServer(spId, picKeyGroupMap.get(spId), true)); } } @@ -778,4 +802,34 @@ public class StoreProductServiceImpl implements IStoreProductService { } } + /** + * 搜图服务同步商品 + * + * @param storeProductId + * @param storeProdFiles + */ + private void sync2ImgSearchServer(Long storeProductId, List storeProdFiles) { + List picKeys = CollUtil.emptyIfNull(storeProdFiles) + .stream() + .filter(o -> FileType.MAIN_PIC.getValue().equals(o.getFileType())) + .map(StoreProdFileDTO::getFileUrl) + .collect(Collectors.toList()); + sync2ImgSearchServer(storeProductId, picKeys, true); + } + + private void sync2ImgSearchServer(Long storeProductId, List picKeys, boolean async) { + if (async) { + ThreadUtil.execAsync(() -> { + ProductPicSyncResultDTO r = + pictureService.sync2ImgSearchServer(new ProductPicSyncDTO(storeProductId, picKeys)); + log.info("商品图片同步至搜图服务器: id: {}, result: {}", storeProductId, JSONUtil.toJsonStr(r)); + } + ); + } else { + ProductPicSyncResultDTO r = + pictureService.sync2ImgSearchServer(new ProductPicSyncDTO(storeProductId, picKeys)); + log.info("商品图片同步至搜图服务器: id: {}, result: {}", storeProductId, JSONUtil.toJsonStr(r)); + } + } + } diff --git a/xkt/src/main/resources/mapper/PictureSearchMapper.xml b/xkt/src/main/resources/mapper/PictureSearchMapper.xml index 1fcf35ad6..d1d1b2b12 100644 --- a/xkt/src/main/resources/mapper/PictureSearchMapper.xml +++ b/xkt/src/main/resources/mapper/PictureSearchMapper.xml @@ -4,88 +4,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - - - - - - - - - - - select id, search_file_id, user_id, voucher_date, version, del_flag, create_by, create_time, update_by, update_time from picture_search - - - - - - - - insert into picture_search - - search_file_id, - user_id, - voucher_date, - version, - del_flag, - create_by, - create_time, - update_by, - update_time, - - - #{searchFileId}, - #{userId}, - #{voucherDate}, - #{version}, - #{delFlag}, - #{createBy}, - #{createTime}, - #{updateBy}, - #{updateTime}, - - - - - update picture_search - - search_file_id = #{searchFileId}, - user_id = #{userId}, - voucher_date = #{voucherDate}, - version = #{version}, - del_flag = #{delFlag}, - create_by = #{createBy}, - create_time = #{createTime}, - update_by = #{updateBy}, - update_time = #{updateTime}, - - where id = #{id} - - - - delete from picture_search where id = #{id} - - - - delete from picture_search where id in - - #{id} - - \ No newline at end of file diff --git a/xkt/src/main/resources/mapper/StoreProductFileMapper.xml b/xkt/src/main/resources/mapper/StoreProductFileMapper.xml index 2a41252a0..d20a15153 100644 --- a/xkt/src/main/resources/mapper/StoreProductFileMapper.xml +++ b/xkt/src/main/resources/mapper/StoreProductFileMapper.xml @@ -135,8 +135,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{storeProdId} - AND spf.file_type = #{fileType} - AND spf.order_num = #{orderNum} + + AND spf.file_type = #{fileType} + + + AND spf.order_num = #{orderNum} +