添加商品关联推荐

This commit is contained in:
gong
2025-12-24 15:23:02 +08:00
parent 506e74747a
commit a6dd4f4611
12 changed files with 164 additions and 25 deletions

View File

@@ -72,6 +72,15 @@ public class UProductController {
return CzgResult.success(list);
}
/**
* 获取相关推荐商品
*/
@GetMapping("/related/{id}")
public CzgResult<List<ShopProductVo>> getRelatedProduct(@PathVariable("id") Long id) {
AssertUtil.isNull(id, "商品id不能为空");
return CzgResult.success(uProductService.queryProductRelatedList(id));
}
/**
* 小程序点餐-商品详情
*

View File

@@ -0,0 +1,36 @@
package com.czg.service;
import com.alibaba.fastjson2.JSONObject;
import com.czg.product.dto.ProductDTO;
import com.czg.product.service.ProductService;
import com.czg.product.service.UProductService;
import com.czg.product.vo.ShopGroupProductVo;
import com.czg.product.vo.ShopProductVo;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author yjjie
* @date 2025/12/24 13:36
*/
@SpringBootTest
public class ProductTest {
@Resource
private ProductService productService;
@Resource
private UProductService uProductService;
@Test
public void testGetById() {
// ProductDTO product = productService.getProductById(169L);
// System.out.println(JSONObject.toJSONString( product));
List<ShopProductVo> productVos = uProductService.queryProductRelatedList(169L);
System.out.println(JSONObject.toJSONString(productVos));
}
}

View File

