DeepSeek Status Bar | Deepseek余额小工具

DeepSeek Status Bar

README_EN Platform Swift License GitHub Stars

DeepSeek Status Bar 是一款轻量级 macOS 菜单栏应用,无需打开浏览器即可随时查看 DeepSeek API 账户余额。零依赖、零配置、开箱即用。


目录


为什么需要它

在使用 DeepSeek API 进行开发时,余额用尽是常见痛点——它通常发生在你最不想停下来的时刻。每次查余额都要登录网页、点进控制台,流程繁琐。

这个菜单栏小工具把余额直接放在你眼前。看一眼菜单栏就够了。

功能

  • 菜单栏实时显示 — 币种、总余额、赠送余额、充值余额一目了然
  • 自动刷新 — 可配置 60–3600 秒轮询间隔,默认 30 分钟
  • 钥匙串存储 — API Key 写入 macOS Keychain,每次按需读取,绝不在内存中缓存
  • 中英双语 — 支持英文和简体中文,跟随系统语言自动切换
  • 零依赖 — 纯 SwiftUI + Foundation + Combine + Security 框架,无需 SPM / CocoaPods / Carthage
  • 沙盒安全 — 启用 App Sandbox,仅开放外网访问

快速开始

# 1. 从 Releases 下载最新 .dmg
# 2. 拖入 /Applications,启动
# 3. 菜单栏 → 设置 → 粘贴 API Key → 应用

完成。余额即刻出现在菜单栏上。

安装

下载安装(推荐)

Releases 页面下载最新 .dmg,拖入 /Applications 文件夹后启动即可。

首次启动时,macOS 可能弹出安全提示。在「系统设置 → 隐私与安全性」中允许运行即可。

从源码构建

git clone https://github.com/Iristack/deepseek-statusbar.git
cd DeepSeekStatusBar
xcodebuild -project DeepSeekStatusBar.xcodeproj -scheme DeepSeekStatusBar build

构建产物路径:DerivedData/.../Build/Products/Debug/DeepSeekStatusBar.app,移至 /Applications 或直接运行。

无需安装任何外部依赖,项目完全自包含。

使用方法

步骤 操作
1 启动应用,菜单栏出现余额信息
2 首次启动显示 「请设置 API Key」
3 点击菜单栏图标 → 设置… → 粘贴你的 DeepSeek API Key → 点击 应用
4 应用立即拉取余额,之后按配置的间隔自动刷新

菜单栏面板

项目 说明 快捷键
余额行 币种、总余额、赠送余额、充值余额
手动刷新 立即拉取最新余额
设置… 打开设置窗口 ⌘,
退出 退出应用 ⌘Q

设置选项

分类 说明
账户 输入或更新 API Key(写入 Keychain)
刷新间隔 60–3600 秒,默认 1800(30 分钟)

点击 「应用」 之前所有修改仅为本地草稿——点击后才会写入 Keychain 并重置定时器。

常见问题

