Files
EmailBill/WebApi/Controllers/TransactionRecordController.cs

339 lines
10 KiB
C#
Raw Permalink Normal View History

2026-02-10 17:49:19 +08:00
using Application.Dto.Transaction;
using Service.AI;
2026-02-10 17:49:19 +08:00
using Application;
2026-01-23 17:14:41 +08:00
namespace WebApi.Controllers;
2025-12-26 17:13:57 +08:00
2025-12-25 11:20:56 +08:00
[ApiController]
[Route("api/[controller]/[action]")]
public class TransactionRecordController(
2026-02-10 17:49:19 +08:00
ITransactionApplication transactionApplication,
2025-12-25 11:20:56 +08:00
ITransactionRecordRepository transactionRepository,
2026-02-09 19:25:51 +08:00
ILogger<TransactionRecordController> logger
2025-12-25 11:20:56 +08:00
) : ControllerBase
{
/// <summary>
/// 获取交易记录列表(分页)
/// </summary>
[HttpGet]
2026-02-10 17:49:19 +08:00
public async Task<PagedResponse<TransactionResponse>> GetListAsync(
2025-12-27 22:05:50 +08:00
[FromQuery] int pageIndex = 1,
[FromQuery] int pageSize = 20,
2025-12-26 17:56:08 +08:00
[FromQuery] string? searchKeyword = null,
[FromQuery] string? classify = null,
[FromQuery] int? type = null,
[FromQuery] int? year = null,
2025-12-27 22:05:50 +08:00
[FromQuery] int? month = null,
[FromQuery] DateTime? startDate = null,
[FromQuery] DateTime? endDate = null,
2025-12-30 17:02:30 +08:00
[FromQuery] string? reason = null,
2025-12-27 22:05:50 +08:00
[FromQuery] bool sortByAmount = false
2025-12-25 11:20:56 +08:00
)
{
2026-02-10 17:49:19 +08:00
var request = new TransactionQueryRequest
{
PageIndex = pageIndex,
PageSize = pageSize,
SearchKeyword = searchKeyword,
Classify = classify,
Type = type,
Year = year,
Month = month,
StartDate = startDate,
EndDate = endDate,
Reason = reason,
SortByAmount = sortByAmount
};
var result = await transactionApplication.GetListAsync(request);
return new PagedResponse<TransactionResponse>
{
Success = true,
Data = result.Data,
Total = result.Total
};
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 获取待确认分类的交易记录列表
/// </summary>
[HttpGet]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<List<TransactionResponse>>> GetUnconfirmedListAsync()
{
2026-02-10 17:49:19 +08:00
var list = await transactionApplication.GetUnconfirmedListAsync();
return list.Ok();
}
/// <summary>
/// 全部确认待确认的交易分类
/// </summary>
[HttpPost]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync([FromBody] ConfirmAllUnconfirmedRequest request)
{
2026-02-10 17:49:19 +08:00
var count = await transactionApplication.ConfirmAllUnconfirmedAsync(request.Ids);
return count.Ok();
}
2025-12-25 11:20:56 +08:00
/// <summary>
/// 根据ID获取交易记录详情
/// </summary>
[HttpGet("{id}")]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<TransactionResponse>> GetByIdAsync(long id)
2025-12-25 11:20:56 +08:00
{
2026-02-10 17:49:19 +08:00
var transaction = await transactionApplication.GetByIdAsync(id);
return transaction.Ok();
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 根据邮件ID获取交易记录列表
/// </summary>
[HttpGet("{emailId}")]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<List<TransactionResponse>>> GetByEmailIdAsync(long emailId)
2025-12-25 11:20:56 +08:00
{
2026-02-10 17:49:19 +08:00
var transactions = await transactionApplication.GetByEmailIdAsync(emailId);
return transactions.Ok();
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 创建交易记录
/// </summary>
[HttpPost]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionRequest request)
2025-12-25 11:20:56 +08:00
{
2026-02-10 17:49:19 +08:00
await transactionApplication.CreateAsync(request);
return BaseResponse.Done();
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 更新交易记录
/// </summary>
[HttpPost]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionRequest request)
2025-12-25 11:20:56 +08:00
{
2026-02-10 17:49:19 +08:00
await transactionApplication.UpdateAsync(request);
return BaseResponse.Done();
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 删除交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
{
2026-02-10 17:49:19 +08:00
await transactionApplication.DeleteByIdAsync(id);
return BaseResponse.Done();
2025-12-25 11:20:56 +08:00
}
2025-12-26 17:13:57 +08:00
/// <summary>
/// 智能分析账单(流式输出)
/// </summary>
2026-02-10 17:49:19 +08:00
[HttpPost]
2025-12-26 17:13:57 +08:00
public async Task AnalyzeBillAsync([FromBody] BillAnalysisRequest request)
{
2026-02-10 17:49:19 +08:00
// SSE响应头设置保留在Controller
2025-12-26 17:13:57 +08:00
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
2025-12-31 11:10:10 +08:00
if (string.IsNullOrWhiteSpace(request.UserInput))
2025-12-26 17:13:57 +08:00
{
2025-12-31 11:10:10 +08:00
await WriteEventAsync("<div class='error-message'>请输入分析内容</div>");
return;
2025-12-26 17:13:57 +08:00
}
2025-12-31 11:10:10 +08:00
2026-02-10 17:49:19 +08:00
// 调用Application传递回调
await transactionApplication.AnalyzeBillAsync(
request.UserInput,
async chunk =>
2026-01-18 22:25:59 +08:00
{
2026-02-10 17:49:19 +08:00
try
{
await WriteEventAsync(chunk);
}
catch (Exception e)
{
logger.LogError(e, "流式写入账单分析结果失败");
}
});
2025-12-26 17:13:57 +08:00
}
/// <summary>
/// 获取指定日期的交易记录
/// </summary>
[HttpGet]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<List<TransactionResponse>>> GetByDateAsync([FromQuery] string date)
2025-12-25 11:20:56 +08:00
{
2026-02-10 17:49:19 +08:00
var dateTime = DateTime.Parse(date);
var records = await transactionApplication.GetByDateAsync(dateTime);
return records.Ok();
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 获取未分类的账单数量
/// </summary>
[HttpGet]
public async Task<BaseResponse<int>> GetUnclassifiedCountAsync()
{
2026-02-10 17:49:19 +08:00
var count = await transactionApplication.GetUnclassifiedCountAsync();
return count.Ok();
}
/// <summary>
/// 获取未分类的账单列表
/// </summary>
[HttpGet]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<List<TransactionResponse>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
{
2026-02-10 17:49:19 +08:00
var records = await transactionApplication.GetUnclassifiedAsync(pageSize);
return records.Ok();
}
/// <summary>
/// 智能分类 - 使用AI对账单进行分类流式响应
/// </summary>
[HttpPost]
public async Task SmartClassifyAsync([FromBody] SmartClassifyRequest request)
{
2026-02-10 17:49:19 +08:00
// SSE响应头设置保留在Controller
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
2025-12-30 18:49:46 +08:00
// 验证账单ID列表
if (request.TransactionIds == null || request.TransactionIds.Count == 0)
{
2025-12-30 18:49:46 +08:00
await WriteEventAsync("error", "请提供要分类的账单ID");
return;
}
2025-12-30 18:49:46 +08:00
2026-02-10 17:49:19 +08:00
// 调用Application传递回调
await transactionApplication.SmartClassifyAsync(
request.TransactionIds.ToArray(),
async chunk =>
2026-01-18 22:25:59 +08:00
{
2026-02-10 17:49:19 +08:00
try
{
var (eventType, content) = chunk;
await TrySetUnconfirmedAsync(eventType, content);
await WriteEventAsync(eventType, content);
}
catch (Exception e)
{
logger.LogError(e, "流式写入智能分类结果失败");
}
});
2025-12-30 18:49:46 +08:00
await Response.Body.FlushAsync();
}
2026-02-10 17:49:19 +08:00
/// <summary>
/// Controller专属逻辑解析AI返回的JSON并更新交易记录的UnconfirmedClassify字段
/// </summary>
private async Task TrySetUnconfirmedAsync(string eventType, string content)
{
if (eventType != "data")
{
return;
}
try
{
var jsonObject = JsonSerializer.Deserialize<JsonObject>(content);
var id = jsonObject?["id"]?.GetValue<long>() ?? 0;
var classify = jsonObject?["Classify"]?.GetValue<string>() ?? string.Empty;
var typeValue = jsonObject?["Type"]?.GetValue<int>() ?? -1;
2026-01-18 22:25:59 +08:00
if (id == 0 || typeValue == -1 || string.IsNullOrEmpty(classify))
{
logger.LogWarning("解析智能分类结果时,发现无效数据,内容: {Content}", content);
return;
}
var record = await transactionRepository.GetByIdAsync(id);
if (record == null)
{
logger.LogWarning("解析智能分类结果时未找到对应的交易记录ID: {Id}", id);
return;
}
record.UnconfirmedClassify = classify;
record.UnconfirmedType = (TransactionType)typeValue;
var success = await transactionRepository.UpdateAsync(record);
if (!success)
{
logger.LogWarning("解析智能分类结果时更新交易记录失败ID: {Id}", id);
}
}
catch (Exception ex)
{
logger.LogError(ex, "解析智能分类结果失败,内容: {Content}", content);
}
}
/// <summary>
/// 批量更新账单分类
/// </summary>
[HttpPost]
public async Task<BaseResponse> BatchUpdateClassifyAsync([FromBody] List<BatchUpdateClassifyItem> items)
{
2026-02-10 17:49:19 +08:00
var count = await transactionApplication.BatchUpdateClassifyAsync(items);
return $"批量更新完成,成功 {count} 条".Ok();
}
2025-12-26 15:21:31 +08:00
/// <summary>
/// 按摘要批量更新分类
/// </summary>
[HttpPost]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<int>> BatchUpdateByReasonAsync([FromBody] BatchUpdateByReasonRequest request)
2025-12-26 15:21:31 +08:00
{
2026-02-10 17:49:19 +08:00
var count = await transactionApplication.BatchUpdateByReasonAsync(request);
return count.Ok($"成功更新 {count} 条记录");
2025-12-26 15:21:31 +08:00
}
/// <summary>
/// 一句话录账解析
/// </summary>
[HttpPost]
2026-02-10 17:49:19 +08:00
public async Task<BaseResponse<TransactionParseResult?>> ParseOneLine([FromBody] ParseOneLineRequest request)
{
2026-02-10 17:49:19 +08:00
var result = await transactionApplication.ParseOneLineAsync(request.Text);
return result.Ok();
}
2026-02-10 17:49:19 +08:00
/// <summary>
/// SSE辅助方法写入事件带类型
/// </summary>
2026-01-30 10:41:19 +08:00
private async Task WriteEventAsync(string eventType, string data)
{
var message = $"event: {eventType}\ndata: {data}\n\n";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
}
2026-02-10 17:49:19 +08:00
/// <summary>
/// SSE辅助方法写入数据
/// </summary>
2025-12-31 11:10:10 +08:00
private async Task WriteEventAsync(string data)
2025-12-29 20:30:15 +08:00
{
2025-12-31 11:10:10 +08:00
var message = $"data: {data}\n\n";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
2025-12-29 20:30:15 +08:00
}
2025-12-25 11:20:56 +08:00
}
/// <summary>
/// 智能分类请求DTO
/// </summary>
public record SmartClassifyRequest(
2025-12-26 15:21:31 +08:00
List<long>? TransactionIds = null
2026-01-18 22:25:59 +08:00
);
2025-12-26 17:13:57 +08:00
/// <summary>
/// 账单分析请求DTO
/// </summary>
public record BillAnalysisRequest(
string UserInput
2026-01-18 22:25:59 +08:00
);