feat:增加部分审核规则验证

This commit is contained in:
王金云
2026-11-02 15:54:55 +08:00
parent dcba8b0359
commit 487663b6cd
23 changed files with 1113 additions and 1 deletions
@@ -0,0 +1,49 @@
package com.crrc.crrcmall.config;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 规则配置提供者
* 模拟从数据库读取配置项。后续只需修改方法体,注入 Mapper 即可无缝对接数据库。
*/
@Component
public class RuleConfigProvider {
/**
* 获取物料编码前缀配置
* @param category 商品类目类型 (INDUSTRIAL/OFFICE)
* @return 期望的前缀 (工业品:DM, 办公用品:DB)
*/
public String getMaterialCodePrefix(String category) {
// TODO: 替换为从数据库 audit_rule_config 表查询
if ("INDUSTRIAL".equalsIgnoreCase(category)) {
return "DM";
} else {
return "DB"; // 默认办公用品
}
}
/**
* 获取违禁词列表 (规则 1, 8, 9)
*/
public List<String> getForbiddenWords() {
// TODO: 替换为从数据库查询
return Arrays.asList("国产", "中车", "联系", "咨询", "详询客服销售", "电话", "微信号");
}
/**
* 获取运费规则 (规则 12)
*/
public String getRequiredShippingFee() {
return "全国包邮";
}
/**
* 获取配送区域规则 (规则 13)
*/
public String getRequiredDeliveryArea() {
return "不限配送区域";
}
}
@@ -0,0 +1,37 @@
package com.crrc.crrcmall.controller;
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
import com.crrc.crrcmall.dto.audit.AuditSearchRequestDto;
import com.crrc.crrcmall.service.AuditService;
import com.crrc.crrcmall.vo.audit.AuditCheckResponseVo;
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
/**
* AI 智能审核控制器
*/
@Tag(name = "AI 审核服务")
@RestController
@RequestMapping("/api/v1/audit")
public class AuditController {
private final AuditService auditService;
public AuditController(AuditService auditService) {
this.auditService = auditService;
}
@Operation(summary = "提交审核任务")
@PostMapping("/check")
public AuditCheckResponseVo checkAudit(@RequestBody AuditCheckRequestDto request) {
return auditService.submitAuditCheck(request);
}
@Operation(summary = "查询审核结果")
@GetMapping("/search")
public AuditDetailResponseVo searchAuditResult(@ModelAttribute AuditSearchRequestDto request) {
return auditService.getAuditResult(request);
}
}
@@ -0,0 +1,37 @@
package com.crrc.crrcmall.controller;
import com.crrc.crrcmall.dto.price.PriceResultRequestDto;
import com.crrc.crrcmall.service.PriceService;
import com.crrc.crrcmall.vo.price.PriceImportResponseVo;
import com.crrc.crrcmall.vo.price.PriceResultResponseVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 选品比价控制器
*/
@Tag(name = "选品比价服务")
@RestController
@RequestMapping("/api/v1/price")
public class PriceController {
private final PriceService priceService;
public PriceController(PriceService priceService) {
this.priceService = priceService;
}
@Operation(summary = "导入比价数据")
@PostMapping("/import")
public PriceImportResponseVo importPriceData(@RequestParam("file") MultipartFile file) {
return priceService.importPriceExcel(file);
}
@Operation(summary = "查询比价结果")
@GetMapping("/result")
public PriceResultResponseVo getPriceResult(@ModelAttribute PriceResultRequestDto request) {
return priceService.getPriceResult(request);
}
}
@@ -0,0 +1,26 @@
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;
/**
* AI 智能审核提交请求 DTO
*/
@Data
@Schema(description = "提交审核任务请求")
public class AuditCheckRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "商品 ID")
private String goodsId;
@Schema(description = "回调地址 (审核完成后通知客户)")
private String callbackUrl;
@Schema(description = "自定义审核项列表")
private List<CustomItemRequestDto> customList;
}
@@ -0,0 +1,65 @@
package com.crrc.crrcmall.dto.audit;
import lombok.Data;
import java.util.List;
/**
* 审核上下文 (内部转换对象)
* 用于将杂乱的 CustomItemRequestDto 列表转换为结构化数据,方便规则校验
*/
@Data
public class AuditContext {
private String goodsId;
private String callbackUrl;
// === 核心校验字段 (对应 13 项审核规则) ===
// 1. 商品名称
private String goodsName;
// 2. 商品条码
private String barcode;
// 3. 品牌项
private String brandName;
// 4. 税码
private String taxCode;
// 5. 物料编码
private String materialCode;
// 6. 商品目录 (类目)
private Long catalogId;
private String catalogName;
// 7. 商品图片 (URL 列表)
private List<String> imageUrls;
// 8. 电脑端详情描述
private String description;
// 9. 市场价格链接 (官网)
private String marketPriceUrl;
// 10. 京东价格链接
private String jdPriceUrl;
// 11. 价格 [单价(含税)]
private Double price;
// 12. 运费
private String shippingFee;
// 13. 配送区域
private String deliveryArea;
// === 扩展字段 (用于辅助校验) ===
// 商品类型标识 (用于区分工业品/办公用品)
private String productType;
// TODO: 入围价格 (待明确字段来源)
private Double targetPrice;
}
@@ -0,0 +1,19 @@
package com.crrc.crrcmall.dto.audit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 审核结果查询请求 DTO
*/
@Data
@Schema(description = "审核结果查询请求")
public class AuditSearchRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "审核任务 ID")
private Long taskId;
}
@@ -0,0 +1,31 @@
package com.crrc.crrcmall.dto.audit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 审核明细项 DTO
*/
@Data
@Schema(description = "审核明细项")
public class CustomItemRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "类型: JD/CRRC")
private String type;
@Schema(description = "字段标识 (如 goodsName, goodsPrice)")
private String customField;
@Schema(description = "字段显示名")
private String customKey;
@Schema(description = "本次申报值")
private Object customValue;
@Schema(description = "所属分组 (如 price, saleInfo)")
private Object customType;
}
@@ -0,0 +1,19 @@
package com.crrc.crrcmall.dto.price;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 比价结果查询请求 DTO
*/
@Data
@Schema(description = "比价结果查询请求")
public class PriceResultRequestDto implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "任务 ID")
private Long taskId;
}
@@ -0,0 +1,54 @@
package com.crrc.crrcmall.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 审核结果详情实体 (模拟数据库表:audit_result_detail)
* 用于记录 13 项规则中每一项的具体审核结果和 AI 反馈
*/
@Data
@Schema(description = "审核结果详情")
public class AuditResultDetail implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键 ID")
private Long id;
@Schema(description = "关联的任务 ID")
private String taskId;
@Schema(description = "关联的字段标识 (如 goodsName, goodsPrice)")
private String customField;
@Schema(description = "规则编码")
private String ruleCode;
@Schema(description = "规则名称")
private String ruleName;
@Schema(description = "审核结果: PASS-通过, FAIL-不通过, WARN-警告")
private String checkStatus;
@Schema(description = "AI 反馈/判定依据")
private String aiIntent;
@Schema(description = "AI 匹配度/置信度 (0.0 - 1.0)")
private Double aiRelevanceScore;
@Schema(description = "AI 反馈类型: 1-格式校验 2-语义/逻辑校验 3-图片内容校验")
private Integer aiIntentType;
@Schema(description = "规则类型: 1-硬规则 2-AI 规则 3-爬虫/外部校验")
private Integer checkType;
@Schema(description = "错误/警告详细信息")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,42 @@
package com.crrc.crrcmall.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 审核规则配置实体 (模拟数据库表:audit_rule_config)
* 用于统一管理 13 项审核规则的配置项(如物料前缀、违禁词、阈值等)
*/
@Data
@Schema(description = "审核规则配置")
public class AuditRuleConfig implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键 ID")
private Long id;
@Schema(description = "规则编码 (唯一标识)")
private String ruleCode;
@Schema(description = "规则名称")
private String ruleName;
@Schema(description = "配置内容 (JSON 格式或字符串)")
private String configValue;
@Schema(description = "是否启用: 1-启用 0-禁用")
private Integer isActive;
@Schema(description = "规则描述")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}
@@ -0,0 +1,42 @@
package com.crrc.crrcmall.entity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 审核任务实体 (模拟数据库表:audit_task)
* 用于记录每一次审核请求的元数据和状态
*/
@Data
@Schema(description = "审核任务")
public class AuditTask implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "主键 ID")
private Long id;
@Schema(description = "任务 ID (UUID)")
private String taskId;
@Schema(description = "商品 ID")
private String goodsId;
@Schema(description = "回调地址")
private String callbackUrl;
@Schema(description = "审核状态: PENDING-待处理, PROCESSING-审核中, COMPLETED-已完成, FAILED-失败")
private String status;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "完成时间")
private LocalDateTime completeTime;
@Schema(description = "备注")
private String remark;
}
@@ -0,0 +1,22 @@
package com.crrc.crrcmall.service;
import com.crrc.crrcmall.dto.audit.AuditContext;
import com.crrc.crrcmall.dto.audit.CustomItemRequestDto;
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
import java.util.List;
/**
* 审核规则引擎服务
* 负责执行 13 项具体的审核规则校验
*/
public interface AuditRuleService {
/**
* 执行全套审核规则 (13 项)
* @param context 审核上下文数据
* @param rawCustomList 原始请求列表 (用于回填)
* @return 包含所有校验项结果的 VO
*/
AuditDetailResponseVo executeAllRules(AuditContext context, List<CustomItemRequestDto> rawCustomList);
}
@@ -0,0 +1,22 @@
package com.crrc.crrcmall.service;
import com.crrc.crrcmall.dto.audit.AuditCheckRequestDto;
import com.crrc.crrcmall.dto.audit.AuditSearchRequestDto;
import com.crrc.crrcmall.vo.audit.AuditCheckResponseVo;
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
/**
* 审核服务接口
*/
public interface AuditService {
/**
* 提交审核任务
*/
AuditCheckResponseVo submitAuditCheck(AuditCheckRequestDto request);
/**
* 查询审核结果
*/
AuditDetailResponseVo getAuditResult(AuditSearchRequestDto request);
}
@@ -0,0 +1,22 @@
package com.crrc.crrcmall.service;
import com.crrc.crrcmall.dto.price.PriceResultRequestDto;
import com.crrc.crrcmall.vo.price.PriceImportResponseVo;
import com.crrc.crrcmall.vo.price.PriceResultResponseVo;
import org.springframework.web.multipart.MultipartFile;
/**
* 比价服务接口
*/
public interface PriceService {
/**
* 导入比价 Excel
*/
PriceImportResponseVo importPriceExcel(MultipartFile file);
/**
* 查询比价结果
*/
PriceResultResponseVo getPriceResult(PriceResultRequestDto request);
}
@@ -9,9 +9,16 @@ import com.crrc.crrcmall.vo.crrc.GoodsSearchResultVo;
public interface SearchService {
/**
* 文本语义搜索
* 文本语义/向量搜索
* @param request 搜索请求参数
* @return 搜索结果
*/
GoodsSearchResultVo searchByText(SearchGoodsRequestDto request);
/**
* 图片向量搜索
* @param request 搜索请求参数
* @return 搜索结果
*/
GoodsSearchResultVo searchByImage(SearchGoodsRequestDto request);
}
@@ -0,0 +1,293 @@
package com.crrc.crrcmall.service.impl;
import com.crrc.crrcmall.config.RuleConfigProvider;
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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* 审核规则引擎实现类
*/
@Slf4j
@Service
public class AuditRuleServiceImpl implements AuditRuleService {
@Autowired
private RuleConfigProvider ruleConfigProvider;
@Override
public AuditDetailResponseVo executeAllRules(AuditContext context, List<CustomItemRequestDto> rawCustomList) {
AuditDetailResponseVo response = new AuditDetailResponseVo();
// TODO: 实际应生成 UUID
response.setTaskId("TASK-MOCK-" + System.currentTimeMillis());
response.setGoodsId(context.getGoodsId());
// 初始状态:假设全部通过,后续根据校验结果更新
response.setStatus("SUCCESSED");
List<AuditDetailResponseVo.CustomItemResponseVo> results = new ArrayList<>();
// 1. 校验商品名称 (规则 1) - AI
results.add(checkName(context));
// 2. 校验商品条码 (规则 2) - 硬规则
results.add(checkBarcode(context));
// 3. 校验品牌项 (规则 3) - AI
results.add(checkBrand(context));
// 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");
}
return response;
}
/**
* 规则 2:商品条码校验
* 要求:条码真实有效,与商品信息一致;非必填项可为空。禁止:填写条码以外的数字、汉字、字母等内容。
*/
private AuditDetailResponseVo.CustomItemResponseVo checkBarcode(AuditContext context) {
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("barcode", "商品条码");
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("条码为空,跳过校验");
}
return res;
}
/**
* 规则 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("税码已填写");
}
return res;
}
/**
* 规则 5:物料编码校验
* 要求:工业品以 DM 开头,办公用品以 DB 开头。
*/
private AuditDetailResponseVo.CustomItemResponseVo checkMaterialCode(AuditContext context) {
AuditDetailResponseVo.CustomItemResponseVo res = buildBaseVo("materialCode", "物料编码");
String code = context.getMaterialCode();
if (!StringUtils.hasText(code)) {
res.setCheckStatus("FAIL");
res.setAiIntent("物料编码不能为空");
return res;
}
// 获取当前商品类别的期望前缀 (从配置读取)
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 + "] 开头");
}
return res;
}
/**
* 规则 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 + "]");
}
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;
}
}
@@ -0,0 +1,134 @@
package com.crrc.crrcmall.service.impl;
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;
import com.crrc.crrcmall.vo.audit.AuditCheckResponseVo;
import com.crrc.crrcmall.vo.audit.AuditDetailResponseVo;
import lombok.extern.slf4j.Slf4j;
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;
import java.util.concurrent.ConcurrentHashMap;
/**
* 审核服务实现 (异步 + 内存模拟 DB)
*/
@Slf4j
@Service
public class AuditServiceImpl implements AuditService {
@Autowired
private AuditRuleService auditRuleService;
// 模拟数据库存储 (Task)
private static final Map<String, AuditTask> TASK_DB = new ConcurrentHashMap<>();
// 模拟数据库存储 (Result)
private static final Map<String, AuditDetailResponseVo> RESULT_DB = new ConcurrentHashMap<>();
@Override
public AuditCheckResponseVo submitAuditCheck(AuditCheckRequestDto request) {
String taskId = UUID.randomUUID().toString().replace("-", "");
// 1. 数据转换:将 customList 转换为结构化 Context
AuditContext context = convertToContext(request);
// 2. 创建任务记录 (模拟 INSERT INTO audit_task)
AuditTask task = new AuditTask();
task.setTaskId(taskId);
task.setGoodsId(request.getGoodsId());
task.setCallbackUrl(request.getCallbackUrl());
task.setStatus("PROCESSING");
task.setCreateTime(LocalDateTime.now());
TASK_DB.put(taskId, task);
// 3. 异步执行审核
CompletableFuture.runAsync(() -> {
try {
log.info("开始异步审核任务: {}", taskId);
// 调用规则引擎
AuditDetailResponseVo result = auditRuleService.executeAllRules(context, request.getCustomList());
result.setTaskId(taskId); // 绑定 TaskId
// 更新任务状态
task.setStatus("COMPLETED");
task.setCompleteTime(LocalDateTime.now());
// 保存结果 (模拟 INSERT INTO audit_result_detail)
RESULT_DB.put(taskId, result);
// TODO: 触发 Callback 通知客户
// httpClient.post(request.getCallbackUrl(), result);
} catch (Exception e) {
log.error("审核任务执行失败: {}", taskId, e);
task.setStatus("FAILED");
}
});
// 4. 立即返回
AuditCheckResponseVo response = new AuditCheckResponseVo();
response.setTaskId(taskId);
response.setStatus("ACCEPTED");
return response;
}
@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;
}
}
@@ -0,0 +1,40 @@
package com.crrc.crrcmall.service.impl;
import com.crrc.crrcmall.dto.price.PriceResultRequestDto;
import com.crrc.crrcmall.service.PriceService;
import com.crrc.crrcmall.vo.price.PriceImportResponseVo;
import com.crrc.crrcmall.vo.price.PriceResultResponseVo;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
/**
* 比价服务实现
*/
@Service
public class PriceServiceImpl implements PriceService {
@Override
public PriceImportResponseVo importPriceExcel(MultipartFile file) {
// TODO: 1. 解析 Excel 文件
// TODO: 2. 生成 TaskID 并保存原始数据
// TODO: 3. 触发比价分析流程
PriceImportResponseVo res = new PriceImportResponseVo();
res.setTaskId(99887766L);
res.setStatus("SUCCESSED");
return res;
}
@Override
public PriceResultResponseVo getPriceResult(PriceResultRequestDto request) {
// TODO: 1. 根据 TaskID 查询比价结果
// TODO: 2. 返回 AI 核价建议
PriceResultResponseVo res = new PriceResultResponseVo();
res.setSkuCode("SKU-001");
res.setProductName("测试商品");
res.setAiMarketPrice(100.0);
res.setAiSuggestion("价格合理");
return res;
}
}
@@ -3,6 +3,7 @@ package com.crrc.crrcmall.service.impl;
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.stereotype.Service;
import java.util.ArrayList;
@@ -10,6 +11,7 @@ import java.util.ArrayList;
/**
* 搜索服务实现类
*/
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
@@ -27,4 +29,13 @@ public class SearchServiceImpl implements SearchService {
return result;
}
@Override
public GoodsSearchResultVo searchByImage(SearchGoodsRequestDto request) {
// TODO: 1. 图片向量化
// TODO: 2. 调用 ES 混合检索
// TODO: 3. 组装数据
return new GoodsSearchResultVo();
}
}
@@ -0,0 +1,22 @@
package com.crrc.crrcmall.vo.audit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 提交审核响应 VO
*/
@Data
@Schema(description = "提交审核响应")
public class AuditCheckResponseVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "任务 ID")
private String taskId;
@Schema(description = "状态")
private String status;
}
@@ -0,0 +1,53 @@
package com.crrc.crrcmall.vo.audit;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 审核详情响应 VO
*/
@Data
@Schema(description = "审核详情响应")
public class AuditDetailResponseVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "任务 ID")
private String taskId;
@Schema(description = "商品 ID")
private String goodsId;
@Schema(description = "状态: SUCCESSED/WAITING")
private String status;
@Schema(description = "审核明细列表")
private List<CustomItemResponseVo> customList;
@Data
public static class CustomItemResponseVo implements Serializable {
@Schema(description = "类型")
private String type;
@Schema(description = "字段标识")
private String customField;
@Schema(description = "字段显示名")
private String customKey;
@Schema(description = "申报值")
private Object customValue;
@Schema(description = "所属分组")
private Object customType;
@Schema(description = "AI 反馈")
private String aiIntent;
@Schema(description = "AI 反馈类型")
private Integer aiIntentType;
@Schema(description = "AI 匹配度")
private Double aiRelevanceScore;
@Schema(description = "规则类型")
private Integer checkType;
@Schema(description = "规则类型")
private String checkStatus;
}
}
@@ -0,0 +1,22 @@
package com.crrc.crrcmall.vo.price;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 导入比价响应 VO
*/
@Data
@Schema(description = "导入比价响应")
public class PriceImportResponseVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "任务 ID")
private Long taskId;
@Schema(description = "状态")
private String status;
}
@@ -0,0 +1,43 @@
package com.crrc.crrcmall.vo.price;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 比价结果响应 VO
*/
@Data
@Schema(description = "比价结果响应")
public class PriceResultResponseVo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "商品编码")
private String skuCode;
@Schema(description = "商品名称")
private String productName;
@Schema(description = "商品封面地址")
private String productImageUrl;
@Schema(description = "商品公司")
private String productCompany;
@Schema(description = "供应商申报价")
private Double supplierPrice;
@Schema(description = "市场均价")
private Double aiMarketPrice;
@Schema(description = "偏离度")
private Double priceDiffRate;
@Schema(description = "AI 核价建议")
private String aiSuggestion;
@Schema(description = "风险标签")
private String riskTag;
}