2026-05-19 00:00:00
SugarLite 最初是一个纯 iOS 应用,技术栈是 SwiftUI + MVVM + Supabase Swift SDK。随着 Android 版本的需求提上日程,如果简单地把业务逻辑复制一份到 Kotlin,后续的维护成本会成倍增长——网络层、数据模型、业务规则都要双端维护。所以我们选择了 Kotlin Multiplatform,让 shared/ 模块承载所有跨平台业务逻辑,iOS 侧只保留 SwiftUI 和系统框架调用。
这篇文章记录整个迁移过程,从架构重构到 CI/CD 适配,踩过的坑和学到的经验。
在引入 KMP 之前,我们先做了一次架构重构。核心目标很简单:让 ViewModel 不依赖任何外部框架,这样底层将来换成 Kotlin 也不会波及上层。
重构后的依赖方向:
View → ViewModel → Protocol ← Repository → External Frameworks
各层职责:
| 层级 | 目录 | 允许导入 | 约束 |
|---|---|---|---|
| View | Views/ |
SwiftUI | 禁止直接访问 Repository |
| ViewModel | ViewModels/ |
Foundation | 禁止导入 Supabase/HealthKit/SwiftData |
| Domain | Domain/ |
Foundation | 定义 Protocol、UseCase、Service |
| Data |
Repositories/ + Data/
|
任意框架 | 唯一允许导入 Supabase、SwiftData 的层级 |
这次重构的价值在后来的迁移中体现得非常明显——当 Supabase 客户端从 Swift SDK 换成 KMP shared 模块时,ViewModel 层一行都不用改,因为它只依赖 FoodReferenceRepositoryProtocol 这样的接口。
架构清理完成后,正式引入 KMP。项目根目录变成了这样:
├── iosApp/ # 原 iOS 项目整体迁入
│ ├── BloodSugarApp/ # SwiftUI 源码
│ ├── BloodSugarApp.xcodeproj
│ └── ci_scripts/ # Xcode Cloud 构建脚本
├── composeApp/ # Compose Multiplatform(Android 入口)
├── shared/ # KMP 共享模块
│ ├── src/commonMain/kotlin/ # 跨平台业务逻辑
│ ├── src/androidMain/kotlin/
│ └── src/iosMain/kotlin/
├── gradle/
├── build.gradle.kts
└── settings.gradle.kts
shared/build.gradle.kts 的关键配置:
kotlin {
android { ... }
listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "Shared"
isStatic = true
}
}
sourceSets {
commonMain.dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.datetime)
api(libs.supabase.auth)
api(libs.supabase.postgrest)
api(libs.supabase.storage)
api(libs.ktor.client.core)
implementation(libs.koin.core)
}
androidMain.dependencies {
implementation(libs.ktor.client.cio)
}
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
两个点值得说:
isStatic = true:静态 framework,避免 Xcode Cloud 构建时的动态库签名问题。api(libs.supabase.*):作为 API 依赖暴露,iOS 侧理论上能直接用,但我们会用封装层隐藏细节。这是最核心的一步——把散落在各 Repository 中的 Supabase 调用整体下沉到 shared/src/commonMain/kotlin/。
为所有 13 张表创建 @Serializable 的 Kotlin DTO,统一用 snake_case 字段映射:
@Serializable
data class FoodReferenceDto(
val id: String,
val name: String,
val name_en: String? = null,
val category: String,
val gi_value: Int? = null,
val calories: Double? = null,
// ...
)
每个领域实体对应一个 CloudSource,封装 CRUD 操作:
class FoodReferenceCloudSource(private val client: SupabaseClient) {
suspend fun fetchAll(): List<FoodReferenceDto> {
return client.from("food_reference").select().decodeList()
}
suspend fun fetchPage(
page: Int, pageSize: Int,
searchText: String?, category: String?
): List<FoodReferenceDto> {
// 构建 PostgREST 查询...
}
}
在 sharedModule 中注册所有 CloudSource:
val sharedModule = module {
single { SupabaseClientProvider.client }
single { FoodReferenceCloudSource(get()) }
single { BloodSugarCloudSource(get()) }
// ...
}
KMP 的迁移不需要一次性完成。我们的策略是"新功能直接写 KMP,旧功能逐步替换"。以 FoodReferenceRepository 为例:
先让 Swift 能拿到 Koin 容器里的实例。在 iosMain 中:
fun doInitKoin() {
startKoin { modules(sharedModule) }
}
在 commonMain 中暴露获取方法:
private object KoinProvider : KoinComponent {
fun provideFoodReferenceCloudSource(): FoodReferenceCloudSource = get()
}
fun getFoodReferenceCloudSource(): FoodReferenceCloudSource =
KoinProvider.provideFoodReferenceCloudSource()
Kotlin 的顶层函数会被编译为 Swift 的全局函数,iOS 侧直接调用 CloudSourceProviderKt.getFoodReferenceCloudSource() 即可。
在 SwiftUI 入口的 init() 中:
init() {
KoinHelperKt.doInitKoin()
AppLogger.database.info("KMP Koin 已初始化")
}
改造前(Swift Supabase SDK):
import Supabase
final class FoodReferenceRepository {
private var supabaseClient: SupabaseClient
func fetchAll() async throws -> [FoodReferenceDTO] {
let dtos: [FoodReferenceDTO] = try await supabaseClient
.from("food_reference")
.select()
.execute()
.value
return dtos
}
}
改造后(KMP CloudSource):
import Shared
final class FoodReferenceRepository {
private let cloudSource: FoodReferenceCloudSource
init() {
self.cloudSource = CloudSourceProviderKt.getFoodReferenceCloudSource()
}
func fetchAll() async throws -> [FoodReferenceDTO] {
let kmpDtos = try await cloudSource.fetchAll()
return kmpDtos.map { FoodReferenceDTO(fromKmp: $0) }
}
}
变化就三处:import Supabase → import Shared,直接写 PostgREST 查询 → 调用封装好的 cloudSource.fetchAll(),新增一个 fromKmp 的 DTO 转换扩展。
这个模式可以复制到每一个 Repository,团队按优先级逐个迁移即可。
引入 KMP 后最大的挑战是 CI/CD。Xcode Cloud 的构建环境默认没有 Java 和 Gradle,而 iOS 构建又依赖 Kotlin/Native 编译出的 Shared.framework。
在 Xcode Cloud 的 ci_pre_xcodebuild.sh 阶段安装 Java 和 Gradle:
#!/bin/sh
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT_DIR"
export GRADLE_USER_HOME="$ROOT_DIR/.gradle"
export HOMEBREW_NO_AUTO_UPDATE=1
resolve_java_home() {
if [ -n "${JAVA_HOME:-}" ] && [ -x "$JAVA_HOME/bin/java" ]; then
echo "$JAVA_HOME"
return 0
fi
for version in 21 17 11; do
if JAVA_CANDIDATE=$(/usr/libexec/java_home -v "$version" 2>/dev/null); then
if [ -x "$JAVA_CANDIDATE/bin/java" ]; then
echo "$JAVA_CANDIDATE"
return 0
fi
fi
done
if command -v brew >/dev/null 2>&1; then
for formula in openjdk@17 openjdk; do
if brew list --versions "$formula" >/dev/null 2>&1; then
JAVA_CANDIDATE="$(brew --prefix "$formula")/libexec/openjdk.jdk/Contents/Home"
if [ -x "$JAVA_CANDIDATE/bin/java" ]; then
echo "$JAVA_CANDIDATE"
return 0
fi
fi
done
fi
return 1
}
ensure_java() {
if JAVA_CANDIDATE="$(resolve_java_home)"; then
export JAVA_HOME="$JAVA_CANDIDATE"
else
echo "[Xcode Cloud] Java not found. Installing openjdk@17 via Homebrew..."
brew install openjdk@17
export JAVA_HOME="$(brew --prefix openjdk@17)/libexec/openjdk.jdk/Contents/Home"
fi
export PATH="$JAVA_HOME/bin:$PATH"
}
ensure_java
echo "[Xcode Cloud] JAVA_HOME=$JAVA_HOME"
java -version
./gradlew --version
在 Xcode Build Phases 中添加 Run Script:
#!/bin/sh
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
cd "$ROOT_DIR"
# Xcode Preview 优化:跳过 Gradle 构建
if [ "${OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED:-}" = "YES" ]; then
echo "Skipping Gradle build due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED=YES"
exit 0
fi
# 解析 JAVA_HOME(与 ci_pre_xcodebuild.sh 相同逻辑)
# ...
export PATH="$JAVA_HOME/bin:$PATH"
./gradlew :shared:embedAndSignAppleFrameworkForXcode
这个脚本在每次 Xcode 构建时调用 Gradle 任务来编译 Kotlin/Native 并嵌入 framework。本地开发时通过 OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED 环境变量做了优化——Xcode Preview 触发构建时跳过 Gradle,避免拖慢速度。
Sentry 脚本与 Widget Extension 的构建循环依赖:错误信息很诡异,排查后发现是 Sentry 的 “Upload Debug Symbols” 脚本和 Widget Extension 的 Embed 阶段之间存在隐式依赖。解决方案很简单——把 Sentry 脚本拖到 Embed 阶段之后。
Java Runtime 解析不稳定:Xcode Cloud 各版本镜像上 Java 安装路径不统一。最终的 resolve_java_home() 用了多级 fallback:系统 JAVA_HOME → /usr/libexec/java_home → Homebrew → brew list。这套冗余查找策略确保了在任何镜像版本上都能找到 Java。
回头看整个过程,我们遵循了几个原则:
@Observable 维持 SwiftUI 响应式体验。本文基于 SugarLite 项目的真实迁移过程撰写。
2026-05-18 09:00:00
前面聊过 cc switch 的基础用法,今天单独说一下 CC Switch 里 Claude Desktop 面板的配置。在 3.15.0 中加入的功能,专门用来给 Claude Desktop 这个官方桌面客户端接入第三方供应商。
CC Switch 中的「Claude」和「Claude Desktop」是两个不同的面板,分别对应 Claude Code(CLI)和 Claude Desktop(桌面客户端)。区别在于 Claude Desktop 用的是自己的 3P profile 配置,而不是
~/.claude/settings.json,两个面板在图标右下角会有小标记区分。
Claude Desktop 面板能做的事情很简单:
支持 macOS 和 Windows。Linux 暂时不能写入 Claude Desktop 的 3P 配置。切换供应商后需要重启 Claude Desktop 才能生效,它不会像 Claude Code 那样热加载。
另外有一点需要注意:Claude Desktop 的 3P profile 不走 CC Switch 的 MCP / Skills 同步,这些功能在桌面端是独立管理的。
左侧应用切换器里选择 Claude Desktop。

