diff --git a/src/main/java/com/rj/controller/LbPurchaseApplyController.java b/src/main/java/com/rj/controller/LbPurchaseApplyController.java index 215b920..aeed859 100644 --- a/src/main/java/com/rj/controller/LbPurchaseApplyController.java +++ b/src/main/java/com/rj/controller/LbPurchaseApplyController.java @@ -4,7 +4,11 @@ import com.rj.entity.LbPurchaseApply; import com.rj.service.ILbPurchaseApplyService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; @@ -21,6 +25,30 @@ public class LbPurchaseApplyController { @Autowired private ILbPurchaseApplyService lbPurchaseApplyService; + @Data + @Schema(description = "大模型解析订货信息请求体") + public static class ParseOrderInfoRequest { + @Schema(description = "订货信息原文", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "订货信息不能为空") + private String orderInfo; + + @Schema(description = "当前登录人租户ID", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "tenantId不能为空") + private String tenantId; + } + + @PostMapping("/parseFromOrderInfo") + @Operation(summary = "大模型解析订货信息为进货申请实体", description = "根据自然语言订货信息调用大模型抽取字段,返回 LbPurchaseApply(不落库)") + public ResponseEntity> parseFromOrderInfo( + @Parameter(description = "订货信息与租户ID", required = true) @Valid @RequestBody ParseOrderInfoRequest body) { + Map result = lbPurchaseApplyService.parseFromOrderInfoByLlm(body.getOrderInfo(), body.getTenantId()); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } + @PostMapping("/add") @Operation(summary = "新增") public ResponseEntity> add( diff --git a/src/main/java/com/rj/service/ILbPurchaseApplyService.java b/src/main/java/com/rj/service/ILbPurchaseApplyService.java index 3b26c7f..5ccb1bd 100644 --- a/src/main/java/com/rj/service/ILbPurchaseApplyService.java +++ b/src/main/java/com/rj/service/ILbPurchaseApplyService.java @@ -24,4 +24,12 @@ public interface ILbPurchaseApplyService extends IService { LocalDate applyDate); Map listNeedPrivilegeRecommenders(LocalDate startDate, LocalDate endDate); + + /** + * 根据订货自然语言描述调用大模型解析为 {@link LbPurchaseApply}(不落库)。 + * + * @param orderInfo 订货信息原文 + * @param tenantId 当前登录人租户 ID(回填到结果实体,覆盖模型输出) + */ + Map parseFromOrderInfoByLlm(String orderInfo, String tenantId); } diff --git a/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java b/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java index 72afe6b..d417257 100644 --- a/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbPurchaseApplyServiceImpl.java @@ -1,17 +1,28 @@ package com.rj.service.impl; +import com.alibaba.dashscope.aigc.generation.Generation; +import com.alibaba.dashscope.aigc.generation.GenerationParam; +import com.alibaba.dashscope.aigc.generation.GenerationResult; +import com.alibaba.dashscope.common.Message; +import com.alibaba.dashscope.common.Role; +import com.alibaba.dashscope.exception.InputRequiredException; +import com.alibaba.dashscope.exception.NoApiKeyException; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.fasterxml.jackson.databind.ObjectMapper; import com.rj.entity.LbDailyUserTradeReport; import com.rj.entity.LbPurchaseApply; import com.rj.mapper.LbDailyUserTradeReportMapper; import com.rj.mapper.LbPurchaseApplyMapper; import com.rj.service.ILbPurchaseApplyService; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.time.LocalDate; import java.time.LocalDateTime; @@ -20,6 +31,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +@Slf4j @Service public class LbPurchaseApplyServiceImpl extends ServiceImpl @@ -28,6 +40,120 @@ public class LbPurchaseApplyServiceImpl @Autowired private LbDailyUserTradeReportMapper lbDailyUserTradeReportMapper; + @Autowired + private ObjectMapper objectMapper; + + @Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}") + private String dashScopeChatModel; + + private static final String PARSE_SYSTEM_PROMPT = """ + 你是信息抽取助手。用户会提供一段「进货申请」相关的自然语言订货信息。 + 请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。 + 字段均为可选;无法从原文推断的字段请省略或设为 null。 + 字段名与含义(JSON 键名必须完全一致): + applyUser 申请人姓名; + applyPhone 申请人电话; + tryDate 试用日期(原文怎么说就怎么填字符串,如 yyyy-MM-dd 或中文描述); + age 年龄(整数); + workExperience 从业经历; + colleagueName 同事姓名; + colleaguePhone 同事电话; + teamLeader 团队长; + applyDate 申请日期,格式 yyyy-MM-dd,若原文无明确日期可省略。 + 不要输出 id、tenantId、createTime、updateTime。 + """; + + @Override + public Map parseFromOrderInfoByLlm(String orderInfo, String tenantId) { + Map result = new HashMap<>(); + try { + if (orderInfo == null || orderInfo.trim().isEmpty()) { + result.put("success", false); + result.put("message", "订货信息不能为空"); + return result; + } + if (tenantId == null || tenantId.trim().isEmpty()) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + + String apiKey = System.getenv("DASHSCOPE_API_KEY"); + if (apiKey == null || apiKey.isEmpty()) { + result.put("success", false); + result.put("message", "未配置环境变量 DASHSCOPE_API_KEY,无法调用大模型"); + return result; + } + + Generation gen = new Generation(); + Message systemMsg = Message.builder() + .role(Role.SYSTEM.getValue()) + .content(PARSE_SYSTEM_PROMPT) + .build(); + Message userMsg = Message.builder() + .role(Role.USER.getValue()) + .content("订货信息如下:\n" + orderInfo.trim()) + .build(); + + GenerationParam param = GenerationParam.builder() + .apiKey(apiKey) + .model(dashScopeChatModel) + .messages(Arrays.asList(systemMsg, userMsg)) + .resultFormat(GenerationParam.ResultFormat.MESSAGE) + .build(); + + GenerationResult call = gen.call(param); + String raw = call.getOutput().getChoices().get(0).getMessage().getContent(); + if (raw == null || raw.trim().isEmpty()) { + result.put("success", false); + result.put("message", "大模型返回内容为空"); + return result; + } + + String json = normalizeJson(raw); + LbPurchaseApply entity = objectMapper.readValue(json, LbPurchaseApply.class); + entity.setTenantId(tenantId.trim()); + entity.setId(null); + entity.setCreateTime(null); + entity.setUpdateTime(null); + + result.put("success", true); + result.put("message", "解析成功"); + result.put("data", entity); + return result; + } catch (NoApiKeyException e) { + log.error("DashScope API Key 异常", e); + result.put("success", false); + result.put("message", "API密钥异常:" + e.getMessage()); + return result; + } catch (InputRequiredException e) { + log.error("DashScope 入参异常", e); + result.put("success", false); + result.put("message", "调用大模型入参错误:" + e.getMessage()); + return result; + } catch (Exception e) { + log.error("订货信息大模型解析失败", e); + result.put("success", false); + result.put("message", "解析失败:" + e.getMessage()); + return result; + } + } + + private static String normalizeJson(String content) { + if (content == null) { + return ""; + } + String trimmed = content.trim(); + if (trimmed.startsWith("```")) { + int firstLineBreak = trimmed.indexOf('\n'); + int lastFence = trimmed.lastIndexOf("```"); + if (firstLineBreak >= 0 && lastFence > firstLineBreak) { + trimmed = trimmed.substring(firstLineBreak + 1, lastFence).trim(); + } + } + return trimmed; + } + @Override public Map add(LbPurchaseApply entity) { Map result = new HashMap<>();