2026-01-28 11:19:23 +08:00
|
|
|
|
using Service.AI;
|
|
|
|
|
|
using Service.EmailServices.EmailParse;
|
|
|
|
|
|
using Service.Message;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
|
2026-01-01 12:32:08 +08:00
|
|
|
|
namespace Service.EmailServices;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
|
|
|
|
|
|
public interface IEmailHandleService
|
|
|
|
|
|
{
|
|
|
|
|
|
Task<bool> HandleEmailAsync(
|
2025-12-27 16:54:08 +08:00
|
|
|
|
string to,
|
2025-12-25 11:20:56 +08:00
|
|
|
|
string from,
|
|
|
|
|
|
string subject,
|
|
|
|
|
|
DateTime date,
|
|
|
|
|
|
string body
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
Task<bool> RefreshTransactionRecordsAsync(long emailMessageId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class EmailHandleService(
|
|
|
|
|
|
IOptions<EmailSettings> emailSettings,
|
|
|
|
|
|
ILogger<EmailHandleService> logger,
|
|
|
|
|
|
IEmailMessageRepository emailRepo,
|
|
|
|
|
|
ITransactionRecordRepository trxRepo,
|
2025-12-29 14:18:09 +08:00
|
|
|
|
IEnumerable<IEmailParseServices> emailParsers,
|
2026-01-10 17:47:09 +08:00
|
|
|
|
IMessageService messageService,
|
2025-12-31 11:10:10 +08:00
|
|
|
|
ISmartHandleService smartHandleService
|
2025-12-25 11:20:56 +08:00
|
|
|
|
) : IEmailHandleService
|
|
|
|
|
|
{
|
|
|
|
|
|
public async Task<bool> HandleEmailAsync(
|
2025-12-27 16:54:08 +08:00
|
|
|
|
string to,
|
2025-12-25 11:20:56 +08:00
|
|
|
|
string from,
|
|
|
|
|
|
string subject,
|
|
|
|
|
|
DateTime date,
|
|
|
|
|
|
string body
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
var filterForm = emailSettings.Value.FilterFromAddresses;
|
|
|
|
|
|
if (filterForm.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未配置邮件过滤条件,跳过账单处理");
|
2025-12-26 15:21:31 +08:00
|
|
|
|
return false;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!filterForm.Any(f => from.Contains(f)))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("邮件不符合发件人过滤条件,跳过账单处理");
|
2025-12-26 15:21:31 +08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-27 16:54:08 +08:00
|
|
|
|
var emailMessage = await SaveEmailAsync(to, from, subject, date, body);
|
2025-12-26 15:21:31 +08:00
|
|
|
|
|
|
|
|
|
|
if (emailMessage == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new InvalidOperationException("邮件保存失败,无法继续处理");
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var parsed = await ParseEmailBodyAsync(
|
2025-12-25 13:40:26 +08:00
|
|
|
|
from,
|
|
|
|
|
|
subject,
|
|
|
|
|
|
string.IsNullOrEmpty(emailMessage.Body)
|
|
|
|
|
|
? emailMessage.HtmlBody
|
2025-12-25 11:20:56 +08:00
|
|
|
|
: emailMessage.Body
|
|
|
|
|
|
);
|
|
|
|
|
|
if (parsed == null || parsed.Length == 0)
|
|
|
|
|
|
{
|
2026-01-10 17:47:09 +08:00
|
|
|
|
await messageService.AddAsync(
|
2025-12-29 14:18:09 +08:00
|
|
|
|
"邮件解析失败",
|
2026-01-10 17:47:09 +08:00
|
|
|
|
$"来自 {from} 发送给 {to} 的邮件(主题:{subject})未能成功解析内容,可能格式已变更或不受支持。",
|
2026-01-18 22:04:56 +08:00
|
|
|
|
url: "/balance?tab=email"
|
2025-12-29 14:18:09 +08:00
|
|
|
|
);
|
2025-12-25 11:20:56 +08:00
|
|
|
|
logger.LogWarning("未能成功解析邮件内容,跳过账单处理");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("成功解析邮件,共 {Count} 条交易记录", parsed.Length);
|
|
|
|
|
|
|
2026-01-28 17:00:58 +08:00
|
|
|
|
var allSuccess = true;
|
2025-12-31 11:10:10 +08:00
|
|
|
|
var records = new List<TransactionRecord>();
|
2025-12-25 11:20:56 +08:00
|
|
|
|
foreach (var (card, reason, amount, balance, type, occurredAt) in parsed)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("处理交易记录: 卡号 {Card}, 交易原因 {Reason}, 金额 {Amount}, 余额 {Balance}, 类型 {Type}", card, reason, amount, balance, type);
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
var record = await SaveTransactionRecordAsync(
|
2025-12-25 11:20:56 +08:00
|
|
|
|
card,
|
|
|
|
|
|
reason,
|
|
|
|
|
|
amount,
|
|
|
|
|
|
balance,
|
|
|
|
|
|
type,
|
|
|
|
|
|
occurredAt ?? date,
|
2026-01-03 12:04:26 +08:00
|
|
|
|
emailMessage.Id,
|
|
|
|
|
|
$"邮件 By {GetEmailByName(to)}"
|
2025-12-25 11:20:56 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
if (record == null)
|
2025-12-25 11:20:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
allSuccess = false;
|
2025-12-31 11:10:10 +08:00
|
|
|
|
continue;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
2025-12-31 11:10:10 +08:00
|
|
|
|
|
|
|
|
|
|
records.Add(record);
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 12:22:37 +08:00
|
|
|
|
_ = AutoClassifyAsync(records.ToArray());
|
2025-12-31 11:10:10 +08:00
|
|
|
|
|
2025-12-25 11:20:56 +08:00
|
|
|
|
return allSuccess;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<bool> RefreshTransactionRecordsAsync(long emailMessageId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var emailMessage = await emailRepo.GetByIdAsync(emailMessageId);
|
|
|
|
|
|
|
|
|
|
|
|
if (emailMessage == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到指定ID的邮件记录,无法刷新交易记录,ID: {Id}", emailMessageId);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var filterForm = emailSettings.Value.FilterFromAddresses;
|
|
|
|
|
|
if (filterForm.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未配置邮件过滤条件,跳过账单处理");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!filterForm.Any(f => emailMessage.From.Contains(f)))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("邮件不符合发件人过滤条件,跳过账单处理");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var parsed = await ParseEmailBodyAsync(
|
|
|
|
|
|
emailMessage.From,
|
2025-12-25 13:40:26 +08:00
|
|
|
|
emailMessage.Subject,
|
|
|
|
|
|
string.IsNullOrEmpty(emailMessage.Body)
|
2025-12-25 11:20:56 +08:00
|
|
|
|
? emailMessage.HtmlBody
|
|
|
|
|
|
: emailMessage.Body
|
|
|
|
|
|
);
|
|
|
|
|
|
if (parsed == null || parsed.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未能成功解析邮件内容,跳过账单处理");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogInformation("成功解析邮件,共 {Count} 条交易记录", parsed.Length);
|
|
|
|
|
|
|
2026-01-28 17:00:58 +08:00
|
|
|
|
var allSuccess = true;
|
2025-12-31 11:10:10 +08:00
|
|
|
|
var records = new List<TransactionRecord>();
|
2025-12-25 11:20:56 +08:00
|
|
|
|
foreach (var (card, reason, amount, balance, type, occurredAt) in parsed)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("刷新交易记录: 卡号 {Card}, 交易原因 {Reason}, 金额 {Amount}, 余额 {Balance}, 类型 {Type}", card, reason, amount, balance, type);
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
var record = await SaveTransactionRecordAsync(
|
2025-12-25 11:20:56 +08:00
|
|
|
|
card,
|
|
|
|
|
|
reason,
|
|
|
|
|
|
amount,
|
|
|
|
|
|
balance,
|
|
|
|
|
|
type,
|
|
|
|
|
|
occurredAt ?? emailMessage.ReceivedDate,
|
2026-01-03 12:04:26 +08:00
|
|
|
|
emailMessage.Id,
|
|
|
|
|
|
$"邮件 By {GetEmailByName(emailMessage.To)}"
|
2025-12-25 11:20:56 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
if (record == null)
|
2025-12-25 11:20:56 +08:00
|
|
|
|
{
|
|
|
|
|
|
allSuccess = false;
|
2025-12-31 11:10:10 +08:00
|
|
|
|
continue;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
2025-12-31 11:10:10 +08:00
|
|
|
|
|
|
|
|
|
|
records.Add(record);
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 12:22:37 +08:00
|
|
|
|
_ = AutoClassifyAsync(records.ToArray());
|
2025-12-31 11:10:10 +08:00
|
|
|
|
|
2025-12-25 11:20:56 +08:00
|
|
|
|
return allSuccess;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 12:22:37 +08:00
|
|
|
|
private async Task AutoClassifyAsync(TransactionRecord[] records)
|
|
|
|
|
|
{
|
2026-01-10 18:04:27 +08:00
|
|
|
|
var clone = records.ToArray().DeepClone();
|
|
|
|
|
|
|
2026-01-30 10:41:19 +08:00
|
|
|
|
if (clone?.Any() != true)
|
2026-01-10 18:04:27 +08:00
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-10 18:09:09 +08:00
|
|
|
|
var analyzedList = await AnalyzeClassifyAsync(clone);
|
2026-01-10 12:22:37 +08:00
|
|
|
|
|
2026-01-10 18:09:09 +08:00
|
|
|
|
foreach (var analyzed in analyzedList)
|
2026-01-10 12:22:37 +08:00
|
|
|
|
{
|
2026-01-10 18:09:09 +08:00
|
|
|
|
var record = records.FirstOrDefault(r => r.Id == analyzed.Id);
|
|
|
|
|
|
|
|
|
|
|
|
if (record == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
record.UnconfirmedClassify = analyzed.Classify;
|
|
|
|
|
|
record.UnconfirmedType = analyzed.Type;
|
|
|
|
|
|
|
|
|
|
|
|
record.Classify = string.Empty;
|
2026-01-10 12:22:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await trxRepo.UpdateRangeAsync(records);
|
|
|
|
|
|
|
|
|
|
|
|
// 消息
|
2026-01-10 17:47:09 +08:00
|
|
|
|
await messageService.AddAsync(
|
2026-01-10 12:22:37 +08:00
|
|
|
|
"交易记录待确认分类",
|
|
|
|
|
|
$"共有 {records.Length} 条交易记录待确认分类,请点击前往确认。",
|
|
|
|
|
|
MessageType.Url,
|
|
|
|
|
|
"/unconfirmed-classification"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-03 12:04:26 +08:00
|
|
|
|
private string GetEmailByName(string to)
|
|
|
|
|
|
{
|
|
|
|
|
|
return emailSettings.Value.SmtpList.FirstOrDefault(s => s.Email == to)?.Name ?? to;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 11:20:56 +08:00
|
|
|
|
private async Task<EmailMessage?> SaveEmailAsync(
|
2025-12-27 16:54:08 +08:00
|
|
|
|
string to,
|
2025-12-25 11:20:56 +08:00
|
|
|
|
string from,
|
|
|
|
|
|
string subject,
|
|
|
|
|
|
DateTime date,
|
|
|
|
|
|
string body
|
|
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
var emailEntity = new EmailMessage
|
|
|
|
|
|
{
|
|
|
|
|
|
From = from,
|
|
|
|
|
|
Subject = subject,
|
|
|
|
|
|
ReceivedDate = date,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 正则判断是否为HTML内容
|
|
|
|
|
|
if (Regex.IsMatch(body, @"<[^>]+>"))
|
|
|
|
|
|
{
|
|
|
|
|
|
emailEntity.HtmlBody = body;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
emailEntity.Body = body;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-12-26 15:21:31 +08:00
|
|
|
|
var emailMd5 = emailEntity.ComputeBodyHash();
|
|
|
|
|
|
var existsEmail = await emailRepo.ExistsAsync(emailMd5);
|
2025-12-25 11:20:56 +08:00
|
|
|
|
if (existsEmail != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("检测到重复邮件,跳过入库:{From} | {Subject} | {Date}", from, subject, date);
|
|
|
|
|
|
return existsEmail;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-26 15:21:31 +08:00
|
|
|
|
emailEntity.Md5 = emailMd5;
|
2025-12-27 16:54:08 +08:00
|
|
|
|
var toName = emailSettings.Value.SmtpList
|
|
|
|
|
|
.FirstOrDefault(s => s.Email == to)?.Name ?? "";
|
|
|
|
|
|
emailEntity.To = string.IsNullOrEmpty(toName) ? to : $"{toName} <{to}>";
|
2025-12-25 11:20:56 +08:00
|
|
|
|
var ok = await emailRepo.AddAsync(emailEntity);
|
|
|
|
|
|
if (ok)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("邮件已落库,ID: {Id}", emailEntity.Id);
|
|
|
|
|
|
return emailEntity;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.LogError("邮件落库失败");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 原始邮件落库失败不阻塞交易记录,但记录告警
|
|
|
|
|
|
logger.LogWarning(ex, "原始邮件落库失败");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
private async Task<TransactionRecord?> SaveTransactionRecordAsync(
|
2025-12-25 11:20:56 +08:00
|
|
|
|
string card,
|
|
|
|
|
|
string reason,
|
|
|
|
|
|
decimal amount,
|
|
|
|
|
|
decimal balance,
|
|
|
|
|
|
TransactionType type,
|
|
|
|
|
|
DateTime occurredAt,
|
2026-01-03 12:04:26 +08:00
|
|
|
|
long emailMessageId,
|
|
|
|
|
|
string importFrom
|
2025-12-25 11:20:56 +08:00
|
|
|
|
)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 根据 emailMessageId 检查是否已存在记录:存在则更新,否则新增
|
|
|
|
|
|
var existing = await trxRepo.ExistsByEmailMessageIdAsync(emailMessageId, occurredAt);
|
|
|
|
|
|
|
|
|
|
|
|
if (existing != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
existing.Card = card;
|
|
|
|
|
|
existing.Reason = reason;
|
|
|
|
|
|
existing.Amount = amount;
|
|
|
|
|
|
existing.Balance = balance;
|
|
|
|
|
|
existing.Type = type;
|
|
|
|
|
|
existing.OccurredAt = occurredAt;
|
|
|
|
|
|
|
|
|
|
|
|
var updated = await trxRepo.UpdateAsync(existing);
|
|
|
|
|
|
if (updated)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("交易记录已更新,卡号 {Card}, 金额 {Amount}", card, amount);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("交易记录更新失败,卡号 {Card}, 金额 {Amount}", card, amount);
|
2025-12-31 11:10:10 +08:00
|
|
|
|
return null;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
return existing;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var trx = new TransactionRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
Card = card,
|
|
|
|
|
|
Reason = reason,
|
|
|
|
|
|
Amount = amount,
|
|
|
|
|
|
Balance = balance,
|
|
|
|
|
|
Type = type,
|
|
|
|
|
|
OccurredAt = occurredAt,
|
|
|
|
|
|
EmailMessageId = emailMessageId,
|
2026-01-03 12:04:26 +08:00
|
|
|
|
ImportFrom = importFrom
|
2025-12-25 11:20:56 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var inserted = await trxRepo.AddAsync(trx);
|
|
|
|
|
|
if (inserted)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogInformation("交易记录已落库,卡号 {Card}, 金额 {Amount}", card, amount);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("交易记录落库失败,卡号 {Card}, 金额 {Amount}", card, amount);
|
2025-12-31 11:10:10 +08:00
|
|
|
|
return null;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 11:10:10 +08:00
|
|
|
|
return trx;
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-25 13:40:26 +08:00
|
|
|
|
private async Task<(string card, string reason, decimal amount, decimal balance, TransactionType type, DateTime? occurredAt)[]?> ParseEmailBodyAsync(string from, string subject, string body)
|
2025-12-25 11:20:56 +08:00
|
|
|
|
{
|
2025-12-25 13:40:26 +08:00
|
|
|
|
var service = emailParsers.FirstOrDefault(s => s.CanParse(from, subject, body));
|
2025-12-25 11:20:56 +08:00
|
|
|
|
|
|
|
|
|
|
if (service == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到合适的邮件解析服务,跳过解析");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return await service.ParseAsync(body);
|
|
|
|
|
|
}
|
2025-12-31 11:10:10 +08:00
|
|
|
|
|
|
|
|
|
|
private async Task<TransactionRecord[]> AnalyzeClassifyAsync(TransactionRecord[] records)
|
|
|
|
|
|
{
|
|
|
|
|
|
var result = new List<TransactionRecord>();
|
|
|
|
|
|
await smartHandleService.SmartClassifyAsync(records.Select(r => r.Id).ToArray(), chunk =>
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理分类结果
|
|
|
|
|
|
var (type, data) = chunk;
|
|
|
|
|
|
|
|
|
|
|
|
if (type != "data")
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未知的分类结果类型: {Type}, {Data}. 跳过分类", type, data);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var item = JsonSerializer.Deserialize<JsonObject>(data);
|
|
|
|
|
|
|
|
|
|
|
|
var recordId = item?["id"]?.GetValue<long>();
|
|
|
|
|
|
var classify = item?["Classify"]?.GetValue<string>();
|
|
|
|
|
|
var recordType = item?["Type"]?.GetValue<int>();
|
|
|
|
|
|
|
|
|
|
|
|
if (recordId == null || string.IsNullOrEmpty(classify) || recordType == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("AI分类结果数据不完整,跳过分类: {Data}", data);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (recordType < (int)TransactionType.Expense || recordType > (int)TransactionType.None)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("AI分类结果交易类型无效,跳过分类: {Data}", data);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var record = records.FirstOrDefault(r => r.Id == recordId);
|
|
|
|
|
|
if (record == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning("未找到对应的交易记录(AI返回内容有误),跳过分类,ID: {Id}", recordId);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
record.Classify = classify;
|
|
|
|
|
|
record.Type = (TransactionType)recordType;
|
|
|
|
|
|
|
|
|
|
|
|
result.Add(record);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.LogWarning(ex, "解析AI分类结果失败,跳过分类: {Data}", data);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return result.ToArray();
|
|
|
|
|
|
}
|
2025-12-25 11:20:56 +08:00
|
|
|
|
}
|