如果没看到入口,去 设置 → 通用 → 应用可见性 里确认没有被隐藏。
大部分用户是先在 Claude Code 里配好了一堆供应商,然后才想把这些配置带到 Claude Desktop 里。第一次进入 Claude Desktop 面板时,如果这里还没有供应商,直接点 将 Claude Code 中已有的供应商导入 就行。

这个功能会帮你把 Claude Code 那边的供应商配置一键搬过来,不用重新填接口地址、API Key 和默认模型。导入逻辑大概是:
ANTHROPIC_DEFAULT_SONNET_MODEL、ANTHROPIC_DEFAULT_OPUS_MODEL、ANTHROPIC_DEFAULT_HAIKU_MODEL 会自动转成 Desktop 的 Sonnet / Opus / Haiku 映射[1M] 后缀会转成 Desktop profile 中的 supports1m 标记导入之后建议逐个检查一下模型映射是否正确,尤其是 Kimi、DeepSeek、GLM、DouBao 这类非 Claude 模型,通常需要用模型映射模式。
如果没有可导入的配置,或者想专门给 Claude Desktop 加一个不同的供应商,点右上角 + 添加即可。
三种选择:
对于普通的 Anthropic Messages API 兼容供应商,流程很简单:选预设或自定义 → 填 API Key → 确认接口地址 → 关掉「需要模型映射」→ 添加。
在供应商卡片上点「启用」,然后完全退出并重启 Claude Desktop。
注意:Claude Desktop 不会热重载配置,只关聊天窗口不够,要从托盘里退出或者确保进程完全结束。
适合供应商本身提供了原生 Anthropic Messages API 的情况。CC Switch 会把 Claude Desktop 的 3P profile 直接指到供应商接口:
{
"inferenceProvider": "gateway",
"inferenceGatewayBaseUrl": "https://api.example.com",
"inferenceGatewayAuthScheme": "bearer",
"inferenceGatewayApiKey": "你的 API Key"
}
适用条件:供应商暴露的是原生 Anthropic Messages API,模型 ID 是 claude-* 或 anthropic/claude-* 格式,不需要格式转换。直连模式下不需要 CC Switch 一直开着本地路由。
「手动指定 Claude Desktop 模型列表」是个高级选项,大部分原生 Claude 模型供应商不需要填,Claude Desktop 会自动读 /v1/models。只有供应商的 /v1/models 不可用,或者返回的模型名 Claude Desktop 不认识时才需要手动添加。
如果供应商不是 Claude 系列模型(比如 deepseek、kimi 等),或者接口格式需要 CC Switch 做转换,就要开启「需要模型映射」。
开启后,Claude Desktop 会连到 CC Switch 的本地网关:
http://127.0.0.1:15721/claude-desktop
CC Switch 在中间负责:向 Claude Desktop 暴露安全的 claude-* 模型路由,把 Desktop 选的模型角色映射到真实上游模型,按供应商要求做 Anthropic / OpenAI / Gemini 格式转换,并用 CC Switch 里存的凭据访问上游。
支持的格式:
| 格式 | 用途 |
|---|---|
| Anthropic Messages | 原生或兼容 Anthropic 请求 |
| OpenAI Chat Completions |
/chat/completions 兼容接口 |
| OpenAI Responses API | OpenAI Responses 兼容接口 |
| Gemini Native generateContent | Gemini 原生接口 |
模型映射模式下,Claude Desktop 只能看到 claude-* 形式的路由模型名,真实的上游模型名不会写进 Claude Desktop profile。
模型映射的核心思路是:Claude Desktop 现在会拒绝非 claude-* 的模型名,所以需要通过 CC Switch 做一轮角色映射。
| 字段 | 说明 |
|---|---|
| 模型角色 | Claude Desktop 可识别的 Sonnet / Opus / Haiku 路由 |
| 菜单显示名 | 在 Claude Desktop 模型菜单里显示的名称 |
| 实际请求模型 | 发给上游供应商的真实模型 ID |
| 1M | 向 Claude Desktop 声明该模型支持 1M 上下文 |
举个例子,如果想在 Claude Desktop 里用 Kimi:
| 模型角色 | 菜单显示名 | 实际请求模型 | 1M |
|---|---|---|---|
| Sonnet | Kimi K2 | kimi-k2 |
按供应商能力选择 |
想用 DeepSeek:
| 模型角色 | 菜单显示名 | 实际请求模型 | 1M |
|---|---|---|---|
| Sonnet | DeepSeek V4 Pro | deepseek-v4-pro |
按供应商能力选择 |
三个角色的建议分工:
| 模型角色 | 用途 |
|---|---|
| Sonnet | 默认主力模型 |
| Opus | 高质量或复杂任务 |
| Haiku | 快速、低成本的场景 |
如果供应商只有一个模型,只配一个 Sonnet 也行。模型映射模式至少需要一条有效映射。
模型映射模式必须依赖 CC Switch 的本地路由来做请求转换。这个开关默认在主界面是隐藏的,需要手动打开。
去 设置 → 路由 → 本地路由,开启 在主页面显示本地路由开关。
打开后回到 Claude Desktop 面板,右上角就能看到本地路由开关了。

