using Service.AI; namespace WebApi.Controllers; [ApiController] [Route("api/[controller]/[action]")] public class TransactionCategoryController( ITransactionCategoryRepository categoryRepository, ITransactionRecordRepository transactionRecordRepository, ILogger logger, IBudgetRepository budgetRepository, IOpenAiService openAiService ) : ControllerBase { /// /// 获取分类列表(支持按类型筛选) /// [HttpGet] public async Task>> GetListAsync([FromQuery] TransactionType? type = null) { try { List 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>(); } } /// /// 根据ID获取分类详情 /// [HttpGet("{id}")] public async Task> GetByIdAsync(long id) { try { var category = await categoryRepository.GetByIdAsync(id); if (category == null) { return "分类不存在".Fail(); } return category.Ok(); } catch (Exception ex) { logger.LogError(ex, "获取分类详情失败, Id: {Id}", id); return $"获取分类详情失败: {ex.Message}".Fail(); } } /// /// 创建分类 /// [HttpPost] public async Task> CreateAsync([FromBody] CreateCategoryDto dto) { try { // 检查同名分类 var existing = await categoryRepository.GetByNameAndTypeAsync(dto.Name, dto.Type); if (existing != null) { return "已存在相同名称的分类".Fail(); } var category = new TransactionCategory { Name = dto.Name, Type = dto.Type }; var result = await categoryRepository.AddAsync(category); if (result) { return category.Id.Ok(); } return "创建分类失败".Fail(); } catch (Exception ex) { logger.LogError(ex, "创建分类失败, Dto: {@Dto}", dto); return $"创建分类失败: {ex.Message}".Fail(); } } /// /// 更新分类 /// [HttpPost] public async Task 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(); } } /// /// 删除分类 /// [HttpPost] public async Task 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(); } } /// /// 批量创建分类(用于初始化) /// [HttpPost] public async Task> BatchCreateAsync([FromBody] List 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(); } catch (Exception ex) { logger.LogError(ex, "批量创建分类失败, Count: {Count}", dtoList.Count); return $"批量创建分类失败: {ex.Message}".Fail(); } } /// /// 为指定分类生成新的SVG图标 /// [HttpPost] public async Task> GenerateIconAsync([FromBody] GenerateIconDto dto) { try { var category = await categoryRepository.GetByIdAsync(dto.CategoryId); if (category == null) { return "分类不存在".Fail(); } // 使用AI生成简洁的SVG图标 var systemPrompt = @"你是一个SVG图标生成专家。请生成简洁、现代的单色SVG图标。 要求: 1. 只返回标签及其内容,不要其他说明文字 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(); } // 提取SVG标签内容 var svgMatch = System.Text.RegularExpressions.Regex.Match(svgContent, @"]*>.*?", System.Text.RegularExpressions.RegexOptions.Singleline); if (!svgMatch.Success) { return "生成的内容不包含有效的SVG标签".Fail(); } var svg = svgMatch.Value; // 解析现有图标数组 var icons = string.IsNullOrWhiteSpace(category.Icon) ? new List() : JsonSerializer.Deserialize>(category.Icon) ?? new List(); // 添加新图标 icons.Add(svg); // 更新数据库 category.Icon = JsonSerializer.Serialize(icons); category.UpdateTime = DateTime.Now; var success = await categoryRepository.UpdateAsync(category); if (!success) { return "更新分类图标失败".Fail(); } return svg.Ok(); } catch (Exception ex) { logger.LogError(ex, "生成图标失败, CategoryId: {CategoryId}", dto.CategoryId); return $"生成图标失败: {ex.Message}".Fail(); } } /// /// 更新分类的选中图标索引 /// [HttpPost] public async Task 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>(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(); } } } /// /// 创建分类DTO /// public record CreateCategoryDto( string Name, TransactionType Type ); /// /// 更新分类DTO /// public record UpdateCategoryDto( long Id, string Name ); /// /// 生成图标DTO /// public record GenerateIconDto( long CategoryId ); /// /// 更新选中图标DTO /// public record UpdateSelectedIconDto( long CategoryId, int SelectedIndex );