Files
smartDriveEE/src/main/java/com/rj/service/impl/LbDeductionAmountServiceImpl.java
2026-05-21 22:57:14 +08:00

468 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.LbDeductionAmount;
import com.rj.mapper.LbDeductionAmountMapper;
import com.rj.service.ILbDeductionAmountService;
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.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
@Service
public class LbDeductionAmountServiceImpl
extends ServiceImpl<LbDeductionAmountMapper, LbDeductionAmount>
implements ILbDeductionAmountService {
@Autowired
private ObjectMapper objectMapper;
@Value("${langchain4j.open-ai.chat-model.model-name:qwen-plus}")
private String dashScopeChatModel;
private static final DateTimeFormatter DATE_TIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** 常见正文首行「M月d日」次行「姓名 手机号」,含「申请…抵扣…金额」与「已提现金额」。 */
private static final Pattern MONTH_DAY_PATTERN = Pattern.compile("(\\d{1,2})月(\\d{1,2})日");
private static final Pattern MOBILE_PHONE_PATTERN = Pattern.compile("1[3-9]\\d{9}");
/** 优先匹配「申请…抵扣…金额数字」,避免误用「已提现金额」。 */
private static final Pattern DIKOU_AMT_PATTERN =
Pattern.compile("申请[^\\n]*?抵扣[^\\n]*?金额\\s*(\\d+(?:\\.\\d+)?)", Pattern.CASE_INSENSITIVE);
private static final String PARSE_SYSTEM_PROMPT = """
你是信息抽取助手。用户会提供「抵扣金记录」自然语言,常见为多行固定格式,例如:
5月21日
刘兰兰 13261700877
申请FXJ抵扣SJF金额2678
已提现金额2678
解析规则:
1. 首行或文中「M月d日」如 5月21日仅作业务日期参考不要写入 JSON服务端会从原文补 createTime
2. 「姓名 + 空格 + 11位手机号」同一行userName 为姓名userPhone 为手机号。
3. dikouAmt 取含「申请」且含「抵扣」且含「金额」那一行中的数字(如上例 2678不要用「已提现金额」作为 dikouAmt。
4. FXJ、SJF 等字母为业务代号,忽略即可。
5. 金额只输出数字,不要带「元」等单位。
请只输出一个 JSON 对象,不要 markdown 代码块,不要解释性文字。
字段名必须完全一致userName、userPhone、dikouAmt。
createTime 若填写,格式 yyyy-MM-dd HH:mm:ss服务端仅将非当前年的年份校正为系统当前年月日不变。
不要输出 id、tenantId、originalText、updateTime。
""";
@Override
public Map<String, Object> parseFromTextByLlmAndSave(String tenantId, String text) {
Map<String, Object> result = new HashMap<>();
try {
if (text == null || text.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;
}
String trimmedText = text.trim();
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" + trimmedText)
.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);
LbDeductionAmount entity = objectMapper.readValue(json, LbDeductionAmount.class);
alignCreateTimeYearToCurrent(entity);
entity.setTenantId(tenantId.trim());
entity.setOriginalText(trimmedText);
entity.setId(null);
entity.setUpdateTime(null);
enrichFromDeductionText(entity, trimmedText);
log.debug(
"parseFromTextByLlmAndSaveuserName={} userPhone={} dikouAmt={} createTime={}",
entity.getUserName(),
entity.getUserPhone(),
entity.getDikouAmt(),
entity.getCreateTime());
return add(entity);
} 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;
}
}
/**
* 大模型若返回 createTime 且年份不是当前年,仅将年份改为系统当前年,月日及时分秒不变。
*/
private void alignCreateTimeYearToCurrent(LbDeductionAmount entity) {
if (entity == null || entity.getCreateTime() == null) {
return;
}
int currentYear = LocalDate.now().getYear();
LocalDate parsedDate = entity.getCreateTime().toLocalDate();
if (parsedDate.getYear() == currentYear) {
return;
}
LocalDate adjustedDate = parsedDate.withYear(currentYear);
LocalDateTime adjusted = LocalDateTime.of(adjustedDate, entity.getCreateTime().toLocalTime());
log.info("大模型返回年份 {} 与系统年份 {} 不一致createTime 已调整为 {}", parsedDate.getYear(), currentYear, adjusted);
entity.setCreateTime(adjusted);
}
/**
* 按业务常见多行格式补全 LLM 可能漏填的字段(日期、姓名电话、抵扣金额)。
*/
private void enrichFromDeductionText(LbDeductionAmount entity, String text) {
if (entity == null || text == null || text.isBlank()) {
return;
}
fillCreateTimeFromText(entity, text);
// 含「申请…抵扣…金额」时以该行为准,避免模型误用「已提现金额」
fillDikouAmtFromText(entity, text);
if (entity.getUserPhone() == null || entity.getUserPhone().isBlank()) {
fillPhoneFromText(entity, text);
}
if (entity.getUserName() == null || entity.getUserName().isBlank()) {
fillUserNameFromText(entity, text);
}
}
private void fillCreateTimeFromText(LbDeductionAmount entity, String text) {
if (entity.getCreateTime() != null) {
return;
}
Matcher matcher = MONTH_DAY_PATTERN.matcher(text);
if (!matcher.find()) {
return;
}
int month = Integer.parseInt(matcher.group(1));
int day = Integer.parseInt(matcher.group(2));
LocalDate date = LocalDate.of(LocalDate.now().getYear(), month, day);
entity.setCreateTime(date.atStartOfDay());
}
private void fillDikouAmtFromText(LbDeductionAmount entity, String text) {
Matcher matcher = DIKOU_AMT_PATTERN.matcher(text);
if (matcher.find()) {
entity.setDikouAmt(new BigDecimal(matcher.group(1)));
return;
}
if (entity.getDikouAmt() == null || entity.getDikouAmt().signum() == 0) {
Matcher fallback = Pattern.compile("金额\\s*(\\d+(?:\\.\\d+)?)").matcher(text);
if (fallback.find()) {
entity.setDikouAmt(new BigDecimal(fallback.group(1)));
}
}
}
private void fillPhoneFromText(LbDeductionAmount entity, String text) {
Matcher matcher = MOBILE_PHONE_PATTERN.matcher(text);
if (matcher.find()) {
entity.setUserPhone(matcher.group());
}
}
private void fillUserNameFromText(LbDeductionAmount entity, String text) {
if (entity.getUserPhone() == null || entity.getUserPhone().isBlank()) {
fillPhoneFromText(entity, text);
}
String phone = entity.getUserPhone();
if (phone == null || phone.isBlank()) {
return;
}
for (String line : text.split("\\R")) {
String trimmedLine = line.trim();
if (!trimmedLine.contains(phone)) {
continue;
}
String namePart = trimmedLine.replace(phone, "").trim();
if (!namePart.isEmpty()) {
entity.setUserName(namePart);
return;
}
}
}
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<String, Object> add(LbDeductionAmount entity) {
Map<String, Object> result = new HashMap<>();
try {
if (entity.getTenantId() == null || entity.getTenantId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "tenantId不能为空");
return result;
}
entity.setTenantId(entity.getTenantId().trim());
if (entity.getUserPhone() != null) {
entity.setUserPhone(entity.getUserPhone().trim());
}
if (entity.getUserName() != null) {
entity.setUserName(entity.getUserName().trim());
}
if (entity.getDikouAmt() == null) {
entity.setDikouAmt(BigDecimal.ZERO);
}
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
entity.setId(UUID.randomUUID().toString());
}
LocalDateTime now = LocalDateTime.now();
if (entity.getCreateTime() == null) {
entity.setCreateTime(now);
}
entity.setUpdateTime(now);
boolean ok = this.save(entity);
result.put("success", ok);
result.put("message", ok ? "新增成功" : "新增失败");
if (ok) {
result.put("data", entity);
}
return result;
} catch (Exception e) {
log.error("抵扣金记录新增异常", e);
result.put("success", false);
result.put("message", "新增异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> update(LbDeductionAmount entity) {
Map<String, Object> result = new HashMap<>();
try {
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
result.put("success", false);
result.put("message", "id不能为空");
return result;
}
if (entity.getTenantId() != null) {
entity.setTenantId(entity.getTenantId().trim());
}
if (entity.getUserPhone() != null) {
entity.setUserPhone(entity.getUserPhone().trim());
}
if (entity.getUserName() != null) {
entity.setUserName(entity.getUserName().trim());
}
entity.setUpdateTime(LocalDateTime.now());
boolean ok = this.updateById(entity);
result.put("success", ok);
result.put("message", ok ? "编辑成功" : "编辑失败");
if (ok) {
result.put("data", this.getById(entity.getId()));
}
return result;
} catch (Exception e) {
log.error("抵扣金记录编辑异常", e);
result.put("success", false);
result.put("message", "编辑异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> deleteById(String id) {
Map<String, Object> result = new HashMap<>();
try {
if (id == null || id.trim().isEmpty()) {
result.put("success", false);
result.put("message", "id不能为空");
return result;
}
boolean ok = this.removeById(id.trim());
result.put("success", ok);
result.put("message", ok ? "删除成功" : "删除失败");
return result;
} catch (Exception e) {
log.error("抵扣金记录删除异常", e);
result.put("success", false);
result.put("message", "删除异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, Object> pageQuery(Integer current,
Integer size,
String tenantId,
String userName,
String userPhone,
String createTimeStart,
String createTimeEnd) {
Map<String, Object> result = new HashMap<>();
try {
if (current == null || current < 1) {
current = 1;
}
if (size == null || size < 1) {
size = 10;
}
LambdaQueryWrapper<LbDeductionAmount> queryWrapper = new LambdaQueryWrapper<>();
if (tenantId != null && !tenantId.trim().isEmpty()) {
queryWrapper.eq(LbDeductionAmount::getTenantId, tenantId.trim());
}
if (userName != null && !userName.trim().isEmpty()) {
queryWrapper.like(LbDeductionAmount::getUserName, userName.trim());
}
if (userPhone != null && !userPhone.trim().isEmpty()) {
queryWrapper.like(LbDeductionAmount::getUserPhone, userPhone.trim());
}
LocalDateTime start = parseDateTime(createTimeStart);
if (createTimeStart != null && !createTimeStart.trim().isEmpty() && start == null) {
result.put("success", false);
result.put("message", "createTimeStart 格式错误,请使用 yyyy-MM-dd HH:mm:ss");
return result;
}
LocalDateTime end = parseDateTime(createTimeEnd);
if (createTimeEnd != null && !createTimeEnd.trim().isEmpty() && end == null) {
result.put("success", false);
result.put("message", "createTimeEnd 格式错误,请使用 yyyy-MM-dd HH:mm:ss");
return result;
}
if (start != null) {
queryWrapper.ge(LbDeductionAmount::getCreateTime, start);
}
if (end != null) {
queryWrapper.le(LbDeductionAmount::getCreateTime, end);
}
queryWrapper.orderByDesc(LbDeductionAmount::getUpdateTime)
.orderByDesc(LbDeductionAmount::getCreateTime);
Page<LbDeductionAmount> page = this.page(new Page<>(current, size), queryWrapper);
result.put("success", true);
result.put("message", "查询成功");
result.put("data", page.getRecords());
result.put("total", page.getTotal());
result.put("current", page.getCurrent());
result.put("size", page.getSize());
result.put("pages", page.getPages());
return result;
} catch (Exception e) {
log.error("抵扣金记录查询异常", e);
result.put("success", false);
result.put("message", "查询异常:" + e.getMessage());
return result;
}
}
@Override
public Map<String, BigDecimal> sumDikouAmtByUserNameForReportDate(String tenantId, LocalDate reportDate) {
Map<String, BigDecimal> byUserName = new HashMap<>();
if (tenantId == null || tenantId.trim().isEmpty() || reportDate == null) {
return byUserName;
}
LocalDateTime start = reportDate.atStartOfDay();
LocalDateTime end = reportDate.plusDays(1).atStartOfDay();
LambdaQueryWrapper<LbDeductionAmount> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(LbDeductionAmount::getTenantId, tenantId.trim())
.ge(LbDeductionAmount::getCreateTime, start)
.lt(LbDeductionAmount::getCreateTime, end);
List<LbDeductionAmount> records = list(queryWrapper);
for (LbDeductionAmount record : records) {
if (record.getUserName() == null || record.getUserName().trim().isEmpty()) {
continue;
}
String key = record.getUserName().trim();
BigDecimal amt = record.getDikouAmt() == null ? BigDecimal.ZERO : record.getDikouAmt();
byUserName.merge(key, amt, BigDecimal::add);
}
return byUserName;
}
private static LocalDateTime parseDateTime(String text) {
if (text == null || text.trim().isEmpty()) {
return null;
}
try {
return LocalDateTime.parse(text.trim(), DATE_TIME_FORMATTER);
} catch (Exception e) {
return null;
}
}
}