状态含义:
| 状态 | 说明 |
|---|---|
| 开启 | 本地网关运行中,地址是 127.0.0.1:15721
|
| 关闭 | 直连供应商仍可用,模型映射供应商无法工作 |
| 正在加载 | 路由服务在启动或停止中 |
只有开了「需要模型映射」的供应商才需要本地路由,直连的不用管。
如果其他应用正在用代理接管,关本地路由可能会被阻止。先去设置里的路由服务区域关掉对应应用接管,再停本地路由。
想切回官方登录模式的话:
CC Switch 会恢复 Claude Desktop 的官方 1P 模式,把 3P profile 清理掉。官方模式不需要 API Key,也不需要本地路由。
从 Claude Code 导入供应商时,会自动加一条 Claude Desktop Official,方便随时切回去。
CC Switch 写入的是 Claude Desktop 的 3P 配置目录:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.json
~/Library/Application Support/Claude-3p/claude_desktop_config.json
~/Library/Application Support/Claude-3p/configLibrary/_meta.json
~/Library/Application Support/Claude-3p/configLibrary/00000000-0000-4000-8000-000000157210.json
Windows:
%LOCALAPPDATA%\Claude\claude_desktop_config.json
%LOCALAPPDATA%\Claude-3p\claude_desktop_config.json
%LOCALAPPDATA%\Claude-3p\configLibrary\_meta.json
%LOCALAPPDATA%\Claude-3p\configLibrary\00000000-0000-4000-8000-000000157210.json
配置文件由 CC Switch 自动维护,不建议手动改。如果出现配置不一致,重新启用一次当前供应商一般就能修好。
CC Switch 的 Claude Desktop 面板本质上就是把原本只对 CLI 工具开放的供应商管理能力搬到了桌面端。对于已经在 CC Switch 里配置了一堆供应商的用户来说,从 Claude Code 一键导入是最快的路径。
直连模式省心,模型映射模式灵活。如果主力用 Claude 系列模型,直连就够了;如果想在 Claude Desktop 里用 Kimi、DeepSeek 等模型,模型映射模式配合本地路由就能搞定。
2026-04-29 14:55:00
在 AI 编程工具的世界里,Claude Code 无疑是当前最强大的选择之一。然而,随着各大云服务商纷纷推出自己的大语言模型 API,如何方便地在不同供应商之间切换,成为了一个实际的需求。今天就给大家介绍两款工具:cc switch 和 cc desktop switch,帮助你快速切换 Claude Code 的模型供应商。
项目地址:
- cc switch: farion1231/cc-switch (54.9k ⭐)
- cc desktop switch: lonr-6/cc-desktop-switch
cc switch 是一款跨平台的桌面应用,专门用于管理 Claude Code、Codex、Gemini CLI、OpenCode 和 OpenClaw 这五款主流 AI 编程 CLI 工具的供应商配置。
# 通过 Homebrew 安装(推荐)
brew tap farion1231/ccswitch
brew install --cask cc-switch
# 更新
brew upgrade --cask cc-switch
或者从 Releases 页面下载 CC-Switch-v{version}-macOS.dmg。
从 Releases 页面下载:
CC-Switch-v{version}-Windows.msi 安装版CC-Switch-v{version}-Windows-Portable.zip 便携版# Arch Linux
paru -S cc-switch-bin
# 或从 Releases 下载 deb/rpm/AppImage
cc desktop switch 是一款专注于 Claude Desktop 官方桌面客户端 的轻量级配置工具。它和 cc switch 的定位不同:cc switch 主要面向 CLI 用户,而 cc desktop switch 专注于桌面版 Claude Desktop。
注意:只关闭聊天窗口通常不够,请在任务栏托盘里退出 Claude,或在任务管理器里确认没有残留的 Claude 进程。
笔者使用 cc switch 已经有一段时间了,整体体验非常不错。以下是我的一些使用感受:
cc switch 和 cc desktop switch 为 Claude Code 用户提供了便捷的供应商管理方案。两者的定位略有不同:
无论你是需要测试不同模型的效果,还是想要找到性价比最高的供应商,这些工具都能满足你的需求。如果你正在使用 Claude Code,不妨试试这些工具,相信它们会给你带来不错的体验。
如果你对 AI 编程工具感兴趣,也可以看看我之前的文章:Neovim 入门指南系列,里面有很多关于编辑器配置和 AI 集成的相关内容。
2025-12-08 09:24:46
如何脱离 xcode 开发 iOS 是很多人在探索的方案。毕竟 xcode 编辑体验实在是太差了。
这里说的是脱离 xcode,而不是脱离 macOS。
这是今天要安利的工具 SweetPad。SweetPad 是 vscode 上的一个插件,插件可以实现在 vscode 中进行自动补全、调试、编译和运行、格式化、测试等功能。常见的开发场景中基本可以脱离 xcode 来使用。
2025-02-15 13:45:30
在使用 Neovim 之前,我也花费了大量的时间进行配置自己的 Neovim 体系,无论是插件,快捷键还是 UI,都花费了大量的心思。 但是维护一套自己的配置说实话很费时间,例如插件的更新,版本升级等。
LazyVim 是一款已经 集成了大量插件的 Neovim 软件。 基本做到了开箱即用,方便快捷,大量的默认配置减少了上手时间,唯一的缺点可能就是快捷键不是自己熟悉的那套,需要修改或者适应。
LazyVim 的安装其实很简单,首先打开官方的启动模板 LazyVim/starter。 点击右上角 Use this template ,将模板 fork 成为自己的仓库。 接下来就是备份本地原有的配置,将自己的仓库 clone 下来。
简单来说就是一个命令进行备份 另外一个命令进行 clone。 这部分可以直接参考官方网站对于安装的介绍文档。
安装完成之后,启动就是一个已经有了大量基础配置的 neovim 了,基本做到了开箱即用。
完成 LazyVim 的配置,记得看一下是否系统安装了 fzf。
在 LazyVim 的项目中,配置可以分成两大块:基础配置(config) 和 插件(plugins)。
config 配置主要是一些基础的配置,文件夹内区分了四个文件, 分别是基础配置(options.lua),快捷键配置(keymaps.lua),自动化命令(autocmds.lua)和 lazy 初始化配置。
默认 LazyVim 已经集成了很多插件,有的已经默认开启,有的需要手动开启。如果需要覆盖原有的插件配置或者安装新的插件, 可以在该文件夹下直接填写相关配置,最后在外层的 init.lua 中添加即可。
LazyVim 已经集成了大量的插件,并且默认了很多快捷键。默认的 Leader 为 空格。可以针对自己的习惯修改快捷键,也可以直接按下 Leader 来查看快捷键。

