Files
EmailBill/WebApi/Controllers/TransactionRecordController.cs
SunCheng d052ae5197 fix
2026-02-10 17:49:19 +08:00

339 lines
10 KiB
C#
Raw Permalink 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.
using Application.Dto.Transaction;
using Service.AI;
using Application;
namespace WebApi.Controllers;
[ApiController]
[Route("api/[controller]/[action]")]
public class TransactionRecordController(
ITransactionApplication transactionApplication,
ITransactionRecordRepository transactionRepository,
ILogger<TransactionRecordController> logger
) : ControllerBase
{
/// <summary>
/// 获取交易记录列表(分页)
/// </summary>
[HttpGet]
public async Task<PagedResponse<TransactionResponse>> GetListAsync(
[FromQuery] int pageIndex = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? searchKeyword = null,
[FromQuery] string? classify = null,
[FromQuery] int? type = null,
[FromQuery] int? year = null,
[FromQuery] int? month = null,
[FromQuery] DateTime? startDate = null,
[FromQuery] DateTime? endDate = null,
[FromQuery] string? reason = null,
[FromQuery] bool sortByAmount = false
)
{
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
};
}
/// <summary>
/// 获取待确认分类的交易记录列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionResponse>>> GetUnconfirmedListAsync()
{
var list = await transactionApplication.GetUnconfirmedListAsync();
return list.Ok();
}
/// <summary>
/// 全部确认待确认的交易分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync([FromBody] ConfirmAllUnconfirmedRequest request)
{
var count = await transactionApplication.ConfirmAllUnconfirmedAsync(request.Ids);
return count.Ok();
}
/// <summary>
/// 根据ID获取交易记录详情
/// </summary>
[HttpGet("{id}")]
public async Task<BaseResponse<TransactionResponse>> GetByIdAsync(long id)
{
var transaction = await transactionApplication.GetByIdAsync(id);
return transaction.Ok();
}
/// <summary>
/// 根据邮件ID获取交易记录列表
/// </summary>
[HttpGet("{emailId}")]
public async Task<BaseResponse<List<TransactionResponse>>> GetByEmailIdAsync(long emailId)
{
var transactions = await transactionApplication.GetByEmailIdAsync(emailId);
return transactions.Ok();
}
/// <summary>
/// 创建交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionRequest request)
{
await transactionApplication.CreateAsync(request);
return BaseResponse.Done();
}
/// <summary>
/// 更新交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionRequest request)
{
await transactionApplication.UpdateAsync(request);
return BaseResponse.Done();
}
/// <summary>
/// 删除交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
{
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");
if (string.IsNullOrWhiteSpace(request.UserInput))
{
await WriteEventAsync("<div class='error-message'>请输入分析内容</div>");
return;
}
// 调用Application传递回调
await transactionApplication.AnalyzeBillAsync(
request.UserInput,
async chunk =>
{
try
{
await WriteEventAsync(chunk);
}
catch (Exception e)
{
logger.LogError(e, "流式写入账单分析结果失败");
}
});
}
/// <summary>
/// 获取指定日期的交易记录
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionResponse>>> GetByDateAsync([FromQuery] string date)
{
var dateTime = DateTime.Parse(date);
var records = await transactionApplication.GetByDateAsync(dateTime);
return records.Ok();
}
/// <summary>
/// 获取未分类的账单数量
/// </summary>
[HttpGet]
public async Task<BaseResponse<int>> GetUnclassifiedCountAsync()
{
var count = await transactionApplication.GetUnclassifiedCountAsync();
return count.Ok();
}
/// <summary>
/// 获取未分类的账单列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionResponse>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
{
var records = await transactionApplication.GetUnclassifiedAsync(pageSize);
return records.Ok();
}
/// <summary>
/// 智能分类 - 使用AI对账单进行分类流式响应
/// </summary>
[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");
// 验证账单ID列表
if (request.TransactionIds == null || request.TransactionIds.Count == 0)
{
await WriteEventAsync("error", "请提供要分类的账单ID");
return;
}
// 调用Application传递回调
await transactionApplication.SmartClassifyAsync(
request.TransactionIds.ToArray(),
async chunk =>
{
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")
{
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;
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)
{
var count = await transactionApplication.BatchUpdateClassifyAsync(items);
return $"批量更新完成,成功 {count} 条".Ok();
}
/// <summary>
/// 按摘要批量更新分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> BatchUpdateByReasonAsync([FromBody] BatchUpdateByReasonRequest request)
{
var count = await transactionApplication.BatchUpdateByReasonAsync(request);
return count.Ok($"成功更新 {count} 条记录");
}
/// <summary>
/// 一句话录账解析
/// </summary>
[HttpPost]
public async Task<BaseResponse<TransactionParseResult?>> ParseOneLine([FromBody] ParseOneLineRequest request)
{
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";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
}
/// <summary>
/// SSE辅助方法写入数据
/// </summary>
private async Task WriteEventAsync(string data)
{
var message = $"data: {data}\n\n";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
}
}
/// <summary>
/// 智能分类请求DTO
/// </summary>
public record SmartClassifyRequest(
List<long>? TransactionIds = null
);
/// <summary>
/// 账单分析请求DTO
/// </summary>
public record BillAnalysisRequest(
string UserInput
);