Skip to content

Commit 8eab26b

Browse files
💾 Feat(Workflow): 实现 WorkflowStorageService 统一存储服务,扩展 WorkflowCase 模型属性,注册新服务至 DI 容器,新增 Workflow 相关事件名
1 parent 023fef8 commit 8eab26b

6 files changed

Lines changed: 261 additions & 3 deletions

File tree

KitX Clients/KitX Core/KitX.Core/DI/CoreServiceCollectionExtensions.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,14 @@ public static IServiceCollection AddCoreServices(this IServiceCollection service
250250
Log.Information("Registering IBlueprintService...");
251251
services.AddSingleton<IBlueprintService, BlueprintService>();
252252

253+
// Workflow Storage Service
254+
Log.Information("Registering IWorkflowStorageService...");
255+
services.AddSingleton<IWorkflowStorageService>(provider =>
256+
{
257+
var service = WorkflowStorageService.Instance;
258+
return service;
259+
});
260+
253261
Log.Information("AddCoreServices completed.");
254262
return services;
255263
}

KitX Clients/KitX Core/KitX.Core/Event/EventNames.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,24 @@ public static class EventNames
109109
/// Plugin unregistered event
110110
/// </summary>
111111
public const string PluginUnregistered = "PluginUnregistered";
112+
113+
/// <summary>
114+
/// Workflow created event
115+
/// </summary>
116+
public const string WorkflowCreated = "WorkflowCreated";
117+
118+
/// <summary>
119+
/// Workflow deleted event
120+
/// </summary>
121+
public const string WorkflowDeleted = "WorkflowDeleted";
122+
123+
/// <summary>
124+
/// Workflow renamed event
125+
/// </summary>
126+
public const string WorkflowRenamed = "WorkflowRenamed";
127+
128+
/// <summary>
129+
/// Workflow data saved event
130+
/// </summary>
131+
public const string WorkflowDataSaved = "WorkflowDataSaved";
112132
}

