当前位置: 首页 > news >正文

Claude Code / Codex 一键安装器 (附带C#源码,MIT开源)

(开源地址在文末gitee链接)

Avalonia 12 · .NET 8 · MVVM · TDD

作者:Wesky / Dotnet Dancer(公众号)

这是一款基于 Avalonia 12 + .NET 8 的 Windows 桌面工具,让用户零命令、无感地装好 Claude Code 与 Codex 环境:自动检测并安装 Node、git 等前置依赖,自动初始化配置模板、断点续传优化等。

为什么我要做这一个教程?我无意间看到,咸鱼、淘宝上有卖安装claude code或者codex安装教程的,一份一二十块。嘿,太黑了!这不是很简单的事情吗?也有人说,还一些非技术的啊!他们可能不太会安装。于是,我写了个开源项目,默认是Windows环境的,感兴趣的技术大神,可以自行拓展为mac、linux环境。理论上玩mac或者linux的大神,应该动手能力好,也不需要我这个安装程序。

客户端其实很简陋,打开效果如下:

源码架构整体截图:

本文目录

一、开发实现说明

二、核心源码解析

三、用户使用说明

四、配置文件配置详解

五、开源项目地址

01 一、开发实现说明

1.1 技术栈

整个项目仅依赖一组轻量、主流的 .NET 生态库,构建产物为单一 Windows 桌面程序:

•Avalonia 12.0.3:跨平台 .NET UI 框架;本项目用其桌面后端(Avalonia.Desktop)、Fluent 主题与 Inter 字体。

•.NET 8(net8.0):开启 ImplicitUsings、Nullable 与 LangVersion=latest;输出类型 WinExe。

•CommunityToolkit.Mvvm 8.4.2:MVVM 源生成器,用 [ObservableProperty] 生成属性、[RelayCommand] 生成命令。

•Avalonia.Controls.WebView 12.0.1:内嵌 NativeWebView(底层 WebView2)展示官方教程。

•Microsoft.Win32.Registry 5.0.0:读注册表判断 WebView2 运行时是否已安装。

•xUnit:单元测试,12 个测试类、约 45 个用例全通过;核心安装链路全部脱网可测。

1.2 架构思想:接口化 + 构造函数注入

核心原则只有一句:把所有外部副作用(执行进程、HTTP 下载、刷新 PATH、读注册表、打开文件、弹窗)全部收敛到接口化的服务层,再以构造函数注入装配。这样 ViewModel 与各安装器只依赖抽象,测试时用 fake 替换即可在无网络、无管理员权限的环境下验证完整安装链路。

服务契约(节选):

•IProcessRunner —— 唯一执行进程的出口,是整个体系最关键的可测试缝隙;

•IFileDownloader / IEnvironmentDetector / IWingetBootstrapper —— 下载、探测、winget 引导;

•INodeInstaller / IGitInstaller / IPackageInstaller —— 三条安装链;

•IConfigFileService / ITutorialService / IPathRefresher / IWebViewBootstrapper —— 配置、教程、PATH、WebView 运行时。

NOTE 没有 DI 容器,手动 Composition Root

依赖装配集中在 App.axaml.cs 的 OnFrameworkInitializationCompleted 里手写 new 出来——对这种规模的工具,手动组合根比引入容器更直观、启动更快、也更易读。Node 与 git 安装器共享同一个 IWingetBootstrapper 与 IFileDownloader 实例。

•MainWindowViewModel 只依赖 6 个接口,对具体实现一无所知;

•应用清单 requireAdministrator,一次提权、全程无二次弹窗(装 MSI/EXE、Add-AppxPackage 都需要管理员);

•遵循 TDD:先写测试再写实现,安装链路的每个分支(已装跳过、winget 成功、回退镜像、源全失败)都有对应用例。

1.3 项目结构

[TXT] AutoInstall.sln

AutoInstall.sln

├─ src/AutoInstall/ Avalonia 应用

│ ├─ App.axaml(.cs) 程序入口 + 依赖装配(组合根)

│ ├─ Models/InstallTypes.cs 领域模型(record)

│ ├─ Services/ 环境检测、安装链路、下载、配置、教程(接口化)

│ ├─ ViewModels/ MVVM 视图模型

│ └─ Views/ 主窗口、教程窗口

└─ tests/AutoInstall.Tests/ xUnit 单元测试 + Fakes.cs

领域模型都是不可变 record:ProcessResult、ToolInfo、EnvironmentStatus、InstallResult,以及枚举 TutorialTopic。简单、无副作用、天然好测。

02 二、核心源码解析

2.1 ProcessRunner:超时 + 杀进程树

所有外部命令的唯一出口。它把 stdout/stderr 异步重定向并实时回吐到日志,关键在于超时控制:用一个由 timeout 派生的 CancellationTokenSource 与外部 ct 链接,超时即终止整棵进程树。

踩坑 → 修复 winget 拉起的常驻进程导致永久挂起

早期直接 await WaitForExitAsync 等待 stdout EOF。但 winget 会拉起常驻后台进程 WindowsPackageManagerServer,它继承了被重定向的输出句柄且不关闭,导致 EOF 永不到达、进程视为“未退出”,UI 永久卡死。

修复:给等待加超时;超时则 Kill(entireProcessTree: true) 杀掉整棵树并返回退出码 -1,交由上层走镜像兜底。

[C#] src/AutoInstall/Services/ProcessRunner.cs

1 │ usingvar timeoutCts = timeout is { } span ? newCancellationTokenSource(span) : null;

2 │ usingvar linked = timeoutCts isnull

3 │ ? null

4 │ : CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCts.Token);

5 │ var waitToken = linked?.Token ?? ct;

6 │

7 │ try

8 │ {

9 │ await process.WaitForExitAsync(waitToken);

10 │ }

11 │ catch (OperationCanceledException) when (timeoutCts is not null

12 │ && timeoutCts.IsCancellationRequested && !ct.IsCancellationRequested)

13 │ {

14 │ // 超时:终止整棵进程树并返回,避免被子进程残留的输出句柄无限期阻塞。

15 │ TryKillTree(process);

16 │ returnnewProcessResult(-1, stdout.ToString(), stderr.ToString());

17 │ }

注意 when 过滤器精确区分两种取消:超时(吞掉、返回 -1)与外部取消(清理后向上 throw),二者语义不同。

2.2 EnvironmentDetector:探测原语

探测的最小单元是 DetectToolAsync:用 cmd.exe /c--version 执行,退出码 0 即视为已装,版本号取输出首行(去掉 CR、按空行切分)。带 15 秒超时兜底,避免某个工具卡死探测。

[C#] src/AutoInstall/Services/EnvironmentDetector.cs

1 │ publicasyncTask<ToolInfo> DetectToolAsync(string command, string versionArg, CancellationToken ct = default)

2 │ {

3 │ var r = await _runner.RunAsync("cmd.exe", $"/c {command} {versionArg}", null, ct, TimeSpan.FromSeconds(15));

4 │ if (r.ExitCode != 0) returnnewToolInfo(false, null);

5 │

6 │ var version = (r.StdOut ?? string.Empty)

7 │ .Replace("\r", "")

8 │ .Split('\n', StringSplitOptions.RemoveEmptyEntries)

9 │ .FirstOrDefault()?.Trim();

10 │

11 │ returnnewToolInfo(true, string.IsNullOrWhiteSpace(version) ? null : version);

12 │ }

NOTE 并行探测在 ViewModel,而非这里

本类的 DetectAsync 是顺序探测(一次性快照)。界面上“逐项就绪即刷新”的并行探测其实在 MainWindowViewModel.RefreshAsync 里用 Task.WhenAll 实现——见 2.9。两者职责分离:本类只提供探测原语。

2.3 WindowsWingetBootstrapper:确保 winget 可用

封装“确保 winget 存在”:已装直接返回 true;否则下载 VCLibs 依赖包与 App Installer 捆绑包,经 PowerShell Add-AppxPackage 安装后复检。Node 与 git 安装链共用它。

[C#] src/AutoInstall/Services/WindowsWingetBootstrapper.cs

1 │ await _downloader.DownloadAsync("https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx", vclibs, log, ct);

2 │ await _downloader.DownloadAsync("https://aka.ms/getwinget", bundle, log, ct);

3 │

4 │ var safeVclibs = vclibs.Replace("'", "''");

5 │ var safeBundle = bundle.Replace("'", "''");

6 │ var r1 = await _runner.RunAsync("cmd.exe",

7 │ $"/c powershell -NoProfile -ExecutionPolicy Bypass -Command \"Add-AppxPackage -LiteralPath '{safeVclibs}'\"", log, ct);

TIP 路径转义防注入

传给 PowerShell 单引号字符串前,先把路径里的 ' 替换为 ''(safeVclibs),并用 -LiteralPath 避免通配符与命令注入。

2.4 WindowsNodeInstaller:winget → 自装 winget → 镜像/官方 MSI

先用 DetectToolAsync 检测 node + npm,两者都在则直接返回“已安装”。否则先试 winget(OpenJS.NodeJS.LTS,3 分钟超时),失败回退 MSI。MSI 源国内镜像优先:

[C#] src/AutoInstall/Services/WindowsNodeInstaller.cs

1 │ var sources = new (string Name, string IndexUrl, string Prefix)[]

2 │ {

3 │ ("国内镜像(npmmirror)", "https://registry.npmmirror.com/-/binary/node/index.json",

4 │ "https://registry.npmmirror.com/-/binary/node"),

5 │ ("官方(nodejs.org)", "https://nodejs.org/dist/index.json", "https://nodejs.org/dist"),

6 │ };

7 │ // 解析最新 LTS → 拼出 node--x64.msi → 下载 → msiexec /i ... /qn;任一源成功即返回

版本解析由纯函数 NodeVersionResolver.ParseLatestLts 完成:遍历 index.json,取第一个 lts 字段不为 false 的条目即为最新 LTS。两个源 index 与路径格式一致,所以同一套解析逻辑通吃。安装完成后调 RefreshProcessPath() 再复检(见 2.10)。

2.5 WindowsGitInstaller:winget → 清华 TUNA → GitHub

git 是运行期依赖(Claude Code/Codex 都要用)。缺失则 winget(Git.Git,3 分钟超时)→ 失败则下载官方静默安装包,源清华 TUNA 优先、GitHub 兜底:

[C#] src/AutoInstall/Services/WindowsGitInstaller.cs

1 │ var sources = new (string Name, Func<CancellationToken, TaskResolve)[]

2 │ {

3 │ ("国内镜像(清华 TUNA)", ResolveExeUrlFromTunaAsync), // LatestRelease 永远指向最新版

4 │ ("GitHub", ResolveExeUrlFromGitHubAsync),

5 │ };

6 │ // 解析 Git--64-bit.exe → 下载 → /VERYSILENT /NORESTART /SP- /NOCANCEL

•ResolveExeUrlFromTunaAsync:抓取 TUNA 的 LatestRelease 目录 HTML,用正则 Git-[0-9][0-9.]*-64-bit\.exe 提取文件名(GitMirrorResolver)。

•ResolveExeUrlFromGitHubAsync:调 GitHub Releases API,从 assets 里找以 -64-bit.exe 结尾的 browser_download_url(GitReleaseResolver)。

2.6 HttpFileDownloader:断点续传 + 重试

踩坑 → 修复 弱网大文件频繁报 “copying content to a stream”

大陆弱网下载 Node MSI / Git EXE 这类几十 MB 的文件,常在中途断流抛 IOException。一次性下载几乎必失败。

修复:下载到 .part 临时文件;断开后用 HTTP Range 头从断点续传,最多重试 4 次、单次 10 分钟超时;全部完成后原子改名到目标路径。

[C#] src/AutoInstall/Services/HttpFileDownloader.cs

1 │ var existing = File.Exists(partPath) ? newFileInfo(partPath).Length : 0L;

2 │

3 │ usingvar req = newHttpRequestMessage(HttpMethod.Get, url);

4 │ if (existing > 0) req.Headers.Range = newRangeHeaderValue(existing, null);

5 │

6 │ usingvar resp = awaitHttp.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, token);

7 │ // 服务器不支持续传(返回 200 而非 206)→ 删除 .part 从头开始

8 │ if (existing > 0 && resp.StatusCode != HttpStatusCode.PartialContent) { File.Delete(partPath); existing = 0; }

9 │ resp.EnsureSuccessStatusCode();

10 │

11 │ awaitusingvar fs = newFileStream(partPath,

12 │ existing > 0 ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.None);

13 │ await src.CopyToAsync(fs, token);

NOTE 全局超时设为无限,按“单次尝试”限时

共享的 HttpClient.Timeout 设为 InfiniteTimeSpan,否则慢速大文件会被全局超时误杀;真正的时限由每次尝试的 CancelAfter(10min) 控制。外部取消会保留 .part 以便下次续传。

2.7 ConfigFileService:缺失则生成模板

打开配置时,若文件不存在则建目录并写入带占位项的合法模板(JSON / TOML);已存在则绝不覆盖。模板自带 _说明 提示键(会被 Claude Code 忽略)与 TOML 行内 # 注释,引导用户填 URL、密钥与模型。模板全文见第四章。

[C#] src/AutoInstall/Services/ConfigFileService.cs

1 │ privatestatic string EnsureFile(string dir, string fileName, string defaultContent)

2 │ {

3 │ Directory.CreateDirectory(dir);

4 │ var path = Path.Combine(dir, fileName);

5 │ if (!File.Exists(path))

6 │ File.WriteAllText(path, defaultContent);

7 │ return path;

8 │ }

2.8 教程:WebView2Bootstrapper + TutorialService

教程在内嵌 NativeWebView 打开官方文档。启动时后台静默确保 WebView2 运行时:先读注册表 EdgeUpdate\Clients\{...} 的 pv 值判断是否已装,缺失则下载官方 MicrosoftEdgeWebview2Setup.exe 静默安装;任何失败都不阻断启动,教程会回退到系统默认浏览器。

TutorialService 仅做 URL 映射。

2.9 MainWindowViewModel:编排(已装跳过 / 非阻塞 / 并行刷新)

安装命令的编排顺序:确保 Node(失败则中止)→ 确保 git(失败仅告警,不阻断)→ 目标 CLI 已装则跳过、未装才 npm 全局安装 → 刷新状态。每个环节都先检测后动作,绝不重复安装。

[C#] src/AutoInstall/ViewModels/MainWindowViewModel.cs

1 │ var node = await _nodeInstaller.EnsureNodeAsync(log);

2 │ ((IProgress)log).Report(node.Message);

3 │ if (!node.Success) return; // Node 失败 → 中止

4 │

5 │ var git = await _gitInstaller.EnsureGitAsync(log);

6 │ ((IProgress)log).Report(git.Message); // git 失败 → 仅告警,继续

7 │

8 │ var existing = await _detector.DetectToolAsync(toolCommand, "--version");

9 │ if (existing.Installed)

10 │ ((IProgress)log).Report($"{toolCommand} 已安装({existing.Version}),跳过安装。");

11 │ else

12 │ await _packageInstaller.InstallAsync(npmPackage, $"{toolCommand} --version", log);

界面刷新则是并行的——这正是“逐项落定”观感的来源:

[C#] src/AutoInstall/ViewModels/MainWindowViewModel.cs

1 │ awaitTask.WhenAll(

2 │ ProbeAsync("node", v => NodeStatus = v),

3 │ ProbeAsync("npm", v => NpmStatus = v),

4 │ ProbeAsync("winget", v => WingetStatus = v),

5 │ ProbeAsync("claude", v => ClaudeStatus = v),

6 │ ProbeAsync("codex", v => CodexStatus = v),

7 │ ProbeAsync("git", v => GitStatus = v));

状态属性由 [ObservableProperty] 生成、命令由 [RelayCommand] 生成;IsBusy / IsRefreshing 做重入保护,单项探测异常被局部 try/catch 兜住、不影响其余。

2.10 WindowsPathRefresher:让新装工具立即可见

刚装好的 Node/git 会把自己写进系统/用户级 PATH,但当前进程的 PATH 是启动时的快照,感知不到新值,于是复检会误判“仍未安装”。RefreshProcessPath 重新读取 Machine + User 两级 PATH 并合并写回当前进程,安装链路调用它后再复检即可命中。

[C#] src/AutoInstall/Services/WindowsPathRefresher.cs

1 │ var machine = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine) ?? "";

2 │ var user = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User) ?? "";

3 │ Environment.SetEnvironmentVariable("PATH", machine + ";" + user, EnvironmentVariableTarget.Process);

03 三、用户使用说明

3.1 环境要求

•Windows 10 / 11(x64)。

•(仅从源码构建时)需要 .NET 8 SDK;普通使用者直接运行已构建的程序即可。

•以管理员权限运行:应用清单已声明 requireAdministrator,首次启动弹一次 UAC,之后全程无弹窗。

3.2 获取与运行

[Shell] PowerShell / CMD

1 │ git clone https://gitee.com/dreamer_j/auto-install-cc.git

2 │ cd auto-install-cc

3 │ dotnet run --project src/AutoInstall

4 │ # 或:dotnet build -c Release 后运行生成的 AutoInstall.exe

3.3 主界面功能

•状态卡片:启动即并行检测 Node / npm / winget / git / Claude Code / Codex,显示“已安装 版本 / 未安装”。

•一键安装 Claude Code:自动装好 Node、git,再 npm 全局安装 @anthropic-ai/claude-code。

•一键安装 Codex:同上,安装 @openai/codex。

•打开配置:打开 Claude Code / Codex 配置文件,缺失则先生成模板再用系统默认程序打开。

•教程:内嵌网页打开官方文档,WebView2 不可用时回退系统浏览器。

•日志面板:实时显示安装/下载/重试输出,过程完全透明。

3.4 中国大陆网络优化

•Node 优先 npmmirror、git 优先清华 TUNA,失败再回退官方源;

•大文件断点续传 + 自动重试(最多 4 次,单次 10 分钟);

•winget 步骤带 3 分钟超时,卡住即转国内镜像,不会无限等待。

TIP 下载仍失败怎么办

把日志面板的输出复制下来到仓库提 Issue:里面带有命中的源、断点续传进度与重试次数,便于定位是哪个源不可达,也方便我们持续补充新的国内镜像。

04 四、配置文件配置详解

4.1 Claude Code:~/.claude/settings.json

配置项位于 env 块;首次打开时由程序写入下列模板(_说明 键仅作提示、会被忽略):

[JSON] ~/.claude/settings.json

1 │ {

2 │ "_说明": "首次使用请填写 env 下的 ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_MODEL 后保存。",

3 │ "env": {

4 │ "ANTHROPIC_BASE_URL": "在此填写API地址",

5 │ "ANTHROPIC_AUTH_TOKEN": "在此填写你的密钥或Token",

6 │ "ANTHROPIC_MODEL": "在此填写模型名",

7 │ "API_TIMEOUT_MS": "3000000",

8 │ "CLAUDE_CODE_ATTRIBUTION_HEADER": "0"

9 │ }

10 │ }

字段

说明

默认 / 示例

ANTHROPIC_BASE_URL

API 接口地址(官方或中转)

待填写

ANTHROPIC_AUTH_TOKEN

鉴权密钥 / Token

待填写

ANTHROPIC_MODEL

默认模型名

待填写

API_TIMEOUT_MS

API 请求超时(毫秒)

3000000

CLAUDE_CODE_ATTRIBUTION_HEADER

是否附带归属标识头(0 关闭)

0

4.2 Codex:~/.codex/config.toml

首次打开时写入的 TOML 模板(含行内注释,支持自定义服务商):

[TOML] ~/.codex/config.toml

1 │ # Codex 配置模板 —— 首次使用请填写 [model_providers.newapi] 下的 base_url 等后保存。

2 │ # 文档:https://developers.openai.com/codex/cli

3 │

4 │ model = "gpt-5.5"

5 │ model_provider = "newapi"

6 │ model_reasoning_effort = "medium"

7 │ disable_response_storage = true

8 │ multi_agent = true

9 │

10 │ [model_providers.newapi]

11 │ name = "newapi"

12 │ base_url = "https://在此填写你的API地址/v1"

13 │ wire_api = "responses"

14 │ requires_openai_auth = true

15 │ env_key = "OPENAI_API_KEY"

字段

说明

默认 / 示例

model

默认模型名

gpt-5.5

model_provider

使用的提供方

newapi

model_reasoning_effort

推理强度

medium

disable_response_storage

禁用响应存储

true

multi_agent

启用多 Agent

true

name

提供方显示名

newapi

base_url

API 基础地址(通常以 /v1 结尾)

待填写

wire_api

接口协议:chat 或 responses

responses

requires_openai_auth

是否需要 OpenAI 式鉴权

true

env_key

读取密钥的环境变量名

OPENAI_API_KEY

WARNING env_key 指向的是环境变量名,不是密钥本身

Codex 的 env_key = "OPENAI_API_KEY" 表示 Codex 会去读名为 OPENAI_API_KEY 的系统环境变量取密钥。请在系统环境变量里设置它的值,而不是把密钥直接写进 config.toml。

05 五、开源项目地址

•Gitee 仓库:https://gitee.com/dreamer_j/auto-install-cc

•开源许可:MIT License

•作者:Wesky / Dotnet Dancer(公众号)

claude/codex官方文档:

claude code:https://docs.claude.com/en/docs/claude-code/overview

codex:https://developers.openai.com/codex/cli

欢迎 Star、Issue 与 PR。

结尾:创作不易,开源撸码更不容易。如果对你有帮助,欢迎打个赏。当然,小手一抖,点个赞或者喜欢,也不是不可以。

http://www.cnnetsun.cn/news/2634191.html

相关文章:

  • 厌倦了在编辑器、终端和浏览器之间频繁切换?试试这个基于无限画布(类Figma风格)的下一代开源桌面开发环境“Cate”
  • TVA凭什么成为具身机器人的“类人智眼“(3)
  • 费米悖论五层拆解:从德雷克方程到大过滤器,探寻宇宙寂静之谜
  • SketchUp STL插件终极指南:5步掌握3D打印模型导入导出
  • 免费开源AMD Ryzen调试工具:SMUDebugTool完全指南
  • 【Mysql】B+树索引
  • 强化基准精度管理,优化传动设备全生命周期成本
  • 别再乱卸载补丁了!Win10/11共享打印机报错0x0000011b,试试这个注册表一键修复法
  • PPO算法里的GAE到底怎么算?一个PyTorch逆向遍历代码带你彻底搞懂优势估计
  • 别再死磕有限元了!用Python和PyTorch快速上手PINN,搞定偏微分方程反问题
  • 神经形态计算与氧化物界面器件的存算一体技术
  • 信号处理避坑指南:你的Savitzky-Golay滤波器用对了吗?详解阶数、窗长与延迟那些事儿
  • ARMv7-M架构LDM/STM指令中断机制解析
  • 别再只盯着LOF了!盘点5种更高效的异常检测算法(附Python代码与适用场景指南)
  • 别再死记硬背了!用‘悬崖行走’游戏带你直观理解Model-based和Model-free的区别
  • 如何彻底解放你的QQ音乐:qmcdump终极音频解密指南
  • RePKG:解锁Wallpaper Engine壁纸资源的钥匙
  • GIS数据工程师的私藏技巧:用FME的StringSearcher和AttributeCreator玩转OSGB批量重命名与格式转换
  • 从零构建320万参数微型语言模型:拆解Transformer与自注意力机制
  • 用Arduino和5个舵机,我复刻了一台能抓牛奶的并联机械臂(附完整代码与3D文件)
  • 不止于切换:深入龙讯HDMI 2.0矩阵芯片LT86404UX,玩转串口指令与通道管理逻辑
  • ChatGPT时代:从内容通胀到信任重构的思维范式转变
  • 终极游戏手柄兼容性解决方案:ViGEmBus驱动完整指南
  • 别急着重装!NextCloud登录失败的三个隐蔽配置项检查(附Nginx反向代理避坑指南)
  • 别只怪内存小!深入理解Linux OOM Killer与C++编译的‘cc1plus’进程
  • 伯克森悖论:为什么渣男反而更容易追到女生?
  • 告别CentOS7的坑,RHEL8内核升级保姆级教程:从ELRepo配置、清华源加速到grubby设置默认启动项
  • EldenRingFPSUnlockAndMore:3层内存注入架构深度解析与性能优化方案
  • 2026年人形机器人:从技术突破到生态定义|附200+报告、数据PPT合集下载
  • Simulink仿真Boost变换器:从理想模型到非理想参数分析(以MOSFET和二极管为例)