Files
EmailBill/WebApi/Controllers/TransactionCategoryController.cs
SunCheng 460dcd17ef tmp
2026-02-02 11:07:34 +08:00

354 lines
11 KiB
C#
Raw 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.
namespace WebApi.Controllers;
[ApiController]
[Route("api/[controller]/[action]")]
public class TransactionCategoryController(
ITransactionCategoryRepository categoryRepository,
ITransactionRecordRepository transactionRecordRepository,
ILogger<TransactionCategoryController> logger,
IBudgetRepository budgetRepository,
IOpenAiService openAiService
) : ControllerBase
{
/// <summary>
/// 获取分类列表(支持按类型筛选)
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionCategory>>> GetListAsync([FromQuery] TransactionType? type = null)
{
try
{
List<TransactionCategory> categories;
if (type.HasValue)
{
categories = await categoryRepository.GetCategoriesByTypeAsync(type.Value);
}
else
{
categories = (await categoryRepository.GetAllAsync()).ToList();
}
return categories.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取分类列表失败");
return $"获取分类列表失败: {ex.Message}".Fail<List<TransactionCategory>>();
}
}
/// <summary>
/// 根据ID获取分类详情
/// </summary>
[HttpGet("{id}")]
public async Task<BaseResponse<TransactionCategory>> GetByIdAsync(long id)
{
try
{
var category = await categoryRepository.GetByIdAsync(id);
if (category == null)
{
return "分类不存在".Fail<TransactionCategory>();
}
return category.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取分类详情失败, Id: {Id}", id);
return $"获取分类详情失败: {ex.Message}".Fail<TransactionCategory>();
}
}
/// <summary>
/// 创建分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<long>> CreateAsync([FromBody] CreateCategoryDto dto)
{
try
{
// 检查同名分类
var existing = await categoryRepository.GetByNameAndTypeAsync(dto.Name, dto.Type);
if (existing != null)
{
return "已存在相同名称的分类".Fail<long>();
}
var category = new TransactionCategory
{
Name = dto.Name,
Type = dto.Type
};
var result = await categoryRepository.AddAsync(category);
if (result)
{
return category.Id.Ok();
}
return "创建分类失败".Fail<long>();
}
catch (Exception ex)
{
logger.LogError(ex, "创建分类失败, Dto: {@Dto}", dto);
return $"创建分类失败: {ex.Message}".Fail<long>();
}
}
/// <summary>
/// 更新分类
/// </summary>
[HttpPost]
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateCategoryDto dto)
{
try
{
var category = await categoryRepository.GetByIdAsync(dto.Id);
if (category == null)
{
return "分类不存在".Fail();
}
// 如果修改了名称,检查同名
if (category.Name != dto.Name)
{
var existing = await categoryRepository.GetByNameAndTypeAsync(dto.Name, category.Type);
if (existing != null && existing.Id != dto.Id)
{
return "已存在相同名称的分类".Fail();
}
// 同步更新交易记录中的分类名称
await transactionRecordRepository.UpdateCategoryNameAsync(category.Name, dto.Name, category.Type);
await budgetRepository.UpdateBudgetCategoryNameAsync(category.Name, dto.Name, category.Type);
}
category.Name = dto.Name;
category.UpdateTime = DateTime.Now;
var success = await categoryRepository.UpdateAsync(category);
if (success)
{
return "更新分类成功".Ok();
}
return "更新分类失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "更新分类失败, Dto: {@Dto}", dto);
return $"更新分类失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 删除分类
/// </summary>
[HttpPost]
public async Task<BaseResponse> DeleteAsync([FromQuery] long id)
{
try
{
// 检查是否被使用
var inUse = await categoryRepository.IsCategoryInUseAsync(id);
if (inUse)
{
return "该分类已被使用,无法删除".Fail();
}
var success = await categoryRepository.DeleteAsync(id);
if (success)
{
return BaseResponse.Done();
}
return "删除分类失败,分类不存在".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "删除分类失败, Id: {Id}", id);
return $"删除分类失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 批量创建分类(用于初始化)
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> BatchCreateAsync([FromBody] List<CreateCategoryDto> dtoList)
{
try
{
var categories = dtoList.Select(dto => new TransactionCategory
{
Name = dto.Name,
Type = dto.Type
}).ToList();
var result = await categoryRepository.AddRangeAsync(categories);
if (result)
{
return categories.Count.Ok();
}
return "批量创建分类失败".Fail<int>();
}
catch (Exception ex)
{
logger.LogError(ex, "批量创建分类失败, Count: {Count}", dtoList.Count);
return $"批量创建分类失败: {ex.Message}".Fail<int>();
}
}
/// <summary>
/// 为指定分类生成新的SVG图标
/// </summary>
[HttpPost]
public async Task<BaseResponse<string>> GenerateIconAsync([FromBody] GenerateIconDto dto)
{
try
{
var category = await categoryRepository.GetByIdAsync(dto.CategoryId);
if (category == null)
{
return "分类不存在".Fail<string>();
}
// 使用AI生成简洁的SVG图标
var systemPrompt = @"你是一个SVG图标生成专家。请生成简洁、现代的单色SVG图标。
要求:
1. 只返回<svg>标签及其内容,不要其他说明文字
2. viewBox=""0 0 24 24""
3. 尺寸为24x24
4. 使用单色fill=""currentColor""
5. 简洁的设计,适合作为应用图标";
var userPrompt = $"为分类"{category.Name}"{(category.Type == TransactionType.Expense ? "" : category.Type == TransactionType.Income ? "" : "")}生成一个SVG图标";
var svgContent = await openAiService.ChatAsync(systemPrompt, userPrompt, timeoutSeconds: 30);
if (string.IsNullOrWhiteSpace(svgContent))
{
return "AI生成图标失败".Fail<string>();
}
// 提取SVG标签内容
var svgMatch = System.Text.RegularExpressions.Regex.Match(svgContent, @"<svg[^>]*>.*?</svg>",
System.Text.RegularExpressions.RegexOptions.Singleline);
if (!svgMatch.Success)
{
return "生成的内容不包含有效的SVG标签".Fail<string>();
}
var svg = svgMatch.Value;
// 解析现有图标数组
var icons = string.IsNullOrWhiteSpace(category.Icon)
? new List<string>()
: JsonSerializer.Deserialize<List<string>>(category.Icon) ?? new List<string>();
// 添加新图标
icons.Add(svg);
// 更新数据库
category.Icon = JsonSerializer.Serialize(icons);
category.UpdateTime = DateTime.Now;
var success = await categoryRepository.UpdateAsync(category);
if (!success)
{
return "更新分类图标失败".Fail<string>();
}
return svg.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "生成图标失败, CategoryId: {CategoryId}", dto.CategoryId);
return $"生成图标失败: {ex.Message}".Fail<string>();
}
}
/// <summary>
/// 更新分类的选中图标索引
/// </summary>
[HttpPost]
public async Task<BaseResponse> UpdateSelectedIconAsync([FromBody] UpdateSelectedIconDto dto)
{
try
{
var category = await categoryRepository.GetByIdAsync(dto.CategoryId);
if (category == null)
{
return "分类不存在".Fail();
}
// 验证索引有效性
if (string.IsNullOrWhiteSpace(category.Icon))
{
return "该分类没有可用图标".Fail();
}
var icons = JsonSerializer.Deserialize<List<string>>(category.Icon);
if (icons == null || dto.SelectedIndex < 0 || dto.SelectedIndex >= icons.Count)
{
return "无效的图标索引".Fail();
}
// 这里可以添加一个SelectedIconIndex字段到实体中或者将选中的图标移到数组第一位
// 暂时采用移动到第一位的方式
var selectedIcon = icons[dto.SelectedIndex];
icons.RemoveAt(dto.SelectedIndex);
icons.Insert(0, selectedIcon);
category.Icon = JsonSerializer.Serialize(icons);
category.UpdateTime = DateTime.Now;
var success = await categoryRepository.UpdateAsync(category);
if (success)
{
return "更新图标成功".Ok();
}
return "更新图标失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "更新选中图标失败, Dto: {@Dto}", dto);
return $"更新选中图标失败: {ex.Message}".Fail();
}
}
}
/// <summary>
/// 创建分类DTO
/// </summary>
public record CreateCategoryDto(
string Name,
TransactionType Type
);
/// <summary>
/// 更新分类DTO
/// </summary>
public record UpdateCategoryDto(
long Id,
string Name
);
/// <summary>
/// 生成图标DTO
/// </summary>
public record GenerateIconDto(
long CategoryId
);
/// <summary>
/// 更新选中图标DTO
/// </summary>
public record UpdateSelectedIconDto(
long CategoryId,
int SelectedIndex
);