@@ -1,12 +1,15 @@
package com.czg.product.dto;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.annotation.JSONField;
import com.czg.product.vo.ProductGroupVo;
import com.czg.validator.group.DefaultGroup;
import com.czg.validator.group.InsertGroup;
import com.czg.validator.group.UpdateGroup;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.*;
import lombok.Data;
@@ -235,6 +238,12 @@ public class ProductDTO implements Serializable {
* 是否可售时间 1-是 0-否
*/
private Integer isSaleTime;
/**
* 相关推荐商品
*/
private List<RelatedProductDTO> relatedRecommendJson;
@JsonIgnore
private String relatedRecommend;
public Object getImages() {
return JSON.parseArray(Convert.toStr(images, "[]"));
@@ -250,4 +259,16 @@ public class ProductDTO implements Serializable {
public Object getGroupSnap() {
return JSON.parseArray(Convert.toStr(groupSnap, "[]"));
}
public String getRelatedRecommendStr() {
if (CollUtil.isNotEmpty(relatedRecommendJson)) {
JSONArray array = new JSONArray();
for (RelatedProductDTO relatedProductDTO : relatedRecommendJson) {
array.add(relatedProductDTO.getId());
}
return array.toJSONString();
}
return "[]";
}
}

View File

@@ -0,0 +1,31 @@
package com.czg.product.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 相关推荐 商品 DTO
*
* @author yjjie
* @date 2025/12/24 13:34
*/
@Data
public class RelatedProductDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 商品 ID
*/
private Long id;
/**
* 商品名称
*/
private String name;
/**
* 商品图片
*/
private String coverImg;
}

View File

@@ -142,6 +142,10 @@ public class Product implements Serializable {
* 退款是否退回库存
*/
private Integer isRefundStock;
/**
* 相关推荐
*/
private String relatedRecommend;
/**
* 创建时间
*/

View File

@@ -1,6 +1,7 @@
package com.czg.product.service;
import com.czg.product.dto.ProductDTO;
import com.czg.product.dto.RelatedProductDTO;
import com.czg.product.entity.Product;
import com.czg.product.entity.ProductStockFlow;
import com.czg.product.param.*;

View File

@@ -1,5 +1,6 @@
package com.czg.product.service;
import com.czg.product.dto.RelatedProductDTO;
import com.czg.product.entity.Product;
import com.czg.product.param.ShopProductSkuParam;
import com.czg.product.vo.ShopGroupProductVo;
@@ -95,4 +96,6 @@ public interface UProductService extends IService<Product> {
* @return 是否可售时间 1-是0-否
*/
Integer calcIsSaleTime(String days, LocalTime startTime, LocalTime endTime);
List<ShopProductVo> queryProductRelatedList(Long productId);
}

View File

@@ -16,9 +16,6 @@ import com.czg.exception.CzgException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
@@ -35,8 +32,8 @@ public class AliOcrUtil {
/**
* <b>description</b> :
* <p>使用凭据初始化账号Client</p>
* @return Client
*
* @return Client
* @throws Exception
*/
public static com.aliyun.bailian20231229.Client createClient() {
@@ -56,7 +53,6 @@ public class AliOcrUtil {
}
public static ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData applyFileUpload(byte[] bytes, String fileName) {
String md5 = DigestUtil.md5Hex(bytes);
@@ -95,7 +91,7 @@ public class AliOcrUtil {
.setCategoryId("default")
.setCategoryType("SESSION_FILE");
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
java.util.Map<String, String> headers = new java.util.HashMap<>();
java.util.Map<String, String> headers = new java.util.HashMap<>();
try {
// 复制代码运行请自行打印 API 的返回值
AddFileResponse addFileResponse = client.addFileWithOptions("llm-9zg04s7wlbvi32tq", addFileRequest, headers, runtime);
@@ -110,7 +106,7 @@ public class AliOcrUtil {
}
}
public static boolean getFileStatus(String fileId) {
public static boolean getFileStatus(String fileId) {
com.aliyun.bailian20231229.Client client = createClient();
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
java.util.Map<String, String> headers = new java.util.HashMap<>();
@@ -158,18 +154,17 @@ public class AliOcrUtil {
}
public static String appCall(byte[] bytes, String fileName) {
String id = null;
public static String appCall(byte[] bytes, String fileName) {
String id;
try {
id = getSessionId(bytes, fileName);
} catch (Exception e) {
throw new RuntimeException(e);
}
ApplicationParam param = ApplicationParam.builder()
ApplicationParam param = ApplicationParam.builder()
.apiKey("sk-2343af4413834ad1ab43b036e3a903de")
.appId("cd612ac509a4499f8ac68a656532d4ae")
.prompt("你是一名票据OCR结构化专家请从我提供的票据图片中智能提取信息并只输出JSON不得添加解释、不补充不存在内容、不得返回空字符串、字段缺失填null、字段不可省略、数字一律用字符串使用以下固定JSON结构{\"documentType\":\"\",\"orderNumber\":\"\",\"date\":\"\",\"customerName\":\"\",\"operator\":\"\",\"items\":[{\"conName\":\"\",\"spec\":\"\",\"unitName\":\"\",\"inOutNumber\":\"\",\"purchasePrice\":\"\",\"subTotal\":\"\"}],\"totalAmount\":\"\",\"remark\":\"\"}。字段映射规则documentType对应单据类型/销售单/采购单/出货单orderNumber对应单号/编号/Nodate对应日期/开单日期customerName对应客户名称/收货单位/供应商operator对应业务员/经办人/制单人/操作员items.conName对应品名/名称items.spec对应规格/型号items.unitName对应单位items.inOutNumber对应数量items.purchasePrice对应单价items.subTotal对应金额/小计totalAmount对应总金额/合计金额remark对应备注。严禁生成图片中不存在的字段内容看不清或未出现的字段必须为null不允许推测或补全不得生成多余字段items只能根据识别到的行生成不得虚构。必须能识别旋转、倾斜、模糊、撕裂、光照差异、列顺序混乱、无表格线等情况并尽量恢复信息。最终输出必须是纯JSON不得包含任何非JSON字符。")
.prompt("你是一名票据OCR结构化专家请从我提供的票据图片中智能提取信息并只输出JSON不得添加解释、不补充不存在内容、不得返回空字符串、字段缺失填null、字段不可省略、数字一律用字符串使用以下固定JSON结构{\"documentType\":\"\",\"orderNumber\":\"\",\"date\":\"\",\"customerName\":\"\",\"operator\":\"\",\"items\":[{\"conName\":\"\",\"spec\":\"\",\"unitName\":\"\",\"inOutNumber\":\"\",\"purchasePrice\":\"\",\"subTotal\":\"\"}],\"totalAmount\":\"\",\"remark\":\"\"}。字段映射规则documentType对应单据类型/销售单/采购单/出货单orderNumber对应单号/编号/Nodate对应日期/开单日期customerName对应客户名称/收货单位/供应商operator对应业务员/经办人/制单人/操作员items.conName对应品名/名称items.spec对应规格/型号items.unitName对应单位items.inOutNumber对应数量items.purchasePrice对应单价items.subTotal对应金额/小计totalAmount对应总金额/合计金额remark对应备注。严禁生成图片中不存在的字段内容看不清或未出现的字段必须为null不允许推测或补全不得生成多余字段items只能根据识别到的行生成不得虚构。必须能识别旋转、倾斜、模糊、撕裂、光照差异、列顺序混乱、无表格线等情况并尽量恢复信息。最终输出必须是纯JSON不得包含任何非JSON字符。")
.ragOptions(RagOptions.builder()
.sessionFileIds(List.of(id))
.build())
@@ -187,11 +182,4 @@ public class AliOcrUtil {
return result.getOutput().getText();
}
public static void main(String[] args) {
}
static void main() throws Exception {
}
}

View File

@@ -32,7 +32,7 @@ public interface ProductMapper extends BaseMapper<Product> {
List<ShopProductVo> selectHotsProductList(@Param("shopId") Long shopId);
List<ShopProductVo> selectGroupProductList(@Param("shopId") Long shopId);
List<ShopProductVo> selectGroupProductList(@Param("shopId") Long shopId, @Param("idList") List<Long> idList);
ShopProductInfoVo selectOneProductInfo(@Param("id") Long id, @Param("shopId") Long shopId);

View File

@@ -6,14 +6,12 @@ import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONWriter;
import com.czg.constant.CacheConstant;
import com.czg.constants.SystemConstants;
import com.czg.exception.CzgException;
import com.czg.product.dto.ProdConsRelationDTO;
import com.czg.product.dto.ProdSkuDTO;
import com.czg.product.dto.ProductDTO;
import com.czg.product.dto.ShopProdCategoryDTO;
import com.czg.product.dto.*;
import com.czg.product.entity.*;
import com.czg.product.enums.*;
import com.czg.product.param.*;
@@ -310,6 +308,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
dto.setSkuList(skuList);
List<ProdConsRelationDTO> consList = prodConsRelationMapper.selectListByProdId(dto.getId());
dto.setConsList(consList);
dto.setRelatedRecommendJson(getRelateProductList(dto.getRelatedRecommend()));
return dto;
}
@@ -336,6 +335,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
}
entity.setIsDel(SystemConstants.OneZero.ZERO);
entity.setShopId(shopId);
entity.setRelatedRecommend(dto.getRelatedRecommendStr());
super.save(entity);
dto.setId(entity.getId());
// 清除商品分类列表缓存
@@ -404,6 +404,7 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
if (!ProductTypeEnum.SKU.value().equals(entity.getType())) {
UpdateChain.of(Product.class).set(Product::getSpecId, null).eq(Product::getId, dto.getId()).update();
}
entity.setRelatedRecommend(dto.getRelatedRecommendStr());
super.updateById(entity);
// 清除商品分类列表缓存
clearProductCache(old.getCategoryId());
@@ -788,4 +789,16 @@ public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> impl
String key = StrUtil.format(CacheConstant.SHOP_PRODUCT_STOCK, shopId, productId);
redisService.set(key, stockNumber);
}
private List<RelatedProductDTO> getRelateProductList(String relatedProduct) {
if (StrUtil.isNotBlank(relatedProduct) && !"[]".equals(relatedProduct)) {
List<Long> idList = JSONArray.parseArray(relatedProduct, Long.class);
QueryWrapper wrapper = QueryWrapper.create().select(Product::getId, Product::getName, Product::getCoverImg).eq(Product::getIsDel, SystemConstants.OneZero.ZERO)
.in(Product::getId, idList);
return super.listAs(wrapper, RelatedProductDTO.class);
}
return new ArrayList<>();
}
}

View File

@@ -6,6 +6,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.czg.constant.CacheConstant;
import com.czg.constants.SystemConstants;
import com.czg.exception.CzgException;
@@ -27,6 +28,7 @@ import com.czg.service.product.mapper.ProdGroupMapper;
import com.czg.service.product.mapper.ProdGroupRelationMapper;
import com.czg.service.product.mapper.ProdSkuMapper;
import com.czg.service.product.mapper.ProductMapper;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -78,7 +80,7 @@ public class UProductServiceImpl extends ServiceImpl<ProductMapper, Product> imp
query().select(ProdGroup::getId, ProdGroup::getName, ProdGroup::getSortMode, ProdGroup::getUseTime, ProdGroup::getSaleStartTime, ProdGroup::getSaleEndTime)
.eq(ProdGroup::getShopId, shopId).eq(ProdGroup::getStatus,SystemConstants.OneZero.ONE)
.orderBy(ProdGroup::getSort, true), ShopGroupProductVo.class);
List<ShopProductVo> productAllList = productMapper.selectGroupProductList(shopId);
List<ShopProductVo> productAllList = productMapper.selectGroupProductList(shopId, null);
productAllList.forEach(item -> {
List<ProdSkuDTO> skuList = prodSkuMapper.selectListByQueryAs(query()
.eq(ProdSku::getProductId, item.getId())
@@ -233,6 +235,31 @@ public class UProductServiceImpl extends ServiceImpl<ProductMapper, Product> imp
return SystemConstants.OneZero.ZERO;
}
@Override
public List<ShopProductVo> queryProductRelatedList(Long productId) {
Product product = getOne(QueryWrapper.create().eq(Product::getId, productId)
.eq(Product::getShopId, StpKit.USER.getShopId())
.eq(Product::getIsDel, SystemConstants.OneZero.ZERO));
if (product == null) {
throw new CzgException("商品信息不存在");
}
if (StrUtil.isBlank(product.getRelatedRecommend()) || "[]".equals(product.getRelatedRecommend())) {
return new ArrayList<>();
}
List<ShopProductVo> productAllList = productMapper.selectGroupProductList(product.getShopId(), JSONArray.parseArray(product.getRelatedRecommend(), Long.class));
productAllList.forEach(item -> {
List<ProdSkuDTO> skuList = prodSkuMapper.selectListByQueryAs(query()
.eq(ProdSku::getProductId, item.getId())
.eq(ProdSku::getIsGrounding, SystemConstants.OneZero.ONE)
.eq(ProdSku::getIsDel, SystemConstants.OneZero.ZERO), ProdSkuDTO.class);
item.setSkuList(skuList);
});
return productAllList;
}
/**
* 计算是否在可售时间内
*

View File

@@ -147,6 +147,12 @@
and t2.sale_price is not null
and t1.shop_id = #{shopId}
</where>
<if test="idList != null">
and t1.id in
<foreach item="item" collection="idList" separator="," open="(" close=")">
#{item}
</foreach>
</if>
order by t1.sort desc,t1.id desc
</select>
<select id="selectOneProductInfo" resultType="com.czg.product.vo.ShopProductInfoVo">