支持 macOS 14 或更低版本吗? 目前最低支持 macOS 26.0。`MenuBarExtra` 是 macOS 26 引入的 SwiftUI API,在旧版本中不可用。未来可能考虑用 `NSStatusBar` 兼容旧系统。
API Key 安全吗? 安全。Key 存储在 macOS Keychain 中(`kSecClassGenericPassword`,`kSecAttrAccessibleAfterFirstUnlock`),每次刷新时按需读取,不会持久保存在 ViewModel 属性或内存缓存中。详见 [隐私与安全](#隐私与安全)。
多久刷新一次合适? 默认 30 分钟对大多数场景足够。高频调用时建议设为 600 秒(10 分钟)。最短间隔为 60 秒,请注意 DeepSeek API 的速率限制。
换了 API Key 后数据不更新? 在设置中粘贴新的 API Key 后,务必点击 **「应用」** 按钮。仅粘贴而不点击应用不会生效——这是草稿-应用模式的设计。
网络断了会怎样? 网络错误不会清空已有的余额数据。菜单栏中过期的数据总好过没有数据。网络恢复后下次定时刷新会自动更新。
如何彻底卸载? 将 `DeepSeekStatusBar.app` 拖入废纸篓。API Key 存储在 Keychain 中,如需一并清除,打开「钥匙串访问」搜索 `xin.iristack.deepseek.status.bar` 并删除。

架构概览

MenuBarExtra  →  BillingViewModel  →  BillingService  →  DeepSeek API
    (App)          @MainActor              (struct)       /user/balance
                  @Published 状态          URLSession
                       │
                       ▼
                KeychainService
                    (enum)

MVVM 架构,共 6 个源文件:

文件 职责
DeepSeekModels.swift API 响应模型 — BalanceResponse + BalanceInfo(Decodable)
KeychainService.swift 通过 Security 框架封装 Keychain 增删查
BillingService.swift HTTP 客户端 — GET /user/balance,Bearer 认证,单例 URLSession
BillingViewModel.swift @MainActor ObservableObject — 状态管理、定时器、一次性数据迁移
SettingsView.swift 设置界面 — 草稿-应用模式,SecureField 输入
DeepSeekStatusBarApp.swift @main 入口 — MenuBarExtra + Settings 场景

更多技术细节见 CLAUDE.md(面向 AI 助手的开发文档)。

隐私与安全

  • API Key 仅存储在 macOS KeychainkSecClassGenericPasswordkSecAttrAccessibleAfterFirstUnlock
  • 每次刷新按需读取 Key,不持久化在任何属性或内存缓存中
  • 仅发起一个外网请求:GET https://api.deepseek.com/user/balance(Bearer 认证)
  • 零数据采集:无埋点、无遥测、无第三方 SDK
  • App Sandbox 已启用,仅开放外网访问权限

本地化

共 26 条本地化字符串,源语言为英文,已翻译为简体中文(zh-Hans)。欢迎贡献更多语言。

贡献

欢迎提 Issue 和 Pull Request!

  • 新功能请先开 Issue 讨论方案
  • Bug 修复可直接提 PR
  • 翻译贡献请参考 Localizable.xcstrings

许可证

MIT License — 自由使用、修改、分发。


Platform

Security Group Manager (安全组管理器)

github

一个轻量级的守护进程,用于监控主机的公网 IPv4 地址,并在地址发生变化时自动同步阿里云 ECS 安全组规则。

特性

  • 定时轮询:以可配置的时间间隔轮询公网 IP 并检测变化。
  • 批量同步:通过单个阿里云 API 调用批量同步规则(每个方向一次)。
  • 灵活策略:支持每条规则配置 allow (允许) 和 drop (拒绝) 策略。
  • 双向覆盖:覆盖 ingress (入站)、egress (出站) 和 all (双向同时) 的规则组。
  • 自动清理:自动移除配置中已不存在的、由 SGM 管理的过期规则。
  • 插件化架构:基于插件的 IP 检测机制——内置两个插件,易于扩展。
  • 环境变量覆盖:支持通过环境变量覆盖凭证(适合 CI/CD 和容器环境)。
  • 历史记录:基于 SQLite 的 IP 变更历史记录,并提供 CLI 查看器。
  • 日志轮转:支持日志文件轮转,并针对不同平台使用合适的路径。

系统要求

  • Python 3.11 或更高版本
  • uv (推荐) 或 pip
  • dig 命令需在 PATH 环境变量中可用(仅 DigFromMyIpOpenDns 插件需要)

安装

使用 uv 从源码安装

git clone https://github.com/your-org/SecurityGroupManager.git
cd SecurityGroupManager
uv sync

作为包安装

uv pip install .

安装后,sgm 命令即可在你的环境中使用。

配置

复制或编辑 cfg/sgm.yaml 文件:

plugins:
  ipv4:
    - DigFromMyIpOpenDns   # 按顺序尝试插件;第一个有效的 IPv4 获胜
    - SocketTrick

interval: 60              # 轮询间隔(秒),范围 5 - 86400

aliyun:
  ak: YOUR_ACCESS_KEY_ID
  sk: YOUR_ACCESS_KEY_SECRET

securityGroup:
  - id: sg-xxxxxxxxxxxxxxxx
    region: cn-hangzhou
    ingress:
      allow:
        - proto: tcp
          port: 22
        - proto: tcp
          port: 443
      drop:
        - proto: tcp
          port: 8080
    egress:
      allow:
        - proto: tcp
          port: 80
    all:                  # 同时应用于入站和出站
      allow:
        - proto: icmp
          port: 1

方向键说明

键名 应用范围
ingress 仅入站规则
egress 仅出站规则
all 同时应用于入站和出站

策略键说明

键名 阿里云策略
allow accept
drop drop

规则识别

SGM 创建的每条规则都会被打上 description: "SGM auto managed" 的标签。 没有此描述的规则永远不会被修改。

环境变量覆盖

凭证和时间间隔可以通过环境变量提供,其优先级高于 YAML 文件中的值。

变量名 配置路径
SGM_ALIYUN_AK aliyun.ak
SGM_ALIYUN_SK aliyun.sk
SGM_INTERVAL interval

示例:

export SGM_ALIYUN_AK=LTAI5xxxxxx
export SGM_ALIYUN_SK=xxxxxxxxxxxxxxxxxxxxxx
sgm run

CLI 使用方法

sgm [OPTIONS] COMMAND [ARGS]

Commands:
  run      启动 IP 监控守护进程
  check    执行单次 IP 检查并显示结果
  history  显示 IP 变更历史

run

# 使用默认配置启动守护进程
sgm run

# 使用自定义配置文件
sgm run -c /etc/sgm/sgm.yaml

# 运行一次后退出 (适合 cron/systemd oneshot)
sgm run --once

# 启用调试日志
sgm run -v

check

# 打印当前公网 IP 并在变更时同步
sgm check

history

# 显示最近 10 次 IP 变更 (默认)
sgm history

# 显示最近 50 次变更
sgm history -n 50

version

sgm --version

日志文件

日志文件会在达到 10 MB 时轮转,最多保留 5 个备份文件。

平台 路径
Linux /var/log/sgm/sgm.log
macOS ~/Library/Logs/sgm/sgm.log
Windows %USERPROFILE%\AppData\Local\sgm\sgm.log

同步逻辑

每次 IP 变更时,针对每个安全组,SGM 最多执行四个 API 调用:

  1. 批量撤销旧 IP 的入站规则。
  2. 批量撤销旧 IP 的出站规则。
  3. 批量授权新 IP 的入站规则。
  4. 批量授权新 IP 的出站规则。
  5. 查询当前规则,并批量撤销当前配置中不存在的、由 SGM 管理的残留规则(过期规则清理)。

内置插件

DigFromMyIpOpenDns

通过 dig 命令向 OpenDNS 解析器查询 myip.opendns.com。 需要系统安装 dig 二进制文件(dnsutilsbind-tools 软件包)。

SocketTrick

8.8.8.8:80 打开一个 UDP 套接字,并读取操作系统选择的本地地址。 无需任何外部命令,但返回的是本地路由 IP 而非实际公网 IP(在大多数无 NAT 层的设置中是准确的)。

编写自定义插件

  1. plugins/ 目录下创建一个 Python 文件。
  2. 继承 IpFetcherPlugin 类并实现 namefetch 方法。
  3. 将类名添加到 sgm.yamlplugins.ipv4 列表中。
# plugins/my_plugin.py
import urllib.request
from ip_fetcher_plugin import IpFetcherPlugin

class MyPlugin(IpFetcherPlugin):

    @property
    def name(self) -> str:
        return "MyPlugin"

    def fetch(self) -> str | None:
        try:
            with urllib.request.urlopen("https://api4.ipify.org", timeout=5) as r:
                return r.read().decode().strip()
        except Exception as e:
            self.logger.error("MyPlugin error: %s", e)
            return None
plugins:
  ipv4:
    - MyPlugin
    - DigFromMyIpOpenDns   # 备用方案

插件按顺序尝试;第一个返回有效 IPv4 地址的插件获胜。

作为 systemd 服务运行

服务文件 (/etc/systemd/system/sgm.service):

[Unit]
Description=Security Group Manager
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/sgm/env
ExecStart=/usr/local/bin/sgm run -c /etc/sgm/sgm.yaml
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

环境变量文件 (/etc/sgm/env):

SGM_ALIYUN_AK=LTAI5xxxxxx
SGM_ALIYUN_SK=xxxxxxxxxxxxxxxxxxxxxx

开发

# 安装开发依赖
uv sync --extra dev

# 运行测试
uv run pytest

# 代码检查
uv run ruff check .

# 类型检查
uv run mypy .

许可证

MIT

给Golang/TypeScript工程师的Dart语法指南

1. 基础语法与变量

变量声明

var name = 'Dart';          // 类型推导为 String
String name = 'Dart';       // 显式类型
final name = 'Dart';        // 运行时常量 (类似 Go 的 const,但值可以运行时确定)
const name = 'Dart';        // 编译时常量 (类似 Go 的常量,只能赋编译时常量值)
late String name;           // 延迟初始化,类似 TS 的 ! 断言或 Go 里先声明后赋值
dynamic anything = 'hello'; // 动态类型,类似 TS 的 any
Object anything = 'hello';  // 所有类的基类,类似 TS 的 unknown

与 Go 的差异

  • Go 用 := 短声明,Dart 用 var 或直接类型标注。
  • Go 的 const 只能是编译时常量(数字、字符串、布尔),Dart 的 const 同理,但 final 可以运行时赋值一次,这是 Go 没有的概念。
  • Dart 有 late 关键字,解决非空延迟赋值,Go 没有对应特性(靠指针默认零值)。

与 TS 的差异

  • TS 的 letconst 是变量绑定不可变,值本身可变。Dart 的 finalconst 才是真正不可重新赋值。
  • TS any 是“放弃类型检查”,Dart dynamic 也是,但 Dart 的类型系统更严格,调用不存在的方法会在运行时抛出异常,而 TS 不会。
  • late 在 TS 中通常配合 ! 断言使用,但 Dart 的 late 会在第一次访问时自动检查是否已初始化,更安全。

空安全 (Null Safety)

Dart 默认不可为 null,需显式声明:

String? nullable;            // 可为 null
String nonNullable = 'hi';   // 不可为 null
nonNullable ??= 'default';    // 若为 null 则赋值 (类似 Go 的 if nil)
nullable?.length;            // 安全调用 (类似 TS 可选链)

Go 对比:Go 无空安全,nil 指针可用但无语法糖,需手动 if err != nil。Dart 的 ?.?? 是强制的空安全语法,减少代码。

TS 对比:TS 有 strictNullChecks 选项,开启后与 Dart 类似,但 ?.?? 语法相同,Dart 多了 late 非空延迟和 required 命名参数(见下方)。


2. 函数

Dart 函数是一等公民,支持闭包、箭头函数、可选参数。

// 普通函数
int add(int a, int b) => a + b;

// 命名可选参数 (大括号)
void greet({required String name, int age = 18}) { }

// 位置可选参数 (中括号)
void sum(int a, [int b = 0, int c = 0]) { }

// 函数类型定义
typedef IntCallback = int Function(int a);

Go 差异

  • Go 函数没有默认参数(需通过结构体或函数选项模式),Dart 支持默认值和命名参数。
  • Dart 箭头函数 => 只能是单一表达式,而 Go 没有箭头函数。
  • Dart 的 required 在 Go 中只能通过结构体强制要求。

TS 差异

  • TS 也有默认参数和可选参数(?),但 Dart 明确区分命名参数(名称必须提供)和位置参数,TS 只有位置参数。
  • Dart 的 required 是编译时强制,类似 TS 中在类型上标记为必填(但 TS 常有 undefined)。
  • TS 支持函数重载声明(多个签名),Dart 不支持重载,但可通过可选参数和命名参数实现类似效果。

3. 类和面向对象

Dart 是纯面向对象语言,所有非 null 值都是对象,支持单继承、mixin、接口(隐式实现)。

class Animal {
  final String name;            // 成员变量
  Animal(this.name);            // 构造函数简写
  Animal.named({required this.name}); // 命名构造函数
}

class Dog extends Animal with Bark {
  Dog(String name) : super(name);  // 初始化列表调用父类构造
  @override
  void makeSound() => print('Woof'); // 方法重写
}

mixin Bark {
  void bark() => print('Bark!');
}

// 隐式接口:任何类都可作为接口实现
class Robot implements Animal {
  @override
  String get name => 'Robot';
}

Go 差异

  • Go 没有类,用结构体和接口组合。Dart 有完整的类、继承、构造函数体系。
  • Go 的接口是鸭子类型隐式实现,Dart 的 implements 需要显式声明并实现所有成员(隐式接口)。
  • Dart 支持 mixin(代码复用机制,类似多继承但无状态),Go 通过嵌入(embedding)实现组合,但不能像 mixin 那样复用方法实现并限制范围。
  • Dart 的构造函数可重载不同名称,Go 需使用工厂函数。

TS 差异

  • TS 的类语法与 Dart 非常相似,但 Dart 没有 public/protected/private 关键字,用 _ 前缀表示库私有(library-private),文件内可见。
  • TS 通过 implements 实现接口(显式),Dart 相同。TS 的 abstract 类 Dart 也有。
  • Dart 不支持装饰器实验性语法外的元数据,比 TS 的装饰器弱。
  • Dart 的 mixin 更纯粹,TS 一般用 mixin 模式(函数实现)或多重继承结合接口,不如 Dart 的语言级别支持。

4. 异步编程

Dart 使用 FutureStream,配合 async/await

Future
<String> fetchData() async {
  final result = await http.get(url);
  return result.body;
}

// Stream (类似 Go 的 channel 或 RxJS Observable)
Stream
<int> countStream(int max) async* {
  for (var i = 1; i <= max; i++) {
    yield i;
    await Future.delayed(Duration(seconds: 1));
  }
}

Go 对比

  • Go 没有 async/await,使用 goroutine + channel。Dart 的异步模型是单线程事件循环(isolate),类似 Node.js 的事件驱动。
  • Dart 的 Future 类似 Go 的 future 模式或 channel,但语法更简洁。Go 的并发是 M:N 调度,Dart 的并发通过 isolate(独立内存堆)。
  • Stream 对应 Go 的 channel,但 Dart 是 push-based,且可通过 async* 生成。

TS 对比

  • TS/JS async/await 与 Dart 几乎一致,都基于 Promise/Future。
  • TS 的 Observable(RxJS)比 Dart 的 Stream 功能更丰富,Dart 的 Stream 更基础,但有转换方法。
  • 注意:Dart 的 `Future ` 类似 `Promise`,但 Dart 没有 `.then` 链上自动展开嵌套,需显式处理。

5. 集合类型 (List, Set, Map)

类型 Dart Go TS
列表 `List
[1,2]| 切片[]int,无泛型方法 |number[]Array`
集合 `Set
{1,2}字面量 | 无内建 Set,用map[Key]bool|Set`
映射 Map<String, int>{k:v} map[string]int Record<string, number>Map
常用方法 map, where, foldfirst for range, slices 原生 map, filter, reduce

Dart 集合是泛型和强类型的,支持链式操作(返回 Iterable),可再转为 List
Dart 特有的 collection if/for 语法糖:

var list = [
  1,
  if (promoActive) 2,
  for (var i in items) i.price,
];

Go 差异:Go 的切片/映射无内置高阶方法,需循环处理。Dart 集合 API 更接近函数式风格。需注意 Dart 的 List.filled(n, el) 填的是相同引用,Go 的 make([]T, n) 是零值。

TS 差异:API 相似,但 TS 的数组方法会直接修改原数组(如 push),Dart 的 List 默认是可变的,但 add 等操作类似。Dart 的 const 列表是不可变且编译时常量。


6. 类型系统与泛型

Dart 的类型是强静态类型,有类型推断,泛型支持型变控制。

class Box
<T> {
  final T value;
  Box(this.value);
}

// 限制类型参数
class NumberBox<T extends num> {}

// 型变(Dart 默认是 invariant,可使用关键字)
// 但典型用法是使用 `covariant` 关键字或函数型变。

Go 差异

  • Go 1.18+ 引入泛型,语法为 func F[T any](t T),Dart 用 ` ` 紧跟类、函数名。
  • Go 泛型基于合约 (interface 约束),Dart 用 extends 限制上界。
  • Dart 没有类似 Go 的类型集和近似类型的理念。

TS 差异

  • TS 泛型语法极为相似,差异点:TS 有条件类型、映射类型、模板字面量类型等高级特性,Dart 类型系统更简单。
  • TS 结构类型 (structural) ,Dart 名义类型 (nominal),即便相同结构的两个类也不同,需显式实现接口。

7. 模块、导入和可见性

Dart 使用库 (library) 作为代码组织单位,每个 .dart 文件默认是一个库。

import 'dart:math';               // 核心库
import 'package:http/http.dart';  // 包
import 'my_file.dart';            // 相对/绝对路径
import 'my_file.dart' as utils;   // 别名,类似 Go 的 import alias / TS 的 import * as
import 'my_file.dart' show foo;   // 只导入 foo
import 'my_file.dart' hide bar;   // 隐藏 bar

Go 差异

  • Go 使用包 (package) ,一个目录一个包,导入路径。Dart 鼓励单文件一个库,无强制目录对应。
  • Go 的导出通过首字母大写,Dart 以 _ 前缀表示库私有(仅文件内可见)。
  • Go 不允许循环导入,Dart 允许(但不鼓励)。

TS 差异

  • TS/ES 模块是 import/export,Dart 的 import 语法类似,但 Dart 没有 default export,所有顶级声明被隐式导出,除非用 _ 前缀私有。
  • TS 的 import type 在 Dart 无对应,Dart 全部导入。

8. 错误处理

try {
  throw FormatException('bad');
} on FormatException catch (e, stack) {
  print(e);
} finally {
  // cleanup
}

Go 差异:Go 没有异常,用返回错误值和多返回值。Dart 既有异常也有 ErrorException 类型,类似其他面向对象语言。

TS 差异:TS/JS 也有 try/catch,但 Dart 不支持 catch 无参数,必须捕获对象。Dart 的 on 可以只捕获特定类型。


9. 并发模型:Isolate

Dart 是单线程事件循环(类似 JS),长时间任务需使用 Isolate

import 'dart:isolate';
Isolate.spawn(heavyTask, message);

Go 对比:Go 天然支持 goroutine 和 channel,Dart 的 Isolate 有独立内存,通信靠消息传递,与 Go 的并发哲学类似但重量不同。普通开发中用 compute 函数快捷创建。

TS 对比:TS 通过 Web Workers 或 Node.js worker_threads,同样通过消息通信。语法差异明显。


10. 常用模式速查

场景 Dart Go TS
多返回值 返回 Record (2.17+) 或 List 原生多返回值 返回元组或对象
枚举 enum Color { red, green } 无原生枚举,用 iota 类似 Dart,支持数字和字符串枚举
扩展方法 extension on String { } 无,通过函数 类似,声明合并也可
别名 typedefextension type MyType = ... type 关键字
代码生成 build_runner, 注解处理器 go generate 装饰器/ts-morph等

11. 变量声明:final vs const vs late

差异点:Go 只有编译时 const,没有运行时单次赋值的 final;TS 的 const 是引用不可变。late 是 Dart 特有的延迟非空初始化。

Dart 示例

// final:运行时确定,但只能赋值一次
final now = DateTime.now(); // Go 无法用 const 做到,TS 的 const 允许 push 数组,这里不一样

// const:编译时常量,类似 Go 的 const,TS 没有这个概念
const apiUrl = 'https://api.example.com';

// late:先声明后初始化,访问前必须赋值,类似 TS 的非空断言!但带检查
late String description;
description = 'Dart is fun';
print(description); // OK

// Go: const apiUrl = "..."
// TS: const apiUrl = "..." as const? 不是编译时常量

12. 空安全:???required

差异点:Go 没有以此为基础的语法糖,全靠 nil 检查;TS 可选链 ?. 类似,但 Dart 强制非空,必须显式声明可空类型 String?

Dart 示例

String? nullableName;
// 空安全调用
print(nullableName?.length); // 不会报错,输出 null
// 空值合并
String name = nullableName ?? 'Guest'; // 类似 Go: if name == "" { name = "Guest" }
// required 命名参数
void greet({required String name}) {}
// greet(); // 编译错误,类似 TS 里强制必填,但 Dart 更严格,运行时不会有 undefined

13. 函数参数:命名可选 vs 位置可选 vs 重载

差异点:Go 函数无默认参数和命名参数;TS 只有位置可选参数,且通过函数重载声明多签名。

Dart 示例

// 命名可选参数(必须通过名称传递)
void configure({bool? darkMode, int? fontSize}) {}
configure(darkMode: true, fontSize: 14); // 必须指定参数名

// 位置可选参数
void log(String msg, [String? tag, int? level]) {}
log('hello'); // 可选的位置参数可跳过

// Dart 没有函数重载,用可选参数模拟
// TS 可以写多个 function signature,Dart 不行
void handle(String input) => print('String');
// void handle(int input) => print('int'); // 错误:重复定义

14. 类:构造函数、mixin、隐式接口、私有成员

差异点:Go 无类,用结构体和接口;TS 有类但 mixin 较弱,且访问修饰符为 public/private/protected

Dart 示例

class Animal {
  final String name;
  Animal(this.name); // 简洁构造
  Animal.named({required this.name}); // 命名构造,Go 需要工厂函数

  // 私有成员(下划线开头,库内可见,非类内)
  void _privateMethod() {}
}

mixin Swimmer {
  void swim() => print('swimming');
}

class Duck extends Animal with Swimmer {
  Duck(String name) : super(name);
}

// 隐式接口:任何类都可以作为接口被实现
class Robot implements Animal {
  @override
  String get name => 'Robot'; // 必须实现 Animal 的所有公共成员
  // 不需要继承,纯粹实现接口
}

对比:Go 的结构体嵌入可以实现类似组合,但无法像 mixin 那样复用方法同时限制基类。TS 没有原生的 mixin 关键字,一般用函数生成类或者混入模式实现。


15. 异步:FutureStream vs Go goroutine vs Promise

差异点:Go 用 goroutine + channel,Dart 用单线程事件循环 + Future/Stream;TS 类似但 Stream 更基础,且 Dart 没有自带 Observable

Dart 示例

Future
<String> fetchData() async {
  // 类似 TS 的 async/await,Go 需要用 goroutine+channel 模拟
  await Future.delayed(Duration(seconds: 1));
  return 'data';
}

// Stream 类似 Go 的 channel,但有更多变换
Stream
<int> numbers() async* {
  for (int i = 0; i < 3; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

Go 类似概念

// chan int 类似 Stream
<int>
func numbers() chan int {
    ch := make(chan int)
    go func() {
        for i := 0; i < 3; i++ {
            time.Sleep(time.Second)
            ch <- i
        }
        close(ch)
    }()
    return ch
}
// 消费:for num := range numbers() { }

16. 集合操作与 collection if/for

差异点:Go 切片/映射无内置高阶方法;TS 有 mapfilter 等,但 Dart 提供了额外的声明式集合构建语法。

Dart 示例

// collection if:根据条件添加元素
bool promoActive = true;
var items = [1, 2, 3];
var list = [
  'header',
  if (promoActive) 'PROMO',
  for (var i in items) i * 10, // 循环生成
];
// list => ['header', 'PROMO', 10, 20, 30]

// Go 需要手动循环 append
// TS 可以用数组展开 [...(promoActive ? ['PROMO'] : []), ...items.map(x => x*10)]

链式调用

final result = [1, 2, 3, 4]
    .where((n) => n > 2)   // 过滤
    .map((n) => n * 2)     // 映射
    .toList();             // 转为 List,TS 自动转换,Dart 需显式调用
// Go 需要 for 循环实现

17. 类型系统:名义类型 vs 结构类型

差异点:TS 是结构类型,Dart 是名义类型(除了 dynamicObject 的兼容)。Go 的接口也是结构类型。

Dart 示例

class Point {
  int x, y;
  Point(this.x, this.y);
}

class Coordinate {
  int x, y;
  Coordinate(this.x, this.y);
}

// 即使结构完全相同,也不能直接赋值
Point p = Coordinate(1, 2); // 编译错误
// 需要显式实现接口或转换

// TS 中如果两个类型结构相同,可以互相赋值,无错误

18. 模块可见性:_ 私有 vs export 导出

差异点:Go 首字母大写导出;TS 用 export 显式导出,有默认导出。Dart 顶级声明默认全部导出,仅 _ 前缀表示文件内私有。

Dart 示例

// file: utils.dart
void publicFunction() {}
void _privateFunction() {} // 仅本文件可见

// 使用时
import 'utils.dart';
publicFunction(); // OK
_privateFunction(); // 编译错误

对比

  • Go:func privateFunc() 小写包内可见,PublicFunc 大写导出。
  • TS:需要 export function publicFunc(),未导出的在模块外不可见,且支持 export default

19. 错误处理:catch 必须指定参数,on 类型捕获

差异点:Go 无异常,用多返回值;TS 的 catch 可以不写参数,Dart 必须捕获对象。

Dart 示例

try {
  throw FormatException('Bad format');
} on FormatException catch (e) {
  // 捕获特定类型
  print(e.message);
} catch (e) {
  // 捕获所有,必须写参数
  print(e);
} finally {
  // 清理
}

TS 可以写 catch { /* 无参数 */ },Dart 不允许。


20. Isolate 并发 vs goroutine vs Worker

差异点:Dart 的 Isolate 是独立内存的隔离体,通过消息通信。Go 的 goroutine 共享内存但有 GMP 调度,更轻量。TS 的 Worker 类似 Isolate 但 API 不同。

Dart 示例

import 'dart:isolate';

void heavyComputation(SendPort sendPort) {
  // 这里运行在独立 Isolate,内存不共享
  int result = 0;
  for (int i = 0; i < 100000000; i++) {
    result += i;
  }
  sendPort.send(result);
}

void main() async {
  final receivePort = ReceivePort();
  await Isolate.spawn(heavyComputation, receivePort.sendPort);
  final result = await receivePort.first;
  print(result);
}

Go 直接用 go heavyComputation() 共享内存。TS 需要 new Worker('worker.js') 并在不同文件。


21. 扩展方法 (extension)

差异点:Go 不支持扩展一个类型(只能定义新函数);TS 也有声明合并/原型扩展,但 Dart 通过 extension 提供类型安全的静态扩展。

Dart 示例

extension StringX on String {
  String get reversed => split('').reversed.join();
}

void main() {
  print('hello'.reversed); // olleh
}

Go:定义包级函数 func Reverse(s string) stringTS:可以 String.prototype.reversed 或通过 interface 声明合并,但不如 Dart 安全明确。


22. 枚举:增强型枚举 vs iota 与 TS 枚举

差异点:Go 用 iota 实现枚举常量,功能较弱;TS 支持数字和字符串枚举。Dart 支持增强型枚举,可带成员和方法。

Dart 示例

enum Color {
  red(0xFF0000),
  green(0x00FF00),
  blue(0x0000FF);

  final int hex;
  const Color(this.hex);
}

void main() {
  print(Color.red.hex); // 16711680
}

Go

type Color int
const (
    Red Color = iota
    Green
    Blue
)

TS 枚举更接近 Dart,但不能直接带方法和工厂。