339 lines
10 KiB
C#
339 lines
10 KiB
C#
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
|
||
);
|