first commot
This commit is contained in:
84
WebApi/Controllers/BillImportController.cs
Normal file
84
WebApi/Controllers/BillImportController.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 账单导入控制器
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class BillImportController(
|
||||
ILogger<BillImportController> logger,
|
||||
IImportService importService
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传账单文件
|
||||
/// </summary>
|
||||
/// <param name="file">账单文件</param>
|
||||
/// <param name="type">账单类型(Alipay | WeChat)</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<object>> UploadFile(
|
||||
[FromForm] IFormFile file,
|
||||
[FromForm] string type
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证参数
|
||||
if (file.Length == 0)
|
||||
{
|
||||
return BaseResponse<object>.Fail("请选择要上传的文件");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(type) || (type != "Alipay" && type != "WeChat"))
|
||||
{
|
||||
return BaseResponse<object>.Fail("账单类型参数错误,必须是 Alipay 或 WeChat");
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
var allowedExtensions = new[] { ".csv", ".xlsx", ".xls" };
|
||||
var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||
if (!allowedExtensions.Contains(fileExtension))
|
||||
{
|
||||
return BaseResponse<object>.Fail("只支持 CSV 或 Excel 文件格式");
|
||||
}
|
||||
|
||||
// 验证文件大小(10MB限制)
|
||||
const long maxFileSize = 10 * 1024 * 1024;
|
||||
if (file.Length > maxFileSize)
|
||||
{
|
||||
return BaseResponse<object>.Fail("文件大小不能超过 10MB");
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
var fileName = $"{type}_{DateTime.Now:yyyyMMddHHmmss}_{Guid.NewGuid():N}{fileExtension}";
|
||||
|
||||
// 保存文件
|
||||
var ok = false;
|
||||
var message = string.Empty;
|
||||
await using (var stream = new MemoryStream())
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
if (type == "Alipay")
|
||||
{
|
||||
(ok, message) = await importService.ImportAlipayAsync(stream, fileExtension);
|
||||
}
|
||||
else if (type == "WeChat")
|
||||
{
|
||||
(ok, message) = await importService.ImportWeChatAsync(stream, fileExtension);
|
||||
}
|
||||
}
|
||||
|
||||
return new BaseResponse<object>
|
||||
{
|
||||
Success = ok,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "文件上传失败,类型: {Type}", type);
|
||||
return BaseResponse<object>.Fail($"文件上传失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
41
WebApi/Controllers/Dto/BaseResponse.cs
Normal file
41
WebApi/Controllers/Dto/BaseResponse.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace WebApi.Controllers.Dto;
|
||||
|
||||
public class BaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
public bool Success { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
|
||||
public static BaseResponse Fail(string message)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = false,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class BaseResponse<T> : BaseResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回数据
|
||||
/// </summary>
|
||||
public T? Data { get; set; }
|
||||
|
||||
public new static BaseResponse<T> Fail(string message)
|
||||
{
|
||||
return new BaseResponse<T>
|
||||
{
|
||||
Success = false,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
40
WebApi/Controllers/Dto/EmailMessageDto.cs
Normal file
40
WebApi/Controllers/Dto/EmailMessageDto.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
namespace WebApi.Controllers.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 邮件消息DTO,包含额外的统计信息
|
||||
/// </summary>
|
||||
public class EmailMessageDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string From { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
public string HtmlBody { get; set; } = string.Empty;
|
||||
public DateTime ReceivedDate { get; set; }
|
||||
public DateTime CreateTime { get; set; }
|
||||
public DateTime? UpdateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已解析的账单数量
|
||||
/// </summary>
|
||||
public int TransactionCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 从实体转换为DTO
|
||||
/// </summary>
|
||||
public static EmailMessageDto FromEntity(Entity.EmailMessage entity, int transactionCount = 0)
|
||||
{
|
||||
return new EmailMessageDto
|
||||
{
|
||||
Id = entity.Id,
|
||||
Subject = entity.Subject,
|
||||
From = entity.From,
|
||||
Body = entity.Body,
|
||||
HtmlBody = entity.HtmlBody,
|
||||
ReceivedDate = entity.ReceivedDate,
|
||||
CreateTime = entity.CreateTime,
|
||||
UpdateTime = entity.UpdateTime,
|
||||
TransactionCount = transactionCount
|
||||
};
|
||||
}
|
||||
}
|
||||
25
WebApi/Controllers/Dto/PagedResponse.cs
Normal file
25
WebApi/Controllers/Dto/PagedResponse.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace WebApi.Controllers.Dto;
|
||||
|
||||
public class PagedResponse<T> : BaseResponse<T[]>
|
||||
{
|
||||
public long LastId { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 最后一条记录的时间(用于游标分页)
|
||||
/// </summary>
|
||||
public DateTime? LastTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 总记录数
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
public new static PagedResponse<T> Fail(string message)
|
||||
{
|
||||
return new PagedResponse<T>
|
||||
{
|
||||
Success = false,
|
||||
Message = message
|
||||
};
|
||||
}
|
||||
}
|
||||
161
WebApi/Controllers/EmailMessageController.cs
Normal file
161
WebApi/Controllers/EmailMessageController.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
namespace WebApi.Controllers.EmailMessage;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class EmailMessageController(
|
||||
IEmailMessageRepository emailRepository,
|
||||
ITransactionRecordRepository transactionRepository,
|
||||
ILogger<EmailMessageController> logger,
|
||||
IEmailHandleService emailHandleService,
|
||||
IEmailBackgroundService emailBackgroundService
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取邮件列表(分页)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<PagedResponse<EmailMessageDto>> GetListAsync(
|
||||
[FromQuery] DateTime? lastReceivedDate = null,
|
||||
[FromQuery] long? lastId = null
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (list, lastTime, lastIdResult) = await emailRepository.GetPagedListAsync(lastReceivedDate, lastId);
|
||||
var total = await emailRepository.GetTotalCountAsync();
|
||||
|
||||
// 为每个邮件获取账单数量
|
||||
var emailDtos = new List<EmailMessageDto>();
|
||||
foreach (var email in list)
|
||||
{
|
||||
var transactionCount = await transactionRepository.GetCountByEmailIdAsync(email.Id);
|
||||
emailDtos.Add(EmailMessageDto.FromEntity(email, transactionCount));
|
||||
}
|
||||
|
||||
return new PagedResponse<EmailMessageDto>
|
||||
{
|
||||
Success = true,
|
||||
Data = emailDtos.ToArray(),
|
||||
Total = (int)total,
|
||||
LastId = lastIdResult,
|
||||
LastTime = lastTime
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取邮件列表失败,时间: {LastTime}, ID: {LastId}", lastReceivedDate, lastId);
|
||||
return PagedResponse<EmailMessageDto>.Fail($"获取邮件列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取邮件详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<EmailMessageDto>> GetByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var email = await emailRepository.GetByIdAsync(id);
|
||||
if (email == null)
|
||||
{
|
||||
return BaseResponse<EmailMessageDto>.Fail("邮件不存在");
|
||||
}
|
||||
|
||||
// 获取账单数量
|
||||
var transactionCount = await transactionRepository.GetCountByEmailIdAsync(id);
|
||||
var emailDto = EmailMessageDto.FromEntity(email, transactionCount);
|
||||
|
||||
return new BaseResponse<EmailMessageDto>
|
||||
{
|
||||
Success = true,
|
||||
Data = emailDto
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取邮件详情失败,邮件ID: {EmailId}", id);
|
||||
return BaseResponse<EmailMessageDto>.Fail($"获取邮件详情失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BaseResponse> DeleteByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await emailRepository.DeleteAsync(id);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("删除邮件失败,邮件不存在");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "删除邮件失败,邮件ID: {EmailId}", id);
|
||||
return BaseResponse.Fail($"删除邮件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新分析邮件并刷新交易记录
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> RefreshTransactionRecordsAsync([FromQuery] long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var email = await emailRepository.GetByIdAsync(id);
|
||||
if (email == null)
|
||||
{
|
||||
return BaseResponse.Fail("邮件不存在");
|
||||
}
|
||||
|
||||
var success = await emailHandleService.RefreshTransactionRecordsAsync(id);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("重新分析失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "重新分析邮件失败,邮件ID: {EmailId}", id);
|
||||
return BaseResponse.Fail($"重新分析失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即同步邮件
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> SyncEmailsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await emailBackgroundService.SyncEmailsAsync();
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true,
|
||||
Message = "同步成功"
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "同步邮件失败");
|
||||
return BaseResponse.Fail($"同步邮件失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
301
WebApi/Controllers/TransactionCategoryController.cs
Normal file
301
WebApi/Controllers/TransactionCategoryController.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
namespace WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class TransactionCategoryController(
|
||||
ITransactionCategoryRepository categoryRepository,
|
||||
ILogger<TransactionCategoryController> logger
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取分类树(支持按类型筛选)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<BaseResponse<List<TransactionCategoryTreeDto>>> GetTreeAsync([FromQuery] TransactionType? type = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tree = await categoryRepository.GetCategoryTreeAsync(type);
|
||||
return new BaseResponse<List<TransactionCategoryTreeDto>>
|
||||
{
|
||||
Success = true,
|
||||
Data = tree
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取分类树失败");
|
||||
return BaseResponse<List<TransactionCategoryTreeDto>>.Fail($"获取分类树失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶级分类列表(按类型)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<BaseResponse<List<TransactionCategory>>> GetTopLevelAsync([FromQuery] TransactionType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categories = await categoryRepository.GetTopLevelCategoriesByTypeAsync(type);
|
||||
return new BaseResponse<List<TransactionCategory>>
|
||||
{
|
||||
Success = true,
|
||||
Data = categories
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取顶级分类失败, Type: {Type}", type);
|
||||
return BaseResponse<List<TransactionCategory>>.Fail($"获取顶级分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取子分类列表
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<BaseResponse<List<TransactionCategory>>> GetChildrenAsync([FromQuery] long parentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categories = await categoryRepository.GetChildCategoriesAsync(parentId);
|
||||
return new BaseResponse<List<TransactionCategory>>
|
||||
{
|
||||
Success = true,
|
||||
Data = categories
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取子分类失败, ParentId: {ParentId}", parentId);
|
||||
return BaseResponse<List<TransactionCategory>>.Fail($"获取子分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取分类详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<TransactionCategory>> GetByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var category = await categoryRepository.GetByIdAsync(id);
|
||||
if (category == null)
|
||||
{
|
||||
return BaseResponse<TransactionCategory>.Fail("分类不存在");
|
||||
}
|
||||
|
||||
return new BaseResponse<TransactionCategory>
|
||||
{
|
||||
Success = true,
|
||||
Data = category
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取分类详情失败, Id: {Id}", id);
|
||||
return BaseResponse<TransactionCategory>.Fail($"获取分类详情失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建分类
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<long>> CreateAsync([FromBody] CreateCategoryDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查同名分类
|
||||
var existing = await categoryRepository.GetByNameAndParentAsync(dto.Name, dto.ParentId, dto.Type);
|
||||
if (existing != null)
|
||||
{
|
||||
return BaseResponse<long>.Fail("同级已存在相同名称的分类");
|
||||
}
|
||||
|
||||
var category = new TransactionCategory
|
||||
{
|
||||
Name = dto.Name,
|
||||
ParentId = dto.ParentId,
|
||||
Type = dto.Type,
|
||||
Level = dto.Level,
|
||||
SortOrder = dto.SortOrder,
|
||||
Icon = dto.Icon,
|
||||
Remark = dto.Remark
|
||||
};
|
||||
|
||||
var result = await categoryRepository.AddAsync(category);
|
||||
if (result)
|
||||
{
|
||||
return new BaseResponse<long>
|
||||
{
|
||||
Success = true,
|
||||
Data = category.Id
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse<long>.Fail("创建分类失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "创建分类失败, Dto: {@Dto}", dto);
|
||||
return BaseResponse<long>.Fail($"创建分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新分类
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateCategoryDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var category = await categoryRepository.GetByIdAsync(dto.Id);
|
||||
if (category == null)
|
||||
{
|
||||
return BaseResponse.Fail("分类不存在");
|
||||
}
|
||||
|
||||
// 如果修改了名称,检查同名
|
||||
if (category.Name != dto.Name)
|
||||
{
|
||||
var existing = await categoryRepository.GetByNameAndParentAsync(dto.Name, category.ParentId, category.Type);
|
||||
if (existing != null && existing.Id != dto.Id)
|
||||
{
|
||||
return BaseResponse.Fail("同级已存在相同名称的分类");
|
||||
}
|
||||
}
|
||||
|
||||
category.Name = dto.Name;
|
||||
category.SortOrder = dto.SortOrder;
|
||||
category.Icon = dto.Icon;
|
||||
category.IsEnabled = dto.IsEnabled;
|
||||
category.Remark = dto.Remark;
|
||||
category.UpdateTime = DateTime.Now;
|
||||
|
||||
var success = await categoryRepository.UpdateAsync(category);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse { Success = true };
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("更新分类失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "更新分类失败, Dto: {@Dto}", dto);
|
||||
return BaseResponse.Fail($"更新分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除分类
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> DeleteAsync([FromQuery] long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否有子分类
|
||||
var children = await categoryRepository.GetChildCategoriesAsync(id);
|
||||
if (children.Any())
|
||||
{
|
||||
return BaseResponse.Fail("该分类下存在子分类,无法删除");
|
||||
}
|
||||
|
||||
// 检查是否被使用
|
||||
var inUse = await categoryRepository.IsCategoryInUseAsync(id);
|
||||
if (inUse)
|
||||
{
|
||||
return BaseResponse.Fail("该分类已被使用,无法删除");
|
||||
}
|
||||
|
||||
var success = await categoryRepository.DeleteAsync(id);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse { Success = true };
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("删除分类失败,分类不存在");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "删除分类失败, Id: {Id}", id);
|
||||
return BaseResponse.Fail($"删除分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量创建分类(用于初始化)
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<int>> BatchCreateAsync([FromBody] List<CreateCategoryDto> dtoList)
|
||||
{
|
||||
try
|
||||
{
|
||||
var categories = dtoList.Select(dto => new TransactionCategory
|
||||
{
|
||||
Name = dto.Name,
|
||||
ParentId = dto.ParentId,
|
||||
Type = dto.Type,
|
||||
Level = dto.Level,
|
||||
SortOrder = dto.SortOrder,
|
||||
Icon = dto.Icon,
|
||||
Remark = dto.Remark
|
||||
}).ToList();
|
||||
|
||||
var result = await categoryRepository.AddRangeAsync(categories);
|
||||
if (result)
|
||||
{
|
||||
return new BaseResponse<int>
|
||||
{
|
||||
Success = true,
|
||||
Data = categories.Count
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse<int>.Fail("批量创建分类失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "批量创建分类失败, Count: {Count}", dtoList.Count);
|
||||
return BaseResponse<int>.Fail($"批量创建分类失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建分类DTO
|
||||
/// </summary>
|
||||
public record CreateCategoryDto(
|
||||
string Name,
|
||||
long ParentId,
|
||||
TransactionType Type,
|
||||
int Level,
|
||||
int SortOrder = 0,
|
||||
string? Icon = null,
|
||||
string? Remark = null
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 更新分类DTO
|
||||
/// </summary>
|
||||
public record UpdateCategoryDto(
|
||||
long Id,
|
||||
string Name,
|
||||
int SortOrder,
|
||||
string? Icon,
|
||||
bool IsEnabled,
|
||||
string? Remark
|
||||
);
|
||||
302
WebApi/Controllers/TransactionRecordController.cs
Normal file
302
WebApi/Controllers/TransactionRecordController.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
namespace WebApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]/[action]")]
|
||||
public class TransactionRecordController(
|
||||
ITransactionRecordRepository transactionRepository,
|
||||
ILogger<TransactionRecordController> logger
|
||||
) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取交易记录列表(分页)
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<PagedResponse<Entity.TransactionRecord>> GetListAsync(
|
||||
[FromQuery] DateTime? lastOccurredAt = null,
|
||||
[FromQuery] long? lastId = null,
|
||||
[FromQuery] string? searchKeyword = null
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var (list, lastTime, lastIdResult) = await transactionRepository.GetPagedListAsync(lastOccurredAt, lastId, 20, searchKeyword);
|
||||
var total = await transactionRepository.GetTotalCountAsync();
|
||||
|
||||
return new PagedResponse<Entity.TransactionRecord>
|
||||
{
|
||||
Success = true,
|
||||
Data = list.ToArray(),
|
||||
Total = (int)total,
|
||||
LastId = lastIdResult,
|
||||
LastTime = lastTime
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取交易记录列表失败,时间: {LastTime}, ID: {LastId}", lastOccurredAt, lastId);
|
||||
return PagedResponse<Entity.TransactionRecord>.Fail($"获取交易记录列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取交易记录详情
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<Entity.TransactionRecord>> GetByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transaction = await transactionRepository.GetByIdAsync(id);
|
||||
if (transaction == null)
|
||||
{
|
||||
return BaseResponse<Entity.TransactionRecord>.Fail("交易记录不存在");
|
||||
}
|
||||
|
||||
return new BaseResponse<Entity.TransactionRecord>
|
||||
{
|
||||
Success = true,
|
||||
Data = transaction
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取交易记录详情失败,交易ID: {TransactionId}", id);
|
||||
return BaseResponse<Entity.TransactionRecord>.Fail($"获取交易记录详情失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据邮件ID获取交易记录列表
|
||||
/// </summary>
|
||||
[HttpGet("{emailId}")]
|
||||
public async Task<BaseResponse<List<Entity.TransactionRecord>>> GetByEmailIdAsync(long emailId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transactions = await transactionRepository.GetByEmailIdAsync(emailId);
|
||||
return new BaseResponse<List<Entity.TransactionRecord>>
|
||||
{
|
||||
Success = true,
|
||||
Data = transactions
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取邮件交易记录失败,邮件ID: {EmailId}", emailId);
|
||||
return BaseResponse<List<Entity.TransactionRecord>>.Fail($"获取邮件交易记录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建交易记录
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析日期字符串
|
||||
if (!DateTime.TryParse(dto.OccurredAt, out var occurredAt))
|
||||
{
|
||||
return BaseResponse.Fail("交易时间格式不正确");
|
||||
}
|
||||
|
||||
var transaction = new Entity.TransactionRecord
|
||||
{
|
||||
OccurredAt = occurredAt,
|
||||
Reason = dto.Reason ?? string.Empty,
|
||||
Amount = dto.Amount,
|
||||
Type = dto.Type,
|
||||
Classify = dto.Classify ?? string.Empty,
|
||||
SubClassify = dto.SubClassify ?? string.Empty,
|
||||
ImportFrom = "手动录入",
|
||||
EmailMessageId = 0 // 手动录入的记录,EmailMessageId 设为 0
|
||||
};
|
||||
|
||||
var result = await transactionRepository.AddAsync(transaction);
|
||||
if (result)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("创建交易记录失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "创建交易记录失败,交易信息: {@TransactionDto}", dto);
|
||||
return BaseResponse.Fail($"创建交易记录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新交易记录
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionDto dto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var transaction = await transactionRepository.GetByIdAsync(dto.Id);
|
||||
if (transaction == null)
|
||||
{
|
||||
return BaseResponse.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;
|
||||
transaction.SubClassify = dto.SubClassify ?? string.Empty;
|
||||
|
||||
var success = await transactionRepository.UpdateAsync(transaction);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("更新交易记录失败");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "更新交易记录失败,交易ID: {TransactionId}, 交易信息: {@TransactionDto}", dto.Id, dto);
|
||||
return BaseResponse.Fail($"更新交易记录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除交易记录
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await transactionRepository.DeleteAsync(id);
|
||||
if (success)
|
||||
{
|
||||
return new BaseResponse
|
||||
{
|
||||
Success = true
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BaseResponse.Fail("删除交易记录失败,记录不存在");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "删除交易记录失败,交易ID: {TransactionId}", id);
|
||||
return BaseResponse.Fail($"删除交易记录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定月份每天的消费统计
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<BaseResponse<List<DailyStatisticsDto>>> GetDailyStatisticsAsync(
|
||||
[FromQuery] int year,
|
||||
[FromQuery] int month
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
var statistics = await transactionRepository.GetDailyStatisticsAsync(year, month);
|
||||
var result = statistics.Select(s => new DailyStatisticsDto(s.Key, s.Value.count, s.Value.amount)).ToList();
|
||||
|
||||
return new BaseResponse<List<DailyStatisticsDto>>
|
||||
{
|
||||
Success = true,
|
||||
Data = result
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取日历统计数据失败,年份: {Year}, 月份: {Month}", year, month);
|
||||
return BaseResponse<List<DailyStatisticsDto>>.Fail($"获取日历统计数据失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定日期的交易记录
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<BaseResponse<List<Entity.TransactionRecord>>> GetByDateAsync(
|
||||
[FromQuery] string date
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!DateTime.TryParse(date, out var targetDate))
|
||||
{
|
||||
return BaseResponse<List<Entity.TransactionRecord>>.Fail("日期格式不正确");
|
||||
}
|
||||
|
||||
// 获取当天的开始和结束时间
|
||||
var startDate = targetDate.Date;
|
||||
var endDate = startDate.AddDays(1);
|
||||
|
||||
var records = await transactionRepository.GetByDateRangeAsync(startDate, endDate);
|
||||
|
||||
return new BaseResponse<List<Entity.TransactionRecord>>
|
||||
{
|
||||
Success = true,
|
||||
Data = records
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "获取指定日期的交易记录失败,日期: {Date}", date);
|
||||
return BaseResponse<List<Entity.TransactionRecord>>.Fail($"获取指定日期的交易记录失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建交易记录DTO
|
||||
/// </summary>
|
||||
public record CreateTransactionDto(
|
||||
string OccurredAt,
|
||||
string? Reason,
|
||||
decimal Amount,
|
||||
TransactionType Type,
|
||||
string? Classify,
|
||||
string? SubClassify
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 更新交易记录DTO
|
||||
/// </summary>
|
||||
public record UpdateTransactionDto(
|
||||
long Id,
|
||||
string? Reason,
|
||||
decimal Amount,
|
||||
decimal Balance,
|
||||
TransactionType Type,
|
||||
string? Classify,
|
||||
string? SubClassify
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// 日历统计响应DTO
|
||||
/// </summary>
|
||||
public record DailyStatisticsDto(
|
||||
string Date,
|
||||
int Count,
|
||||
decimal Amount
|
||||
);
|
||||
Reference in New Issue
Block a user