默认的快捷键我认为是比较合理,而且使用几次基本也会记住,这里我除了修改了一下 blink 候选提示的上下选择之后,并没有做其他修改。 默认的快捷键也可以在官方文档中找到 keymaps
LazyVim 一些默认的插件已经启用,但是除了默认插件,还有一些其他插件可以根据具体的情况按需使用。
通过 :LazyExtras 命令可以查看其他扩展插件。如果有自己使用的,需要的可以直接通过 x 进行启用。
需要注意的是启用后需要重新启动一些 neovim。
LazyVim 中自带了一些配置,例如 tab 为 2,在一些缩进比较多语言中,tab 为 2是比较友好的,例如 lua,但是对于一些项目,尤其是很团队合作的项目, tab 改成2 就会让代码一团乱。
在 options.lua 中新增一些 tab 相关的配置。
local opt = vim.opt
opt.expandtab = true
opt.tabstop = 4
opt.shiftwidth = 4
opt.softtabstop = 4
同时为了让 lua 还保持 tab 为2的缩进,在 autocmds.lua 中新增自动化命令。
vim.api.nvim_create_autocmd("FileType", {
pattern = { "lua" },
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.tabstop = 2
vim.opt_local.shiftwidth = 2
vim.opt_local.softtabstop = 2
end,
})
还有一个就是中文拼写问题,默认的 spell 一直检查,尤其是在进行 markdown 的时候,大量的波浪线导致编辑的感官太差,可以新增命令来解决。
vim.api.nvim_create_autocmd("FileType", {
pattern = { "markdown", "txt" },
callback = function()
vim.opt_local.spell = false
end,
})
![]()
LazyVim 对 vscode 支持比较好,并且 vscode-neovim 也推荐使用 lazyvim。通过这个插件,可以让我们在 Vscode 中加载一些 Neovim 的插件,做到一个配置两处使用。
首先需要在 LazyExtras 中打开 Vscode 。 然后在 Vscode 中安装 Vscode Neovim 插件。
在 LazyVim 中可以通过 vim.g.vscode 来判断当前的运行环境。
首先是对快捷键进行配置,比如常用的重命名、格式化等。
local map = vim.keymap.set
if vim.g.vscode then
map("n", "<leader>cf", "<Cmd>lua require('vscode').call('editor.action.formatDocument')<CR>", { desc = "Format" })
map("v", "<leader>cf", "<Cmd>lua require('vscode').call('editor.action.formatSelection')<CR>", { desc = "Format" })
map("n", "<leader>cr", "<Cmd>lua require('vscode').call('editor.action.rename')<CR>", { desc = "Rename" })
end
针对 Golang 开发,首先就是语言的相关配置:
Mason 安装 gopls;LazyExtras 进行安装 test.core;Mason 安装 dlv。安装完成就可以进行编辑,运行,测试和调试。
可以通过 <Leader>tr 进行测试运行。