KitX Clients/KitX Core/KitX.Core/Workflow/WorkflowScriptService.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,10 @@ public class WorkflowCase : IWorkflowCase
990990
public string Id { get; set; } = Guid.NewGuid().ToString();
991991
public string Name { get; set; } = "Untitled Workflow";
992992
public string Description { get; set; } = string.Empty;
993-
public string IconPath { get; set; } = string.Empty;
993+
public string Author { get; set; } = string.Empty;
994994
public bool IsRunning { get; set; }
995995
public string? ScriptPath { get; set; }
996+
public DateTime CreatedTime { get; set; } = DateTime.UtcNow;
997+
public DateTime LastModifiedTime { get; set; } = DateTime.UtcNow;
998+
public string TriggerType { get; set; } = "Manual";
996999
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text.Json;
6+
using System.Threading.Tasks;
7+
using KitX.Core.Contract.Workflow;
8+
using Serilog;
9+
10+
namespace KitX.Core.Workflow;
11+
12+
/// <summary>
13+
/// Workflow storage service implementation - manages workflow file persistence
14+
/// </summary>
15+
public class WorkflowStorageService : IWorkflowStorageService
16+
{
17+
private static readonly JsonSerializerOptions _jsonOptions = new()
18+
{
19+
WriteIndented = true
20+
};
21+
22+
private static readonly Lazy<WorkflowStorageService> _instance = new(() => new());
23+
public static WorkflowStorageService Instance => _instance.Value;
24+
25+
private readonly string _storageDirectory;
26+
27+
public WorkflowStorageService()
28+
{
29+
_storageDirectory = Path.Combine("./Data/", "Workflows");
30+
}
31+
32+
/// <inheritdoc/>
33+
public string StorageDirectory => _storageDirectory;
34+
35+
/// <inheritdoc/>
36+
public async Task<IWorkflowCase> CreateWorkflowAsync(string name, string? description = null)
37+
{
38+
EnsureDirectoryExists();
39+
40+
var id = Guid.NewGuid().ToString();
41+
var now = DateTime.UtcNow;
42+
43+
var kcs = new KcsFileFormat
44+
{
45+
Id = id,
46+
Name = name,
47+
Description = description ?? string.Empty,
48+
Author = string.Empty,
49+
CreatedTime = now,
50+
LastModifiedTime = now,
51+
TriggerType = "Manual",
52+
UseBlockMode = true,
53+
BlockScriptSource = GetDefaultBlockScriptTemplate(),
54+
MainProgram = string.Empty,
55+
HelperFunctions = [],
56+
VariableConstants = [],
57+
BlueprintData = null,
58+
};
59+
60+
var filePath = GetWorkflowFilePath(id);
61+
await SaveKcsFileInternalAsync(filePath, kcs);
62+
63+
Log.Information("[WorkflowStorageService] Created workflow: {Id} - {Name}", id, name);
64+
65+
return new WorkflowCase
66+
{
67+
Id = id,
68+
Name = name,
69+
Description = description ?? string.Empty,
70+
Author = string.Empty,
71+
IsRunning = false,
72+
ScriptPath = filePath,
73+
CreatedTime = now,
74+
LastModifiedTime = now,
75+
TriggerType = "Manual",
76+
};
77+
}
78+
79+
/// <inheritdoc/>
80+
public async Task<KcsFileFormat?> LoadWorkflowDataAsync(string workflowId)
81+
{
82+
var filePath = GetWorkflowFilePath(workflowId);
83+
if (!File.Exists(filePath))
84+
{
85+
Log.Warning("[WorkflowStorageService] Workflow file not found: {FilePath}", filePath);
86+
return null;
87+
}
88+
89+
return await LoadKcsFileInternalAsync(filePath);
90+
}
91+
92+
/// <inheritdoc/>
93+
public async Task SaveWorkflowDataAsync(string workflowId, KcsFileFormat data)
94+
{
95+
EnsureDirectoryExists();
96+
97+
data.LastModifiedTime = DateTime.UtcNow;
98+
var filePath = GetWorkflowFilePath(workflowId);
99+
await SaveKcsFileInternalAsync(filePath, data);
100+
101+
Log.Information("[WorkflowStorageService] Saved workflow: {Id}", workflowId);
102+
}
103+
104+
/// <inheritdoc/>
105+
public Task DeleteWorkflowAsync(string workflowId)
106+
{
107+
var filePath = GetWorkflowFilePath(workflowId);
108+
if (File.Exists(filePath))
109+
{
110+
File.Delete(filePath);
111+
Log.Information("[WorkflowStorageService] Deleted workflow: {Id}", workflowId);
112+
}
113+
else
114+
{
115+
Log.Warning("[WorkflowStorageService] Workflow file not found for deletion: {Id}", workflowId);
116+
}
117+
118+
return Task.CompletedTask;
119+
}
120+
121+
/// <inheritdoc/>
122+
public async Task RenameWorkflowAsync(string workflowId, string newName)
123+
{
124+
var data = await LoadWorkflowDataAsync(workflowId);
125+
if (data != null)
126+
{
127+
data.Name = newName;
128+
await SaveWorkflowDataAsync(workflowId, data);
129+
Log.Information("[WorkflowStorageService] Renamed workflow {Id} to: {Name}", workflowId, newName);
130+
}
131+
}
132+
133+
/// <inheritdoc/>
134+
public async Task<IReadOnlyList<IWorkflowCase>> DiscoverWorkflowsAsync()
135+
{
136+
EnsureDirectoryExists();
137+
138+
var results = new List<IWorkflowCase>();
139+
140+
try
141+
{
142+
var files = Directory.GetFiles(_storageDirectory, "*.kcs");
143+
foreach (var file in files)
144+
{
145+
try
146+
{
147+
var kcs = await LoadKcsFileInternalAsync(file);
148+
if (kcs == null) continue;
149+
150+
// Backward compatibility: use file name as Id if missing
151+
var id = string.IsNullOrEmpty(kcs.Id)
152+
? Path.GetFileNameWithoutExtension(file)
153+
: kcs.Id;
154+
155+
// Backward compatibility: use file creation time if missing
156+
var createdTime = kcs.CreatedTime == default
157+
? File.GetCreationTimeUtc(file)
158+
: kcs.CreatedTime;
159+
160+
results.Add(new WorkflowCase
161+
{
162+
Id = id,
163+
Name = string.IsNullOrEmpty(kcs.Name) ? Path.GetFileNameWithoutExtension(file) : kcs.Name,
164+
Description = kcs.Description ?? string.Empty,
165+
Author = kcs.Author ?? string.Empty,
166+
IsRunning = false,
167+
ScriptPath = file,
168+
CreatedTime = createdTime,
169+
LastModifiedTime = kcs.LastModifiedTime == default ? File.GetLastWriteTimeUtc(file) : kcs.LastModifiedTime,
170+
TriggerType = string.IsNullOrEmpty(kcs.TriggerType) ? "Manual" : kcs.TriggerType,
171+
});
172+
}
173+
catch (Exception ex)
174+
{
175+
Log.Warning(ex, "[WorkflowStorageService] Error loading workflow file: {File}", file);
176+
}
177+
}
178+
}
179+
catch (DirectoryNotFoundException)
180+
{
181+
Log.Information("[WorkflowStorageService] Storage directory not found, returning empty list");
182+
}
183+
184+
Log.Information("[WorkflowStorageService] Discovered {Count} workflows", results.Count);
185+
return results;
186+
}
187+
188+
/// <inheritdoc/>
189+
public string GetWorkflowFilePath(string workflowId)
190+
{
191+
return Path.Combine(_storageDirectory, $"{workflowId}.kcs");
192+
}
193+
194+
private void EnsureDirectoryExists()
195+
{
196+
if (!Directory.Exists(_storageDirectory))
197+
Directory.CreateDirectory(_storageDirectory);
198+
}
199+
200+
private static async Task<KcsFileFormat?> LoadKcsFileInternalAsync(string filePath)
201+
{
202+
try
203+
{
204+
var json = await File.ReadAllTextAsync(filePath);
205+
return JsonSerializer.Deserialize<KcsFileFormat>(json, _jsonOptions);
206+
}
207+
catch (Exception ex)
208+
{
209+
Log.Error(ex, "[WorkflowStorageService] Error loading KCS file: {FilePath}", filePath);
210+
return null;
211+
}
212+
}
213+
214+
private static async Task SaveKcsFileInternalAsync(string filePath, KcsFileFormat data)
215+
{
216+
var json = JsonSerializer.Serialize(data, _jsonOptions);
217+
await File.WriteAllTextAsync(filePath, json);
218+
}
219+
220+
/// <summary>
221+
/// Returns a default BlockScript template for new workflows
222+
/// </summary>
223+
private static string GetDefaultBlockScriptTemplate()
224+
{
225+
return "#ConstBlock\n\n#PubVarBlock\n\n#MainBlock\nPrint(\"Hello, KitX Workflow!\");\n";
226+
}
227+
}

0 commit comments

Comments
 (0)