feat:模拟数据
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package com.crrc.crrcmall.common.mock;
|
||||
|
||||
import com.crrc.crrcmall.dto.audit.AuditContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审核模拟数据生成器
|
||||
* 用于 Demo 环境,提供不同场景的商品数据和 AI 模拟结果
|
||||
*/
|
||||
@Service
|
||||
public class AuditMockDataGenerator {
|
||||
|
||||
/**
|
||||
* 根据 auditId 获取模拟的商品上下文数据
|
||||
* 支持多种测试场景:
|
||||
* - "01": 完美商品 (全部通过)
|
||||
* - "02": 条码错误 (含非法字符)
|
||||
* - "03": 物料编码错误 (前缀不匹配)
|
||||
* - 默认: 普通办公用品
|
||||
*/
|
||||
public AuditContext getGoodsContext(String auditId) {
|
||||
AuditContext context = new AuditContext();
|
||||
|
||||
// 默认值
|
||||
context.setProductType("OFFICE");
|
||||
context.setShippingFee("全国包邮");
|
||||
context.setDeliveryArea("不限配送区域");
|
||||
context.setTaxCode("1090212020000000000");
|
||||
context.setDescription("这是商品的详细描述,内容真实有效,无夸大宣传。");
|
||||
|
||||
if ("01".equals(auditId)) {
|
||||
// 场景 1: 完美商品
|
||||
context.setGoodsName("得力(deli) S01 中性笔 0.5mm 黑色 12支/盒");
|
||||
context.setBarcode("6901234567890");
|
||||
context.setMaterialCode("DB100123");
|
||||
context.setBrandName("得力");
|
||||
context.setImageUrls(List.of("https://example.com/image1.jpg"));
|
||||
} else if ("02".equals(auditId)) {
|
||||
// 场景 2: 条码错误 (包含汉字)
|
||||
context.setGoodsName("晨光 M&G 笔记本 A5");
|
||||
context.setBarcode("690123中文456"); // 错误点
|
||||
context.setMaterialCode("DB100456");
|
||||
context.setBrandName("晨光");
|
||||
context.setImageUrls(List.of("https://example.com/image2.jpg"));
|
||||
} else if ("03".equals(auditId)) {
|
||||
// 场景 3: 物料编码错误 (工业品用了 DB 开头)
|
||||
context.setGoodsName("重型工业轴承 6204");
|
||||
context.setBarcode("6909876543210");
|
||||
context.setMaterialCode("DB999888"); // 错误点:工业品应为 DM 开头
|
||||
context.setProductType("INDUSTRIAL");
|
||||
context.setBrandName("SKF");
|
||||
context.setImageUrls(List.of("https://example.com/image3.jpg"));
|
||||
} else {
|
||||
// 默认场景
|
||||
context.setGoodsName("标准办公商品示例");
|
||||
context.setBarcode("6901234567890");
|
||||
context.setMaterialCode("DB123456");
|
||||
context.setBrandName("通用品牌");
|
||||
context.setImageUrls(List.of("https://example.com/default.jpg"));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AI 模拟审核结果
|
||||
* 用于填充原本需要调用大模型的字段
|
||||
*/
|
||||
public AiMockResult getAiMockResult(String fieldName, Object value) {
|
||||
AiMockResult result = new AiMockResult();
|
||||
result.setSuccess(true);
|
||||
result.setScore(0.95 + Math.random() * 0.05); // 随机高分
|
||||
|
||||
switch (fieldName) {
|
||||
case "goodsName":
|
||||
result.setMessage("AI 语义分析:名称结构完整 [品牌+品名+型号+规格],无违禁词。");
|
||||
break;
|
||||
case "brandName":
|
||||
result.setMessage("AI 比对:品牌项与标题中的品牌一致。");
|
||||
break;
|
||||
case "imageUrls":
|
||||
result.setMessage("视觉模型:图片清晰,未检测到二维码、水印或违规 Logo。");
|
||||
break;
|
||||
case "description":
|
||||
result.setMessage("NLP 分析:详情描述真实,未发现夸大宣传词汇。");
|
||||
break;
|
||||
case "marketPriceUrl":
|
||||
result.setMessage("爬虫校验:链接有效,确认为供应商官网。");
|
||||
break;
|
||||
default:
|
||||
result.setMessage("AI 辅助校验通过。");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 模拟结果内部类
|
||||
*/
|
||||
public static class AiMockResult {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private double score;
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public double getScore() { return score; }
|
||||
public void setScore(double score) { this.score = score; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.crrc.crrcmall.common.mock;
|
||||
|
||||
import com.crrc.crrcmall.dto.crrc.SearchGoodsRequestDto;
|
||||
import com.crrc.crrcmall.vo.crrc.GoodsSearchResultVo;
|
||||
import com.crrc.crrcmall.vo.crrc.GoodsVo;
|
||||
import com.crrc.crrcmall.vo.crrc.BrandVo;
|
||||
import com.crrc.crrcmall.vo.crrc.CatalogVo;
|
||||
import com.crrc.crrcmall.vo.crrc.PurchaserCatalogVo;
|
||||
import com.crrc.crrcmall.vo.crrc.SpecVo;
|
||||
import com.crrc.crrcmall.vo.crrc.SpecValueVo;
|
||||
import com.crrc.crrcmall.vo.ai.AiSearchInfoVo;
|
||||
import com.crrc.crrcmall.common.enums.MatchTypeEnum;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 搜索模拟数据生成器
|
||||
* 基于真实返回结果构建 Demo 数据,并叠加 AI 增强字段
|
||||
*/
|
||||
@Service
|
||||
public class SearchMockDataGenerator {
|
||||
|
||||
/**
|
||||
* 生成搜索结果
|
||||
* @param request 搜索请求
|
||||
* @return 包含 AI 增强信息的搜索结果
|
||||
*/
|
||||
public GoodsSearchResultVo generateResult(SearchGoodsRequestDto request) {
|
||||
GoodsSearchResultVo result = new GoodsSearchResultVo();
|
||||
|
||||
// 1. 基础信息
|
||||
result.setAreaId("55646");
|
||||
result.setAreaName("北京/丰台区/西罗园街道");
|
||||
result.setIsSelf(true);
|
||||
result.setIsShowPriceUnlogin("1");
|
||||
|
||||
// 2. AI 搜索解析信息 (叠加在原有结果之上)
|
||||
AiSearchInfoVo aiInfo = new AiSearchInfoVo();
|
||||
aiInfo.setOriginalQuery(request.getGoodsName() != null ? request.getGoodsName() : "");
|
||||
aiInfo.setIntent("办公用品 - 书写/修正用品");
|
||||
aiInfo.setSuggestion("您是不是想找:签字笔、圆珠笔?");
|
||||
aiInfo.setExpandedTerms(Arrays.asList("签字笔", "水笔", "书写工具"));
|
||||
aiInfo.setMatchType(MatchTypeEnum.SEMANTIC);
|
||||
result.setAiSearchInfo(aiInfo);
|
||||
|
||||
// 3. 模拟品牌列表 (取真实数据)
|
||||
List<BrandVo> brandList = new ArrayList<>();
|
||||
brandList.add(createBrandVo("258123000988416", "齐心/Comix", "齐"));
|
||||
brandList.add(createBrandVo("100657835537516201", "晨光/M&G", "晨"));
|
||||
brandList.add(createBrandVo("1222540037137240064", "得力/deli", "得"));
|
||||
result.setBrandList(brandList);
|
||||
|
||||
// 4. 模拟目录列表 (取真实数据)
|
||||
List<CatalogVo> catalogList = new ArrayList<>();
|
||||
catalogList.add(createCatalogVo("1750717233614614529", "中性笔"));
|
||||
catalogList.add(createCatalogVo("1750717233572671489", "笔芯"));
|
||||
catalogList.add(createCatalogVo("1750717234243760130", "其他笔类"));
|
||||
result.setCatalogList(catalogList);
|
||||
|
||||
// 5. 模拟采购人目录列表
|
||||
List<PurchaserCatalogVo> purchaserCatalogList = new ArrayList<>();
|
||||
purchaserCatalogList.add(createPurchaserCatalogVo("1750717233614614529", "中性笔"));
|
||||
purchaserCatalogList.add(createPurchaserCatalogVo("1750717233572671489", "笔芯"));
|
||||
result.setPurchaserCatalogList(purchaserCatalogList);
|
||||
|
||||
// 6. 模拟规格列表 (简化版)
|
||||
List<SpecVo> specList = new ArrayList<>();
|
||||
specList.add(createSpecVo("型号", Arrays.asList("K3511", "6600ES", "S01")));
|
||||
specList.add(createSpecVo("品牌", Arrays.asList("齐心/Comix", "晨光/M&G", "得力/deli")));
|
||||
result.setSpecList(specList);
|
||||
|
||||
// 7. 商品列表 (取真实数据的前 3 个,并添加 AI 字段)
|
||||
List<GoodsVo> goodsList = new ArrayList<>();
|
||||
|
||||
// 商品 1: 齐心 K3511
|
||||
GoodsVo g1 = new GoodsVo();
|
||||
g1.setGoodsId("250344595341056");
|
||||
g1.setGoodsName("齐心Comix) 0.5mm 子弹头中性笔 12支*1盒 K3511 黑色(单位:盒)");
|
||||
g1.setGoodsCode("XM02040089");
|
||||
g1.setMaterialCode("DB00006298");
|
||||
g1.setGoodsImage("https://mall.crrcgo.cc/common/img.comix.com.cn/product/1001685297/img800/1.jpg");
|
||||
g1.setSkuImage("https://mall.crrcgo.cc/common/img.comix.com.cn/product/1001685297/img800/1.jpg");
|
||||
g1.setGoodsPrice(new BigDecimal("12.15"));
|
||||
g1.setSupplierId("759113572558180352");
|
||||
g1.setSupplierName("齐心");
|
||||
g1.setProtocolType("02");
|
||||
g1.setPurchaserCatalogId("1750717233614614529");
|
||||
g1.setPurchaserCatalogName("中性笔");
|
||||
g1.setPurchaserCatalogFullName("行政办公-文具-书写/修正用品-中性笔");
|
||||
g1.setCatalogId("1750717233614614529");
|
||||
g1.setBrandId("258123000988416");
|
||||
g1.setBrandName("齐心/Comix");
|
||||
g1.setProtocolId("1208779682986946560");
|
||||
g1.setGoodsSourceType(0);
|
||||
g1.setMarketPrice(new BigDecimal("22"));
|
||||
g1.setIsGroup(false);
|
||||
g1.setSales(28705);
|
||||
g1.setSaleStatus(1);
|
||||
g1.setOriginalPrice(new BigDecimal("12.15"));
|
||||
g1.setSpecialAreaCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E0/Eg-DFGacjvWARFNJAAAG-U8Uuuc215.png");
|
||||
g1.setPortalCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E1/Eg-DFGadtP2AUu03AAAFYj9phaw575.png");
|
||||
g1.setHighlightGoodsName("齐心Comix) 0.5mm 子弹头<em style='color: red'>中性</em><em style='color: red'>笔</em> 12支*1盒 K3511 黑色(单位:盒)");
|
||||
g1.setIsPostStation(false);
|
||||
// AI 增强字段
|
||||
g1.setAiRelevanceScore(0.98);
|
||||
goodsList.add(g1);
|
||||
|
||||
// 商品 2: kaco中性笔
|
||||
GoodsVo g2 = new GoodsVo();
|
||||
g2.setGoodsId("1162049076309274624");
|
||||
g2.setGoodsName("kaco中性笔145mm");
|
||||
g2.setGoodsCode("sc00220221101000021");
|
||||
g2.setMaterialCode("");
|
||||
g2.setGoodsImage("/supplier/images/group1/M00/1C/BD/wKiAAmNgx36APqdMAADKRhtpqYQ395.jpg");
|
||||
g2.setSkuImage("/supplier/images/group1/M00/1C/BD/wKiAAmNgx36APqdMAADKRhtpqYQ395.jpg");
|
||||
g2.setGoodsPrice(new BigDecimal("2.8"));
|
||||
g2.setSupplierId("1035673654182154240");
|
||||
g2.setSupplierName("中车智程文化科技(北京)有限公司");
|
||||
g2.setProtocolType("02");
|
||||
g2.setPurchaserCatalogId("1712341648069373954");
|
||||
g2.setPurchaserCatalogName("办公定制");
|
||||
g2.setPurchaserCatalogFullName("[选品]办公用品-行政办公-办公定制");
|
||||
g2.setCatalogId("1712341648069373954");
|
||||
g2.setBrandId("0");
|
||||
g2.setProtocolId("1160903739087532032");
|
||||
g2.setGoodsSourceType(0);
|
||||
g2.setMarketPrice(new BigDecimal("2.8"));
|
||||
g2.setIsGroup(false);
|
||||
g2.setSales(22317);
|
||||
g2.setSaleStatus(1);
|
||||
g2.setOriginalPrice(new BigDecimal("2.8"));
|
||||
g2.setSpecialAreaCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E0/Eg-DFGacjvWARFNJAAAG-U8Uuuc215.png");
|
||||
g2.setPortalCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E1/Eg-DFGadtP2AUu03AAAFYj9phaw575.png");
|
||||
g2.setHighlightGoodsName("kaco<em style='color: red'>中性</em><em style='color: red'>笔</em>145mm");
|
||||
g2.setIsPostStation(false);
|
||||
// AI 增强字段
|
||||
g2.setAiRelevanceScore(0.92);
|
||||
goodsList.add(g2);
|
||||
|
||||
// 商品 3: 得力 6600ES
|
||||
GoodsVo g3 = new GoodsVo();
|
||||
g3.setGoodsId("1220406174674522112");
|
||||
g3.setGoodsName("中性笔 得力6600ES中性笔0.5mm子弹头12支/盒 黑(单位:盒)");
|
||||
g3.setGoodsCode("100002862BX1");
|
||||
g3.setMaterialCode("DB00006244");
|
||||
g3.setGoodsImage("https://mall.crrcgo.cc/commons/img.jslink.com/front/fileserver/image/FILEe2f149458e6a49af9d2813b8813e670d.jpg");
|
||||
g3.setSkuImage("https://mall.crrcgo.cc/commons/img.jslink.com/front/fileserver/image/FILEe2f149458e6a49af9d2813b8813e670d.jpg");
|
||||
g3.setGoodsPrice(new BigDecimal("7.31"));
|
||||
g3.setSupplierId("759108823876243456");
|
||||
g3.setSupplierName("得力");
|
||||
g3.setProtocolType("02");
|
||||
g3.setPurchaserCatalogId("1750717233614614529");
|
||||
g3.setPurchaserCatalogName("中性笔");
|
||||
g3.setPurchaserCatalogFullName("行政办公-文具-书写/修正用品-中性笔");
|
||||
g3.setCatalogId("1750717233614614529");
|
||||
g3.setBrandId("1222540037137240064");
|
||||
g3.setBrandName("得力/deli");
|
||||
g3.setProtocolId("1208779682986946560");
|
||||
g3.setGoodsSourceType(0);
|
||||
g3.setMarketPrice(new BigDecimal("24"));
|
||||
g3.setIsGroup(false);
|
||||
g3.setSales(6704);
|
||||
g3.setSaleStatus(1);
|
||||
g3.setOriginalPrice(new BigDecimal("7.31"));
|
||||
g3.setSpecialAreaCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E0/Eg-DFGacjvWARFNJAAAG-U8Uuuc215.png");
|
||||
g3.setPortalCornerLogo("https://mall.crrcgo.cc/fdsimage/group1/M00/02/E1/Eg-DFGadtP2AUu03AAAFYj9phaw575.png");
|
||||
g3.setHighlightGoodsName("<em style='color: red'>中性</em><em style='color: red'>笔</em> 得力6600ES<em style='color: red'>中性</em><em style='color: red'>笔</em>0.5mm子弹头12支/盒 黑(单位:盒)");
|
||||
g3.setIsPostStation(false);
|
||||
// AI 增强字段
|
||||
g3.setAiRelevanceScore(0.89);
|
||||
goodsList.add(g3);
|
||||
|
||||
result.setGoodsList(goodsList);
|
||||
result.setTotal(goodsList.size());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private BrandVo createBrandVo(String id, String name, String letter) {
|
||||
BrandVo vo = new BrandVo();
|
||||
vo.setBrandId(id);
|
||||
vo.setBrandName(name);
|
||||
vo.setFirstLetter(letter);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private CatalogVo createCatalogVo(String id, String name) {
|
||||
CatalogVo vo = new CatalogVo();
|
||||
vo.setCatalogId(id);
|
||||
vo.setCatalogName(name);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private PurchaserCatalogVo createPurchaserCatalogVo(String id, String name) {
|
||||
PurchaserCatalogVo vo = new PurchaserCatalogVo();
|
||||
vo.setPurchaserCatalogId(id);
|
||||
vo.setPurchaserCatalogName(name);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private SpecVo createSpecVo(String name, List<String> values) {
|
||||
SpecVo vo = new SpecVo();
|
||||
vo.setSpecId(name);
|
||||
vo.setSpecName(name);
|
||||
List<SpecValueVo> valueList = new ArrayList<>();
|
||||
for (String v : values) {
|
||||
SpecValueVo sv = new SpecValueVo();
|
||||
sv.setValueId(v);
|
||||
sv.setValueName(v);
|
||||
valueList.add(sv);
|
||||
}
|
||||
vo.setSpecValueList(valueList);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,10 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(apiAuthInterceptor)
|
||||
// 拦截所有 /api/v1/ 开头的请求
|
||||
.addPathPatterns("/api/v1/**")
|
||||
// 排除 Swagger 和错误页面
|
||||
.excludePathPatterns("/swagger-ui/**", "/api-docs/**", "/error");
|
||||
// registry.addInterceptor(apiAuthInterceptor)
|
||||
// // 拦截所有 /api/v1/ 开头的请求
|
||||
// .addPathPatterns("/api/v1/**")
|
||||
// // 排除 Swagger 和错误页面
|
||||
// .excludePathPatterns("/swagger-ui/**", "/api-docs/**", "/error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.crrc.crrcmall.controller;
|
||||
|
||||
import com.crrc.crrcmall.dto.audit.AuditBatchRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditSearchRequestDto;
|
||||
import com.crrc.crrcmall.service.AuditService;
|
||||
@@ -29,6 +30,12 @@ public class AuditController {
|
||||
return auditService.submitAuditCheck(request);
|
||||
}
|
||||
|
||||
@Operation(summary = "批量提交审核任务")
|
||||
@PostMapping("/checks")
|
||||
public AuditCheckResponseVo checkAuditBatch(@RequestBody AuditBatchRequestDto request) {
|
||||
return auditService.submitAuditCheckBatch(request);
|
||||
}
|
||||
|
||||
@Operation(summary = "查询审核结果")
|
||||
@GetMapping("/search")
|
||||
public AuditDetailResponseVo searchAuditResult(@ModelAttribute AuditSearchRequestDto request) {
|
||||
|
||||
@@ -27,4 +27,10 @@ public class SearchController {
|
||||
public GoodsSearchResultVo searchByText(@RequestBody SearchGoodsRequestDto request) {
|
||||
return searchService.searchByText(request);
|
||||
}
|
||||
|
||||
@Operation(summary = "图片搜索", description = "接收自然语言查询,返回 AI 增强后的商品列表")
|
||||
@PostMapping("/image")
|
||||
public GoodsSearchResultVo searchByImage(@RequestBody SearchGoodsRequestDto request) {
|
||||
return searchService.searchByImage(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.crrc.crrcmall.dto.audit;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量提交审核任务请求 DTO (v2)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "批量提交审核任务请求")
|
||||
public class AuditBatchRequestDto implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "审核任务信息列表")
|
||||
private List<AuditInfo> auditInfo;
|
||||
|
||||
@Schema(description = "回调地址")
|
||||
private String callbackUrl;
|
||||
|
||||
@Data
|
||||
public static class AuditInfo implements Serializable {
|
||||
@Schema(description = "商品 ID")
|
||||
private String goodsId;
|
||||
|
||||
@Schema(description = "审核 ID")
|
||||
private String auditId;
|
||||
|
||||
@Schema(description = "审核类型: 1-新品审核 2-新品复审 3-调价审核")
|
||||
private Integer type;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,9 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI 智能审核提交请求 DTO
|
||||
* 提交审核任务请求 DTO (v2)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "提交审核任务请求")
|
||||
@@ -18,9 +17,12 @@ public class AuditCheckRequestDto implements Serializable {
|
||||
@Schema(description = "商品 ID")
|
||||
private String goodsId;
|
||||
|
||||
@Schema(description = "审核 ID (客户系统唯一标识)")
|
||||
private String auditId;
|
||||
|
||||
@Schema(description = "审核类型: 1-新品审核 2-新品复审 3-调价审核")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "回调地址 (审核完成后通知客户)")
|
||||
private String callbackUrl;
|
||||
|
||||
@Schema(description = "自定义审核项列表")
|
||||
private List<CustomItemRequestDto> customList;
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ public class AuditSearchRequestDto implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "审核任务 ID")
|
||||
private Long taskId;
|
||||
private String taskId;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package com.crrc.crrcmall.service;
|
||||
|
||||
import com.crrc.crrcmall.dto.audit.AuditContext;
|
||||
import com.crrc.crrcmall.dto.audit.CustomItemRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
|
||||
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审核规则引擎服务
|
||||
* 负责执行 13 项具体的审核规则校验
|
||||
@@ -14,9 +11,8 @@ public interface AuditRuleService {
|
||||
|
||||
/**
|
||||
* 执行全套审核规则 (13 项)
|
||||
* @param context 审核上下文数据
|
||||
* @param rawCustomList 原始请求列表 (用于回填)
|
||||
* @param request 审核请求 (包含 auditId, goodsId, type)
|
||||
* @return 包含所有校验项结果的 VO
|
||||
*/
|
||||
AuditDetailResponseVo executeAllRules(AuditContext context, List<CustomItemRequestDto> rawCustomList);
|
||||
AuditDetailResponseVo executeAllRules(AuditCheckRequestDto request);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.crrc.crrcmall.service;
|
||||
|
||||
import com.crrc.crrcmall.dto.audit.AuditBatchRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditSearchRequestDto;
|
||||
import com.crrc.crrcmall.vo.audit.AuditCheckResponseVo;
|
||||
@@ -11,10 +12,15 @@ import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
|
||||
public interface AuditService {
|
||||
|
||||
/**
|
||||
* 提交审核任务
|
||||
* 提交审核任务 (单个)
|
||||
*/
|
||||
AuditCheckResponseVo submitAuditCheck(AuditCheckRequestDto request);
|
||||
|
||||
/**
|
||||
* 提交审核任务 (批量)
|
||||
*/
|
||||
AuditCheckResponseVo submitAuditCheckBatch(AuditBatchRequestDto request);
|
||||
|
||||
/**
|
||||
* 查询审核结果
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.crrc.crrcmall.service.impl;
|
||||
|
||||
import com.crrc.crrcmall.config.RuleConfigProvider;
|
||||
import com.crrc.crrcmall.common.mock.AuditMockDataGenerator;
|
||||
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditContext;
|
||||
import com.crrc.crrcmall.dto.audit.CustomItemRequestDto;
|
||||
import com.crrc.crrcmall.service.AuditRuleService;
|
||||
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -16,278 +16,125 @@ import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 审核规则引擎实现类
|
||||
* 负责执行 13 项具体的审核规则校验
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuditRuleServiceImpl implements AuditRuleService {
|
||||
|
||||
@Autowired
|
||||
private RuleConfigProvider ruleConfigProvider;
|
||||
private AuditMockDataGenerator mockDataGenerator;
|
||||
|
||||
@Override
|
||||
public AuditDetailResponseVo executeAllRules(AuditContext context, List<CustomItemRequestDto> rawCustomList) {
|
||||
public AuditDetailResponseVo executeAllRules(AuditCheckRequestDto request) {
|
||||
// 1. 从 Mock 生成器获取商品数据 (模拟从数据库读取)
|
||||
AuditContext context = mockDataGenerator.getGoodsContext(request.getAuditId());
|
||||
|
||||
AuditDetailResponseVo response = new AuditDetailResponseVo();
|
||||
// TODO: 实际应生成 UUID
|
||||
response.setType(request.getType());
|
||||
response.setTaskId("TASK-MOCK-" + System.currentTimeMillis());
|
||||
response.setGoodsId(context.getGoodsId());
|
||||
|
||||
// 初始状态:假设全部通过,后续根据校验结果更新
|
||||
response.setStatus("SUCCESSED");
|
||||
response.setStatus(1); // 1: Completed
|
||||
|
||||
List<AuditDetailResponseVo.CustomItemResponseVo> results = new ArrayList<>();
|
||||
List<AuditDetailResponseVo.AuditGroupVo> groupList = new ArrayList<>();
|
||||
AuditDetailResponseVo.AuditGroupVo group = new AuditDetailResponseVo.AuditGroupVo();
|
||||
group.setAuditId(request.getAuditId());
|
||||
group.setGoodsId(request.getGoodsId());
|
||||
|
||||
// 1. 校验商品名称 (规则 1) - AI
|
||||
results.add(checkName(context));
|
||||
List<AuditDetailResponseVo.AuditDetailItemVo> detailList = new ArrayList<>();
|
||||
|
||||
// 2. 校验商品条码 (规则 2) - 硬规则
|
||||
results.add(checkBarcode(context));
|
||||
// 2. 执行硬规则校验 (基于获取到的 Context 数据)
|
||||
detailList.add(buildItem("barcode", "商品条码", context.getBarcode(), checkBarcode(context)));
|
||||
detailList.add(buildItem("taxCode", "税码", context.getTaxCode(), checkTaxCode(context)));
|
||||
detailList.add(buildItem("materialCode", "物料编码", context.getMaterialCode(), checkMaterialCode(context)));
|
||||
detailList.add(buildItem("shippingFee", "运费", context.getShippingFee(), checkShipping(context)));
|
||||
detailList.add(buildItem("deliveryArea", "配送区域", context.getDeliveryArea(), checkDeliveryArea(context)));
|
||||
|
||||
// 3. 校验品牌项 (规则 3) - AI
|
||||
results.add(checkBrand(context));
|
||||
// 3. 执行 AI 规则模拟 (使用 Mock 生成器生成逼真的 AI 结果)
|
||||
detailList.add(buildAiItem("goodsName", "商品名称", context.getGoodsName()));
|
||||
detailList.add(buildAiItem("brandName", "品牌项", context.getBrandName()));
|
||||
detailList.add(buildAiItem("imageUrls", "商品图片", context.getImageUrls()));
|
||||
detailList.add(buildAiItem("description", "详情描述", context.getDescription()));
|
||||
|
||||
// 4. 校验税码 (规则 4) - 硬规则
|
||||
results.add(checkTaxCode(context));
|
||||
|
||||
// 5. 校验物料编码 (规则 5) - 硬规则
|
||||
results.add(checkMaterialCode(context));
|
||||
|
||||
// 6. 校验商品目录 (规则 6) - AI
|
||||
results.add(checkCatalog(context));
|
||||
|
||||
// 7. 校验商品图片 (规则 7) - AI/Vision
|
||||
results.add(checkImages(context));
|
||||
|
||||
// 8. 校验市场价格链接 (规则 8) - 爬虫/AI
|
||||
results.add(checkMarketUrl(context));
|
||||
|
||||
// 9. 校验电脑端详情描述 (规则 9) - AI
|
||||
results.add(checkDescription(context));
|
||||
|
||||
// 10. 校验京东价格链接 (规则 10) - 爬虫
|
||||
results.add(checkJdUrl(context));
|
||||
|
||||
// 11. 校验价格 (规则 11) - 硬规则 + TODO
|
||||
results.add(checkPrice(context));
|
||||
|
||||
// 12. 校验运费 (规则 12) - 硬规则
|
||||
results.add(checkShipping(context));
|
||||
|
||||
// 13. 校验配送区域 (规则 13) - 硬规则
|
||||
results.add(checkDeliveryArea(context));
|
||||
|
||||
response.setCustomList(results);
|
||||
|
||||
// 如果有任何一项 FAIL,则整体状态改为 FAIL
|
||||
boolean hasFail = results.stream().anyMatch(r -> "FAIL".equals(r.getCheckStatus()));
|
||||
if (hasFail) {
|
||||
response.setStatus("FAIL");
|
||||
}
|
||||
group.setAuditDetailList(detailList);
|
||||
groupList.add(group);
|
||||
response.setAuditList(groupList);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建硬规则结果项
|
||||
*/
|
||||
private AuditDetailResponseVo.AuditDetailItemVo buildItem(String field, String name, Object value, RuleCheckResult result) {
|
||||
AuditDetailResponseVo.AuditDetailItemVo item = new AuditDetailResponseVo.AuditDetailItemVo();
|
||||
item.setCustomField(field);
|
||||
item.setCustomKey(name);
|
||||
item.setCustomValue(value);
|
||||
item.setAiIntent(result.message);
|
||||
item.setAiRelevanceScore(result.score);
|
||||
item.setCheckType(1); // 1: Hard Rule
|
||||
item.setAiIntentType(1);
|
||||
item.setAuditStatus(result.isSuccess ? 0 : 1);
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则 2:商品条码校验
|
||||
* 要求:条码真实有效,与商品信息一致;非必填项可为空。禁止:填写条码以外的数字、汉字、字母等内容。
|
||||
* 构建 AI 规则结果项 (模拟)
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkBarcode(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("barcode", "商品条码");
|
||||
private AuditDetailResponseVo.AuditDetailItemVo buildAiItem(String field, String name, Object value) {
|
||||
AuditMockDataGenerator.AiMockResult aiResult = mockDataGenerator.getAiMockResult(field, value);
|
||||
AuditDetailResponseVo.AuditDetailItemVo item = new AuditDetailResponseVo.AuditDetailItemVo();
|
||||
item.setCustomField(field);
|
||||
item.setCustomKey(name);
|
||||
item.setCustomValue(value);
|
||||
item.setAiIntent(aiResult.getMessage());
|
||||
item.setAiRelevanceScore(aiResult.getScore());
|
||||
item.setCheckType(2); // 2: AI Rule
|
||||
item.setAiIntentType(1);
|
||||
item.setAuditStatus(aiResult.isSuccess() ? 0 : 1);
|
||||
return item;
|
||||
}
|
||||
|
||||
private record RuleCheckResult(boolean isSuccess, String message, double score) {}
|
||||
|
||||
// ==================== 硬规则校验逻辑 ====================
|
||||
|
||||
private RuleCheckResult checkBarcode(AuditContext context) {
|
||||
String code = context.getBarcode();
|
||||
if (StringUtils.hasText(code)) {
|
||||
// 校验规则:只能包含数字和字母 (正则)
|
||||
// TODO: 实际业务可能需要调用第三方 API 校验条码真实性
|
||||
boolean isValid = Pattern.matches("^[a-zA-Z0-9]+$", code);
|
||||
if (!isValid) {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("条码包含非法字符(如汉字或特殊符号)");
|
||||
} else {
|
||||
res.setCheckStatus("PASS");
|
||||
res.setAiIntent("格式校验通过");
|
||||
}
|
||||
} else {
|
||||
res.setCheckStatus("PASS"); // 非必填
|
||||
res.setAiIntent("条码为空,跳过校验");
|
||||
if (StringUtils.hasText(code) && Pattern.matches("^[a-zA-Z0-9]+$", code)) {
|
||||
return new RuleCheckResult(true, "格式校验通过", 1.0);
|
||||
}
|
||||
return res;
|
||||
return new RuleCheckResult(false, "条码格式错误 (含非法字符)", 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则 4:税码校验
|
||||
* 要求:税码分类正确,且不得为空。
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkTaxCode(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("taxCode", "税码");
|
||||
if (!StringUtils.hasText(context.getTaxCode())) {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("税码不能为空");
|
||||
} else {
|
||||
res.setCheckStatus("PASS");
|
||||
res.setAiIntent("税码已填写");
|
||||
private RuleCheckResult checkTaxCode(AuditContext context) {
|
||||
if (StringUtils.hasText(context.getTaxCode())) {
|
||||
return new RuleCheckResult(true, "税码已填写", 1.0);
|
||||
}
|
||||
return res;
|
||||
return new RuleCheckResult(false, "税码不能为空", 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则 5:物料编码校验
|
||||
* 要求:工业品以 DM 开头,办公用品以 DB 开头。
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkMaterialCode(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("materialCode", "物料编码");
|
||||
private RuleCheckResult checkMaterialCode(AuditContext context) {
|
||||
String code = context.getMaterialCode();
|
||||
String prefix = "OFFICE".equalsIgnoreCase(context.getProductType()) ? "DB" : "DM";
|
||||
|
||||
if (!StringUtils.hasText(code)) {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("物料编码不能为空");
|
||||
return res;
|
||||
if (StringUtils.hasText(code) && code.startsWith(prefix)) {
|
||||
return new RuleCheckResult(true, "物料编码前缀正确 (" + prefix + ")", 1.0);
|
||||
}
|
||||
return new RuleCheckResult(false, "物料编码前缀错误 (期望 " + prefix + " 开头)", 0.0);
|
||||
}
|
||||
|
||||
// 获取当前商品类别的期望前缀 (从配置读取)
|
||||
String expectedPrefix = ruleConfigProvider.getMaterialCodePrefix(context.getProductType());
|
||||
|
||||
if (code.startsWith(expectedPrefix)) {
|
||||
res.setCheckStatus("PASS");
|
||||
res.setAiIntent("物料编码前缀正确 (" + expectedPrefix + ")");
|
||||
} else {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("物料编码前缀错误。当前类目 [" + context.getProductType() + "] 期望以 [" + expectedPrefix + "] 开头");
|
||||
private RuleCheckResult checkShipping(AuditContext context) {
|
||||
if ("全国包邮".equals(context.getShippingFee())) {
|
||||
return new RuleCheckResult(true, "运费符合标准", 1.0);
|
||||
}
|
||||
return res;
|
||||
return new RuleCheckResult(false, "运费不符合要求", 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则 12:运费校验
|
||||
* 要求:全国包邮
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkShipping(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("shippingFee", "运费");
|
||||
String required = ruleConfigProvider.getRequiredShippingFee();
|
||||
|
||||
if (required.equals(context.getShippingFee())) {
|
||||
res.setCheckStatus("PASS");
|
||||
res.setAiIntent("运费配置符合标准");
|
||||
} else {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("运费不符合要求,应为 [" + required + "]");
|
||||
private RuleCheckResult checkDeliveryArea(AuditContext context) {
|
||||
if ("不限配送区域".equals(context.getDeliveryArea())) {
|
||||
return new RuleCheckResult(true, "配送区域符合标准", 1.0);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规则 13:配送区域校验
|
||||
* 要求:不限配送区域
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkDeliveryArea(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("deliveryArea", "配送区域");
|
||||
String required = ruleConfigProvider.getRequiredDeliveryArea();
|
||||
|
||||
if (required.equals(context.getDeliveryArea())) {
|
||||
res.setCheckStatus("PASS");
|
||||
res.setAiIntent("配送区域符合标准");
|
||||
} else {
|
||||
res.setCheckStatus("FAIL");
|
||||
res.setAiIntent("配送区域不符合要求,应为 [" + required + "]");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 1. 校验结构 [品牌 + 名称 + 型号 + 规格]
|
||||
* 2. 校验违禁词 (从 RuleConfigProvider 获取配置)
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkName(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("goodsName", "商品名称");
|
||||
// TODO: 1. 校验结构 [品牌 + 名称 + 型号 + 规格]
|
||||
// TODO: 2. 校验违禁词 (从 RuleConfigProvider 获取配置)
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待 AI 语义分析");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验品牌项与商品名称中的品牌是否一致
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkBrand(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("brandName", "品牌项");
|
||||
// TODO: 校验品牌项与商品名称中的品牌是否一致
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待 AI 比对");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验类目归属合理性,防跨类
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkCatalog(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("catalogId", "商品目录");
|
||||
// TODO: 校验类目归属合理性,防跨类
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待 AI 类目映射");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验商品图片清晰度、无二维码、无违规 Logo
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkImages(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("imageUrls", "商品图片");
|
||||
// TODO: 调用 Qwen-VL 校验图片清晰度、无二维码、无违规 Logo
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待视觉模型分析");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验链接有效性,是否为官网
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkMarketUrl(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("marketPriceUrl", "市场价格链接");
|
||||
// TODO: 校验链接有效性,是否为官网
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待爬虫校验");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验描述真实性、无夸大、无违禁词
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkDescription(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("description", "电脑端详情描述");
|
||||
// TODO: 校验描述真实性、无夸大、无违禁词
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待 AI 文本分析");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否为京东自营链接,是否有效
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkJdUrl(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("jdPriceUrl", "京东价格链接");
|
||||
// TODO: 校验是否为京东自营链接,是否有效
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待京东链接校验");
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 价格 <= 入围价格
|
||||
*/
|
||||
private AuditDetailResponseVo.CustomItemResponseVo checkPrice(AuditContext context) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("price", "价格 [单价]");
|
||||
// TODO: 校验 价格 <= 入围价格
|
||||
// TODO: 获取 context.getTargetPrice() 进行比对
|
||||
res.setCheckStatus("WAITING");
|
||||
res.setAiIntent("待入围价格数据接入后校验");
|
||||
return res;
|
||||
}
|
||||
|
||||
private AuditDetailResponseVo.CustomItemResponseVo buildBaseVo(String field, String name) {
|
||||
AuditDetailResponseVo.CustomItemResponseVo vo = new AuditDetailResponseVo.CustomItemResponseVo();
|
||||
vo.setCustomField(field);
|
||||
vo.setCustomKey(name);
|
||||
vo.setCheckStatus("PASS"); // 默认通过,失败时在具体方法中修改
|
||||
return vo;
|
||||
return new RuleCheckResult(false, "配送区域不符合要求", 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package com.crrc.crrcmall.service.impl;
|
||||
|
||||
import com.crrc.crrcmall.dto.audit.AuditBatchRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.AuditContext;
|
||||
import com.crrc.crrcmall.dto.audit.AuditSearchRequestDto;
|
||||
import com.crrc.crrcmall.dto.audit.CustomItemRequestDto;
|
||||
import com.crrc.crrcmall.entity.AuditTask;
|
||||
import com.crrc.crrcmall.service.AuditRuleService;
|
||||
import com.crrc.crrcmall.service.AuditService;
|
||||
@@ -14,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -39,10 +37,7 @@ public class AuditServiceImpl implements AuditService {
|
||||
public AuditCheckResponseVo submitAuditCheck(AuditCheckRequestDto request) {
|
||||
String taskId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
// 1. 数据转换:将 customList 转换为结构化 Context
|
||||
AuditContext context = convertToContext(request);
|
||||
|
||||
// 2. 创建任务记录 (模拟 INSERT INTO audit_task)
|
||||
// 1. 创建任务记录
|
||||
AuditTask task = new AuditTask();
|
||||
task.setTaskId(taskId);
|
||||
task.setGoodsId(request.getGoodsId());
|
||||
@@ -51,21 +46,20 @@ public class AuditServiceImpl implements AuditService {
|
||||
task.setCreateTime(LocalDateTime.now());
|
||||
TASK_DB.put(taskId, task);
|
||||
|
||||
// 3. 异步执行审核
|
||||
// 2. 异步执行审核
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
log.info("开始异步审核任务: {}", taskId);
|
||||
// 调用规则引擎
|
||||
AuditDetailResponseVo result = auditRuleService.executeAllRules(context, request.getCustomList());
|
||||
result.setTaskId(taskId); // 绑定 TaskId
|
||||
log.info("开始异步审核任务: {}, Type: {}, AuditId: {}", taskId, request.getType(), request.getAuditId());
|
||||
|
||||
// 调用规则引擎 (内部会使用 MockDataGenerator 生成数据)
|
||||
AuditDetailResponseVo result = auditRuleService.executeAllRules(request);
|
||||
|
||||
// 保存结果
|
||||
RESULT_DB.put(taskId, result);
|
||||
|
||||
// 更新任务状态
|
||||
task.setStatus("COMPLETED");
|
||||
task.setCompleteTime(LocalDateTime.now());
|
||||
|
||||
// 保存结果 (模拟 INSERT INTO audit_result_detail)
|
||||
RESULT_DB.put(taskId, result);
|
||||
|
||||
// TODO: 触发 Callback 通知客户
|
||||
// httpClient.post(request.getCallbackUrl(), result);
|
||||
|
||||
@@ -75,7 +69,44 @@ public class AuditServiceImpl implements AuditService {
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 立即返回
|
||||
// 3. 立即返回
|
||||
AuditCheckResponseVo response = new AuditCheckResponseVo();
|
||||
response.setTaskId(taskId);
|
||||
response.setStatus("ACCEPTED");
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuditCheckResponseVo submitAuditCheckBatch(AuditBatchRequestDto request) {
|
||||
String taskId = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
AuditTask task = new AuditTask();
|
||||
task.setTaskId(taskId);
|
||||
task.setStatus("PROCESSING");
|
||||
task.setCreateTime(LocalDateTime.now());
|
||||
TASK_DB.put(taskId, task);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
log.info("开始批量异步审核任务: {}, Count: {}", taskId, request.getAuditInfo().size());
|
||||
for (AuditBatchRequestDto.AuditInfo info : request.getAuditInfo()) {
|
||||
AuditCheckRequestDto singleReq = new AuditCheckRequestDto();
|
||||
singleReq.setGoodsId(info.getGoodsId());
|
||||
singleReq.setAuditId(info.getAuditId());
|
||||
singleReq.setType(info.getType());
|
||||
singleReq.setCallbackUrl(request.getCallbackUrl());
|
||||
|
||||
AuditDetailResponseVo result = auditRuleService.executeAllRules(singleReq);
|
||||
RESULT_DB.put(taskId + "_" + info.getAuditId(), result);
|
||||
}
|
||||
task.setStatus("COMPLETED");
|
||||
task.setCompleteTime(LocalDateTime.now());
|
||||
} catch (Exception e) {
|
||||
log.error("批量审核任务执行失败: {}", taskId, e);
|
||||
task.setStatus("FAILED");
|
||||
}
|
||||
});
|
||||
|
||||
AuditCheckResponseVo response = new AuditCheckResponseVo();
|
||||
response.setTaskId(taskId);
|
||||
response.setStatus("ACCEPTED");
|
||||
@@ -85,50 +116,6 @@ public class AuditServiceImpl implements AuditService {
|
||||
@Override
|
||||
public AuditDetailResponseVo getAuditResult(AuditSearchRequestDto request) {
|
||||
// 从模拟 DB 中查询
|
||||
return RESULT_DB.getOrDefault(String.valueOf(request.getTaskId()), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:将 Key-Value 列表转换为结构化对象
|
||||
*/
|
||||
private AuditContext convertToContext(AuditCheckRequestDto request) {
|
||||
AuditContext context = new AuditContext();
|
||||
context.setGoodsId(request.getGoodsId());
|
||||
context.setCallbackUrl(request.getCallbackUrl());
|
||||
|
||||
if (request.getCustomList() != null) {
|
||||
for (CustomItemRequestDto item : request.getCustomList()) {
|
||||
String key = item.getCustomField();
|
||||
Object value = item.getCustomValue();
|
||||
|
||||
// 简单的映射逻辑 (实际可根据 customType 进一步细分)
|
||||
switch (key) {
|
||||
case "goodsName": context.setGoodsName(String.valueOf(value)); break;
|
||||
case "barcode": context.setBarcode(String.valueOf(value)); break;
|
||||
case "brandName": context.setBrandName(String.valueOf(value)); break;
|
||||
case "taxCode": context.setTaxCode(String.valueOf(value)); break;
|
||||
case "materialCode": context.setMaterialCode(String.valueOf(value)); break;
|
||||
case "catalogId":
|
||||
if (value instanceof Number) context.setCatalogId(((Number) value).longValue());
|
||||
break;
|
||||
case "imageUrls":
|
||||
// 假设传入的是逗号分隔的字符串或 List
|
||||
if (value instanceof List) context.setImageUrls((List<String>) value);
|
||||
else if (value instanceof String) context.setImageUrls(List.of((String) value));
|
||||
break;
|
||||
case "description": context.setDescription(String.valueOf(value)); break;
|
||||
case "marketPriceUrl": context.setMarketPriceUrl(String.valueOf(value)); break;
|
||||
case "jdPriceUrl": context.setJdPriceUrl(String.valueOf(value)); break;
|
||||
case "price":
|
||||
if (value instanceof Number) context.setPrice(((Number) value).doubleValue());
|
||||
break;
|
||||
case "shippingFee": context.setShippingFee(String.valueOf(value)); break;
|
||||
case "deliveryArea": context.setDeliveryArea(String.valueOf(value)); break;
|
||||
case "productType": context.setProductType(String.valueOf(value)); break;
|
||||
// TODO: 其他字段映射
|
||||
}
|
||||
}
|
||||
}
|
||||
return context;
|
||||
return RESULT_DB.getOrDefault(request.getTaskId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
package com.crrc.crrcmall.service.impl;
|
||||
|
||||
import com.crrc.crrcmall.common.mock.SearchMockDataGenerator;
|
||||
import com.crrc.crrcmall.dto.crrc.SearchGoodsRequestDto;
|
||||
import com.crrc.crrcmall.service.SearchService;
|
||||
import com.crrc.crrcmall.vo.crrc.GoodsSearchResultVo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 搜索服务实现类
|
||||
* 搜索服务实现类 (Demo 模式)
|
||||
* 目前通过 Mock 数据生成器返回搜索结果,方便演示 AI 搜索效果
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SearchServiceImpl implements SearchService {
|
||||
|
||||
@Autowired
|
||||
private SearchMockDataGenerator mockDataGenerator;
|
||||
|
||||
@Override
|
||||
public GoodsSearchResultVo searchByText(SearchGoodsRequestDto request) {
|
||||
// TODO: 1. 调用 AI 意图识别模块
|
||||
// TODO: 2. 调用 ES 混合检索
|
||||
// TODO: 3. 组装数据
|
||||
|
||||
// 目前返回空数据结构
|
||||
GoodsSearchResultVo result = new GoodsSearchResultVo();
|
||||
result.setGoodsList(new ArrayList<>());
|
||||
result.setBrandList(new ArrayList<>());
|
||||
result.setCatalogList(new ArrayList<>());
|
||||
|
||||
return result;
|
||||
log.info("执行 AI 文本搜索,关键词:{}", request.getGoodsName());
|
||||
// 调用 Mock 生成器返回演示数据
|
||||
return mockDataGenerator.generateResult(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoodsSearchResultVo searchByImage(SearchGoodsRequestDto request) {
|
||||
// TODO: 1. 图片向量化
|
||||
// TODO: 2. 调用 ES 混合检索
|
||||
// TODO: 3. 组装数据
|
||||
|
||||
return new GoodsSearchResultVo();
|
||||
return mockDataGenerator.generateResult(request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
package com.crrc.crrcmall.vo.ai;
|
||||
|
||||
/**
|
||||
* @Author:Jinyun.Wang
|
||||
* @Date:2026/11/2
|
||||
**/
|
||||
import com.crrc.crrcmall.common.enums.MatchTypeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审核详情响应 VO
|
||||
* 审核详情响应 VO (v2 - 嵌套结构)
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "审核详情响应")
|
||||
@@ -15,22 +15,32 @@ public class AuditDetailResponseVo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "任务 ID")
|
||||
@Schema(description = "审核类型: 1-新品审核 2-新品复审 3-调价审核")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "审核任务 ID")
|
||||
private String taskId;
|
||||
|
||||
@Schema(description = "商品 ID")
|
||||
private String goodsId;
|
||||
@Schema(description = "状态: 1 已完成 2 未检测")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "状态: SUCCESSED/WAITING")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "审核明细列表")
|
||||
private List<CustomItemResponseVo> customList;
|
||||
@Schema(description = "审核分组列表 (按 auditId 分组)")
|
||||
private List<AuditGroupVo> auditList;
|
||||
|
||||
@Data
|
||||
public static class CustomItemResponseVo implements Serializable {
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
public static class AuditGroupVo implements Serializable {
|
||||
@Schema(description = "商品 ID")
|
||||
private String goodsId;
|
||||
|
||||
@Schema(description = "审核 ID")
|
||||
private String auditId;
|
||||
|
||||
@Schema(description = "审核明细列表")
|
||||
private List<AuditDetailItemVo> auditDetailList;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AuditDetailItemVo implements Serializable {
|
||||
@Schema(description = "字段标识")
|
||||
private String customField;
|
||||
@Schema(description = "字段显示名")
|
||||
@@ -41,13 +51,13 @@ public class AuditDetailResponseVo implements Serializable {
|
||||
private Object customType;
|
||||
@Schema(description = "AI 反馈")
|
||||
private String aiIntent;
|
||||
@Schema(description = "AI 反馈类型")
|
||||
@Schema(description = "AI 反馈类型: 1-格式 2-逻辑 3-图片")
|
||||
private Integer aiIntentType;
|
||||
@Schema(description = "AI 匹配度")
|
||||
private Double aiRelevanceScore;
|
||||
@Schema(description = "规则类型")
|
||||
@Schema(description = "规则类型: 1-硬规则 2-AI 规则")
|
||||
private Integer checkType;
|
||||
@Schema(description = "规则类型")
|
||||
private String checkStatus;
|
||||
@Schema(description = "检测状态: 0 成功 1 失败")
|
||||
private Integer auditStatus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,4 +46,7 @@ public class GoodsSearchResultVo implements Serializable {
|
||||
|
||||
@Schema(description = "ai规则说明")
|
||||
private AiSearchInfoVo aiSearchInfo;
|
||||
|
||||
@Schema(description = "条数")
|
||||
private int total;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user