通过 <Leader>db 进行打断点。
通过 <Leader>dc 进行 Debug 运行。

2024-03-25 13:45:30
在分布式系统中最重要的一条理论为 CAP 理论。这个理论是由加州大学伯克利分校的计算机科学家 Eric Allen Brewer 在 2000 年提出的一个猜想,由 2002 年,麻省理工两位科学家发表了该猜想的证明,使得该猜想变成了一个定理。
CAP 定理中对分布式系统提出了三点,分别为:

例如当前系统有两个 DB 分别为 DB0 和 DB1。
一致性是指在数据发生变化的时候(也就是写操作发生后),无论是谁获取到的数据(也就是读操作)也是一样的。
当用户1 通过写操作对 DB0 的数据进行修改后,那么无论用户1 还是 用户2 ,无论从 DB0 还是 DB1 读取,读取后的数据都应该是完全一样的,这就是所谓的一致性。
也就是 DB0 的数据发生了修改,应该由相关的机制告诉 DB1 也将相关的数据发生修改,保证该数据在不同的 DB 中是一样的。
当用户发出相关请求,无论 DB0 还是 DB1 都会返回相关的数据,但是这里不需要关心数据是否一致。
DB0 和 DB1 例如出现了问题,这个问题可能是网络问题,也有可能是其他硬件问题,导致了 DB0 和 DB1 的系统无法通信。这样 DB0 和 DB1 就成为了两个分区。即使 DB0 和 DB1 无法进行通信,但是 DB0 和 DB1 仍旧可以对外提供服务。
但是这种情况,在实际系统中无法避免这种情况,所以分区容错性是一个必选的条件。
既然 CAP 三条规则无法同时满足,那么就出现了上图中的三种情况,满足任意两条规则,也就是 CA,CP 和 AP 三种架构,但是分区容错性是必选的,这样我们就剩下 CP 和 AP 两种关系。
常见的 CP 软件有 Zookeeper,Zookeeper 为了保证数据的一致性,牺牲了可用性。任何时候 Zookeeper 的访问都能获取一致的结果,但是不保证每次服务请求都可以用。
而 AP 架构中,要求数据一致性并不是那么重要,允许不同的服务可以返回不同的数据。
CAP 理论并不是完美的,存在很多问题。例如 DB0 和 DB1 要保持数据的一致性,那么就会发生相关通信,通信是需要时间,这就导致了某些时刻数据是不同步的,常见的情况在主从的机器上的主从延迟,当延迟过大的时候,用户读取的数据是不一致的。
CAP 理论也并不完全是三选二(或者说二选一)的问题,例如分区容错性发生的概论很小,那么就没必要牺牲了 A 和 C。
BASE 理论算是 CAP 理论的延伸,是对 CAP 理论中一致性和可用性的权衡。在 CAP 中,所谓的一致性是指数据时时刻刻的都保持一致,也就是强一致性。上文中 CAP 的不足也说到,要保证时时刻刻数据的一致性是一件很困难的事情。而 BASE 理论就是对改问题的补充,既然很难做到强一致性,那么系统根据自身的业务特点,确保系统中的数据保证最终一致性也是可以的。
BASE 理论是 ebay 工程师 Dan Pritchett
BASE 是指 Basically Available, Soft State 和 Eventually Consistent 三个短语的缩写。
基本可用指的是系统出现故障后,但是还可以使用,但是可能比较正常系统上可能出现一些问题,例如响应时间上,服务降级牺牲部分功能等。
软状态指的是系统数据允许出现中间状态,例如数据库主从同步过程中会出现中间状态,这就是一种软状态。
最终一致性强调经过上述的软状态后,最后数据保持一致性。
BASE 理论的提出是通过牺牲系统的强一致性来保证系统的可用性,允许系统在一段时间内的数据是不一致的,但是要求最终数据的一致性。