This commit is contained in:
SunCheng
2026-02-10 17:49:19 +08:00
parent 3e18283e52
commit d052ae5197
104 changed files with 10369 additions and 3000 deletions

View File

@@ -1,13 +1,14 @@
using Application.Dto.Transaction;
using Service.AI;
using Service.Transaction;
using Application;
namespace WebApi.Controllers;
[ApiController]
[Route("api/[controller]/[action]")]
public class TransactionRecordController(
ITransactionApplication transactionApplication,
ITransactionRecordRepository transactionRepository,
ISmartHandleService smartHandleService,
ILogger<TransactionRecordController> logger
) : ControllerBase
{
@@ -15,7 +16,7 @@ public class TransactionRecordController(
/// 获取交易记录列表(分页)
/// </summary>
[HttpGet]
public async Task<PagedResponse<TransactionRecord>> GetListAsync(
public async Task<PagedResponse<TransactionResponse>> GetListAsync(
[FromQuery] int pageIndex = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? searchKeyword = null,
@@ -29,212 +30,88 @@ public class TransactionRecordController(
[FromQuery] bool sortByAmount = false
)
{
try
var request = new TransactionQueryRequest
{
var classifies = string.IsNullOrWhiteSpace(classify)
? null
: classify.Split(',', StringSplitOptions.RemoveEmptyEntries);
PageIndex = pageIndex,
PageSize = pageSize,
SearchKeyword = searchKeyword,
Classify = classify,
Type = type,
Year = year,
Month = month,
StartDate = startDate,
EndDate = endDate,
Reason = reason,
SortByAmount = sortByAmount
};
TransactionType? transactionType = type.HasValue ? (TransactionType)type.Value : null;
var list = await transactionRepository.QueryAsync(
year: year,
month: month,
startDate: startDate,
endDate: endDate,
type: transactionType,
classifies: classifies,
searchKeyword: searchKeyword,
reason: reason,
pageIndex: pageIndex,
pageSize: pageSize,
sortByAmount: sortByAmount);
var total = await transactionRepository.CountAsync(
year: year,
month: month,
startDate: startDate,
endDate: endDate,
type: transactionType,
classifies: classifies,
searchKeyword: searchKeyword,
reason: reason);
return new PagedResponse<TransactionRecord>
{
Success = true,
Data = list.ToArray(),
Total = (int)total
};
}
catch (Exception ex)
var result = await transactionApplication.GetListAsync(request);
return new PagedResponse<TransactionResponse>
{
logger.LogError(ex, "获取交易记录列表失败,页码: {PageIndex}, 页大小: {PageSize}", pageIndex, pageSize);
return PagedResponse<TransactionRecord>.Fail($"获取交易记录列表失败: {ex.Message}");
}
Success = true,
Data = result.Data,
Total = result.Total
};
}
/// <summary>
/// 获取待确认分类的交易记录列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetUnconfirmedListAsync()
public async Task<BaseResponse<List<TransactionResponse>>> GetUnconfirmedListAsync()
{
try
{
var list = await transactionRepository.GetUnconfirmedRecordsAsync();
return list.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取待确认分类交易列表失败");
return $"获取待确认分类交易列表失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
var list = await transactionApplication.GetUnconfirmedListAsync();
return list.Ok();
}
/// <summary>
/// 全部确认待确认的交易分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync([FromBody] ConfirmAllUnconfirmedRequestDto request)
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync([FromBody] ConfirmAllUnconfirmedRequest request)
{
try
{
var count = await transactionRepository.ConfirmAllUnconfirmedAsync(request.Ids);
return count.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "全部确认待确认分类失败");
return $"全部确认待确认分类失败: {ex.Message}".Fail<int>();
}
var count = await transactionApplication.ConfirmAllUnconfirmedAsync(request.Ids);
return count.Ok();
}
/// <summary>
/// 根据ID获取交易记录详情
/// </summary>
[HttpGet("{id}")]
public async Task<BaseResponse<TransactionRecord>> GetByIdAsync(long id)
public async Task<BaseResponse<TransactionResponse>> GetByIdAsync(long id)
{
try
{
var transaction = await transactionRepository.GetByIdAsync(id);
if (transaction == null)
{
return "交易记录不存在".Fail<TransactionRecord>();
}
return transaction.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取交易记录详情失败交易ID: {TransactionId}", id);
return $"获取交易记录详情失败: {ex.Message}".Fail<TransactionRecord>();
}
var transaction = await transactionApplication.GetByIdAsync(id);
return transaction.Ok();
}
/// <summary>
/// 根据邮件ID获取交易记录列表
/// </summary>
[HttpGet("{emailId}")]
public async Task<BaseResponse<List<TransactionRecord>>> GetByEmailIdAsync(long emailId)
public async Task<BaseResponse<List<TransactionResponse>>> GetByEmailIdAsync(long emailId)
{
try
{
var transactions = await transactionRepository.GetByEmailIdAsync(emailId);
return transactions.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取邮件交易记录失败邮件ID: {EmailId}", emailId);
return $"获取邮件交易记录失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
var transactions = await transactionApplication.GetByEmailIdAsync(emailId);
return transactions.Ok();
}
/// <summary>
/// 创建交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionDto dto)
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionRequest request)
{
try
{
// 解析日期字符串
if (!DateTime.TryParse(dto.OccurredAt, out var occurredAt))
{
return "交易时间格式不正确".Fail();
}
var transaction = new TransactionRecord
{
OccurredAt = occurredAt,
Reason = dto.Reason ?? string.Empty,
Amount = dto.Amount,
Type = dto.Type,
Classify = dto.Classify ?? string.Empty,
ImportFrom = "手动录入",
ImportNo = Guid.NewGuid().ToString("N"),
Card = "手动",
EmailMessageId = 0 // 手动录入的记录EmailMessageId 设为 0
};
var result = await transactionRepository.AddAsync(transaction);
if (result)
{
return BaseResponse.Done();
}
return "创建交易记录失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "创建交易记录失败,交易信息: {@TransactionDto}", dto);
return $"创建交易记录失败: {ex.Message}".Fail();
}
await transactionApplication.CreateAsync(request);
return BaseResponse.Done();
}
/// <summary>
/// 更新交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionDto dto)
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionRequest request)
{
try
{
var transaction = await transactionRepository.GetByIdAsync(dto.Id);
if (transaction == null)
{
return "交易记录不存在".Fail();
}
// 更新可编辑字段
transaction.Reason = dto.Reason ?? string.Empty;
transaction.Amount = dto.Amount;
transaction.Balance = dto.Balance;
transaction.Type = dto.Type;
transaction.Classify = dto.Classify ?? string.Empty;
// 更新交易时间
if (!string.IsNullOrEmpty(dto.OccurredAt) && DateTime.TryParse(dto.OccurredAt, out var occurredAt))
{
transaction.OccurredAt = occurredAt;
}
// 清除待确认状态
transaction.UnconfirmedClassify = null;
transaction.UnconfirmedType = null;
var success = await transactionRepository.UpdateAsync(transaction);
if (success)
{
return BaseResponse.Done();
}
return "更新交易记录失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "更新交易记录失败交易ID: {TransactionId}, 交易信息: {@TransactionDto}", dto.Id, dto);
return $"更新交易记录失败: {ex.Message}".Fail();
}
await transactionApplication.UpdateAsync(request);
return BaseResponse.Done();
}
/// <summary>
@@ -243,29 +120,17 @@ public class TransactionRecordController(
[HttpPost]
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
{
try
{
var success = await transactionRepository.DeleteAsync(id);
if (success)
{
return BaseResponse.Done();
}
return "删除交易记录失败,记录不存在".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "删除交易记录失败交易ID: {TransactionId}", id);
return $"删除交易记录失败: {ex.Message}".Fail();
}
await transactionApplication.DeleteByIdAsync(id);
return BaseResponse.Done();
}
/// <summary>
/// 智能分析账单(流式输出)
/// </summary>
[HttpPost]
public async Task AnalyzeBillAsync([FromBody] BillAnalysisRequest request)
{
// SSE响应头设置保留在Controller
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
@@ -276,45 +141,31 @@ public class TransactionRecordController(
return;
}
await smartHandleService.AnalyzeBillAsync(request.UserInput, async void (chunk) =>
{
try
// 调用Application传递回调
await transactionApplication.AnalyzeBillAsync(
request.UserInput,
async chunk =>
{
await WriteEventAsync(chunk);
}
catch (Exception e)
{
logger.LogError(e, "流式写入账单分析结果失败");
}
});
try
{
await WriteEventAsync(chunk);
}
catch (Exception e)
{
logger.LogError(e, "流式写入账单分析结果失败");
}
});
}
/// <summary>
/// 获取指定日期的交易记录
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetByDateAsync([FromQuery] string date)
public async Task<BaseResponse<List<TransactionResponse>>> GetByDateAsync([FromQuery] string date)
{
try
{
if (!DateTime.TryParse(date, out var targetDate))
{
return "日期格式不正确".Fail<List<TransactionRecord>>();
}
// 获取当天的开始和结束时间
var startDate = targetDate.Date;
var endDate = startDate.AddDays(1);
var records = await transactionRepository.QueryAsync(startDate: startDate, endDate: endDate);
return records.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取指定日期的交易记录失败,日期: {Date}", date);
return $"获取指定日期的交易记录失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
var dateTime = DateTime.Parse(date);
var records = await transactionApplication.GetByDateAsync(dateTime);
return records.Ok();
}
/// <summary>
@@ -323,34 +174,18 @@ public class TransactionRecordController(
[HttpGet]
public async Task<BaseResponse<int>> GetUnclassifiedCountAsync()
{
try
{
var count = (int)await transactionRepository.CountAsync();
return count.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取未分类账单数量失败");
return $"获取未分类账单数量失败: {ex.Message}".Fail<int>();
}
var count = await transactionApplication.GetUnclassifiedCountAsync();
return count.Ok();
}
/// <summary>
/// 获取未分类的账单列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
public async Task<BaseResponse<List<TransactionResponse>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
{
try
{
var records = await transactionRepository.GetUnclassifiedAsync(pageSize);
return records.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取未分类账单列表失败");
return $"获取未分类账单列表失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
var records = await transactionApplication.GetUnclassifiedAsync(pageSize);
return records.Ok();
}
/// <summary>
@@ -359,6 +194,7 @@ public class TransactionRecordController(
[HttpPost]
public async Task SmartClassifyAsync([FromBody] SmartClassifyRequest request)
{
// SSE响应头设置保留在Controller
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
@@ -370,23 +206,29 @@ public class TransactionRecordController(
return;
}
await smartHandleService.SmartClassifyAsync(request.TransactionIds.ToArray(), async void (chunk) =>
{
try
// 调用Application传递回调
await transactionApplication.SmartClassifyAsync(
request.TransactionIds.ToArray(),
async chunk =>
{
var (eventType, content) = chunk;
await TrySetUnconfirmedAsync(eventType, content);
await WriteEventAsync(eventType, content);
}
catch (Exception e)
{
logger.LogError(e, "流式写入智能分类结果失败");
}
});
try
{
var (eventType, content) = chunk;
await TrySetUnconfirmedAsync(eventType, content);
await WriteEventAsync(eventType, content);
}
catch (Exception e)
{
logger.LogError(e, "流式写入智能分类结果失败");
}
});
await Response.Body.FlushAsync();
}
/// <summary>
/// Controller专属逻辑解析AI返回的JSON并更新交易记录的UnconfirmedClassify字段
/// </summary>
private async Task TrySetUnconfirmedAsync(string eventType, string content)
{
if (eventType != "data")
@@ -436,102 +278,33 @@ public class TransactionRecordController(
[HttpPost]
public async Task<BaseResponse> BatchUpdateClassifyAsync([FromBody] List<BatchUpdateClassifyItem> items)
{
try
{
var successCount = 0;
var failCount = 0;
foreach (var item in items)
{
var record = await transactionRepository.GetByIdAsync(item.Id);
if (record != null)
{
record.Classify = item.Classify ?? string.Empty;
// 如果提供了Type也更新Type
if (item.Type.HasValue)
{
record.Type = item.Type.Value;
}
if (!string.IsNullOrEmpty(record.Classify))
{
record.UnconfirmedClassify = null;
}
if (record.Type == item.Type)
{
record.UnconfirmedType = TransactionType.None;
}
var success = await transactionRepository.UpdateAsync(record);
if (success)
successCount++;
else
failCount++;
}
else
{
failCount++;
}
}
return $"批量更新完成,成功 {successCount} 条,失败 {failCount} 条".Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "批量更新分类失败");
return $"批量更新分类失败: {ex.Message}".Fail();
}
var count = await transactionApplication.BatchUpdateClassifyAsync(items);
return $"批量更新完成,成功 {count} 条".Ok();
}
/// <summary>
/// 按摘要批量更新分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> BatchUpdateByReasonAsync([FromBody] BatchUpdateByReasonDto dto)
public async Task<BaseResponse<int>> BatchUpdateByReasonAsync([FromBody] BatchUpdateByReasonRequest request)
{
try
{
var count = await transactionRepository.BatchUpdateByReasonAsync(dto.Reason, dto.Type, dto.Classify);
return count.Ok($"成功更新 {count} 条记录");
}
catch (Exception ex)
{
logger.LogError(ex, "按摘要批量更新分类失败,摘要: {Reason}", dto.Reason);
return $"按摘要批量更新分类失败: {ex.Message}".Fail<int>();
}
var count = await transactionApplication.BatchUpdateByReasonAsync(request);
return count.Ok($"成功更新 {count} 条记录");
}
/// <summary>
/// 一句话录账解析
/// </summary>
[HttpPost]
public async Task<BaseResponse<TransactionParseResult>> ParseOneLine([FromBody] ParseOneLineRequestDto request)
public async Task<BaseResponse<TransactionParseResult?>> ParseOneLine([FromBody] ParseOneLineRequest request)
{
if (string.IsNullOrEmpty(request.Text))
{
return "请求参数缺失text".Fail<TransactionParseResult>();
}
try
{
var result = await smartHandleService.ParseOneLineBillAsync(request.Text);
if (result == null)
{
return "AI解析失败".Fail<TransactionParseResult>();
}
return result.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "一句话录账解析失败,文本: {Text}", request.Text);
return ("AI解析失败: " + ex.Message).Fail<TransactionParseResult>();
}
var result = await transactionApplication.ParseOneLineAsync(request.Text);
return result.Ok();
}
/// <summary>
/// SSE辅助方法写入事件带类型
/// </summary>
private async Task WriteEventAsync(string eventType, string data)
{
var message = $"event: {eventType}\ndata: {data}\n\n";
@@ -539,6 +312,9 @@ public class TransactionRecordController(
await Response.Body.FlushAsync();
}
/// <summary>
/// SSE辅助方法写入数据
/// </summary>
private async Task WriteEventAsync(string data)
{
var message = $"data: {data}\n\n";
@@ -547,32 +323,6 @@ public class TransactionRecordController(
}
}
/// <summary>
/// 创建交易记录DTO
/// </summary>
public record CreateTransactionDto(
string OccurredAt,
string? Reason,
decimal Amount,
TransactionType Type,
string? Classify
);
/// <summary>
/// 更新交易记录DTO
/// </summary>
public record UpdateTransactionDto(
long Id,
string? Reason,
decimal Amount,
decimal Balance,
TransactionType Type,
string? Classify,
string? OccurredAt = null
);
/// <summary>
/// 智能分类请求DTO
/// </summary>
@@ -580,35 +330,9 @@ public record SmartClassifyRequest(
List<long>? TransactionIds = null
);
/// <summary>
/// 批量更新分类项DTO
/// </summary>
public record BatchUpdateClassifyItem(
long Id,
string? Classify,
TransactionType? Type = null
);
/// <summary>
/// 按摘要批量更新DTO
/// </summary>
public record BatchUpdateByReasonDto(
string Reason,
TransactionType Type,
string Classify
);
/// <summary>
/// 账单分析请求DTO
/// </summary>
public record BillAnalysisRequest(
string UserInput
);
public record ParseOneLineRequestDto(
string Text
);
public record ConfirmAllUnconfirmedRequestDto(
long[] Ids
);