去年我们团队接到一个看似简单的需求:开发一个能让程序员随时随地使用AI编程助手的Web平台。产品经理最初认为这不过是给现有CLI工具套个Web壳,但实际开发中我们发现这完全是一个需要从零设计的分布式系统。
核心挑战来自五个维度:
面对不同AI工具的参数差异,我们放弃了if-else方案,采用适配器模式:
csharp复制public interface ICliToolAdapter
{
string[] SupportedToolIds { get; }
string BuildArguments(CliToolConfig tool, string prompt, CliSessionContext context);
CliOutputEvent? ParseOutputLine(string line);
}
以Claude Code和Codex为例,它们的参数构建差异被封装在各自适配器中:
csharp复制// Claude适配器
public string BuildArguments(...)
{
return $"-p --output-format=stream-json --resume {sessionId}";
}
// Codex适配器
public string BuildArguments(...)
{
return $"exec --json resume {sessionId}";
}
关键经验:新增工具只需实现适配器接口,核心业务逻辑零修改。我们后续接入Copilot仅耗时2小时。
原始方案直接同步渲染导致性能灾难:
csharp复制// 错误示范:每次输出都触发渲染
while((line=await reader.ReadLineAsync())!=null)
{
_currentMessage += Parse(line);
StateHasChanged(); // 每秒触发数十次渲染
}
优化方案采用防抖机制:
csharp复制private Timer? _updateTimer;
private bool _pendingUpdate;
void QueueUIUpdate()
{
if(_pendingUpdate) return;
_pendingUpdate = true;
_updateTimer = new Timer(_ => {
_pendingUpdate = false;
InvokeAsync(StateHasChanged);
}, null, 50, Timeout.Infinite);
}
实测数据对比:
| 方案 | CPU占用 | 渲染帧率 | 内存波动 |
|---|---|---|---|
| 直接渲染 | 98% | 10-15fps | ±300MB |
| 防抖方案 | 22% | 60fps | ±50MB |
采用三层防护体系:
目录隔离:每个会话使用UUID命名的工作目录
csharp复制var workspacePath = Path.Combine(rootDir, Guid.NewGuid().ToString());
路径校验:防止目录穿越攻击
csharp复制bool IsPathSafe(string basePath, string userPath)
{
var fullPath = Path.GetFullPath(Path.Combine(basePath, userPath));
return fullPath.StartsWith(basePath);
}
命令白名单:严格限制可执行命令
csharp复制private static readonly HashSet<string> AllowedCommands = new() { "ls", "cat", "git" };
我们为不同类型的消息设置优先级权重:
| 消息类型 | 优先级 | 保留策略 |
|---|---|---|
| 错误信息 | 9 | 永远保留 |
| 用户提问 | 7 | 保留最近5条 |
| 代码片段 | 6 | 压缩后保留 |
| AI回复 | 5 | 可丢弃 |
csharp复制public class ContextItem
{
public int Priority { get; set; } // 0-10
public bool IsCompressed { get; set; }
}
当接近token限制时触发压缩:
mermaid复制graph TD
A[检查token总数] --> B{超过阈值?}
B -->|是| C[按优先级排序]
C --> D[压缩低优先级内容]
D --> E[生成摘要]
B -->|否| F[继续正常处理]
代码片段压缩示例:
python复制# 压缩前
def calculate_sum(arr):
total = 0
for num in arr:
total += num
return total
# 压缩后
def calculate_sum(arr): # 计算数组求和
[实现细节已折叠]
针对不同内容类型采用差异化估算策略:
csharp复制public static int EstimateTokens(string text)
{
var chinese = text.Count(c => c >= 0x4E00);
var english = text.Length - chinese;
return (int)(chinese/1.5 + english/4.0);
}
public static int EstimateCodeTokens(string code)
{
return (int)(code.Length/3.5); // 代码token密度更高
}
解决iOS Safari 100vh问题:
css复制.container {
height: 100dvh; /* 动态视口单位 */
height: -webkit-fill-available; /* 兼容方案 */
}
通过VisualViewport API动态调整布局:
javascript复制window.visualViewport.addEventListener('resize', () => {
const keyboardHeight = window.innerHeight - visualViewport.height;
document.documentElement.style.setProperty('--keyboard-height', `${keyboardHeight}px`);
});
遵循人机交互指南:
css复制.code-button {
min-width: 44px;
min-height: 44px;
padding: 12px; /* 扩大热区 */
}
文件树渲染优化方案:
typescript复制function getVisibleNodes(allNodes: Node[], scrollTop: number) {
const startIdx = Math.floor(scrollTop / ROW_HEIGHT);
return allNodes.slice(startIdx, startIdx + VISIBLE_ROWS);
}
Markdown解析结果缓存:
csharp复制private readonly Dictionary<string, MarkupString> _mdCache = new();
MarkupString RenderMarkdown(string text)
{
if(_mdCache.TryGetValue(text, out var cached))
return cached;
var html = Markdown.ToHtml(text);
if(_mdCache.Count > 100) _mdCache.Clear();
return _mdCache[text] = new MarkupString(html);
}
现象:移动端网络波动时出现JSON解析错误
原因:TCP分包导致单次收到不完整JSON片段
解决方案:实现缓冲区和完整性检查
csharp复制private StringBuilder _buffer = new();
void ProcessChunk(string chunk)
{
_buffer.Append(chunk);
if(IsCompleteJson(_buffer.ToString()))
{
Parse(_buffer.ToString());
_buffer.Clear();
}
}
现象:长时间闲置后AI"失忆"
根因:会话ID未持久化到本地存储
修复方案:
javascript复制// 保存到localStorage
window.addEventListener('beforeunload', () => {
localStorage.setItem('lastSessionId', currentSessionId);
});
csharp复制async Task<List<ModelResponse>> CompareModelsAsync(string prompt)
{
var tasks = new[] {
ExecuteAsync(prompt, "claude"),
ExecuteAsync(prompt, "gpt-4")
};
return await Task.WhenAll(tasks);
}
采用CRDT算法实现多人协同编辑:
mermaid复制sequenceDiagram
UserA->>Server: 操作A
Server->>UserB: 操作A'
UserB->>Server: 操作B
Server->>UserA: 操作B'
csharp复制public interface IPlugin
{
void RegisterRoutes(IEndpointRouteBuilder routes);
void ConfigureServices(IServiceCollection services);
}
这个项目的开发经历让我深刻体会到:真正的技术挑战往往隐藏在"简单需求"的背后。从CLI工具适配到移动端优化,每个环节都需要深入理解底层原理。最大的收获是建立了处理复杂系统的思维框架——将大问题分解为可验证的小模块,通过持续迭代逼近最优解。