第一章:Golang国内份额临界点的实证分析与战略意义
近年来,Golang在国内技术生态中的渗透率呈现非线性跃升趋势。根据 CNCF 2023 年度中国开发者调研与 Stack Overflow 中国区语言使用热力图交叉验证,Go 语言在云原生、中间件及高并发后端服务领域的采用率已突破 37.2%,首次超越 Java 在该细分场景的部署占比(35.8%)。这一拐点并非孤立现象,而是由基础设施演进、人才供给结构变化与企业级工程效率诉求共同驱动的结果。
关键指标的结构性拐点
- 招聘需求增速:拉勾网数据显示,2023 Q3 Go 岗位同比增长 62%,显著高于 Python(+18%)和 Java(+9%)
- 开源项目密度:GitHub China Trending 中 Go 项目占比达 41%,连续六个季度稳居第一
- 头部云厂商 SDK 支持度:阿里云、腾讯云、华为云最新版 SDK 全量提供 Go 客户端,且默认文档优先级高于 Java/Python
生产环境落地效能实证
某金融级消息平台重构案例显示:将核心路由模块从 Java 迁移至 Go 后,在同等硬件资源下,P99 延迟从 127ms 降至 23ms,内存占用减少 64%,同时 CI 构建耗时压缩至原来的 1/5。其关键优化路径如下:
# 使用 go build -ldflags="-s -w" 剥离调试符号并压缩二进制体积
# 配合 goreleaser 自动化发布多平台制品(Linux/ARM64/Windows)
go build -ldflags="-s -w" -o ./bin/router ./cmd/router
该构建指令通过静态链接与符号裁剪,使最终二进制体积控制在 9.2MB 内,规避了容器镜像中 glibc 依赖兼容性问题,直接支撑灰度发布节奏从“天级”提升至“小时级”。
产业协同效应初显
| 维度 | 2021 年状态 | 2023 年状态 | 变化动因 |
|---|---|---|---|
| 教育体系覆盖 | 零星高校选修课 | 32 所双一流高校纳入核心实践课程 | 教育部《新一代编程语言教学指南》推动 |
| 工具链成熟度 | 依赖 VS Code 插件 | GoLand 官方深度集成 go.mod 智能依赖分析 | JetBrains 与 GopherChina 联合优化 |
| 社区响应速度 | issue 平均闭环 14 天 | 核心库 PR 平均合并 | 腾讯、字节等企业设立专职 Maintainer 席位 |
这一临界点标志着 Go 不再是“替代性选择”,而成为支撑中国数字基建自主可控的关键编译型语言基座。
第二章:Java团队Go语言能力重构路径
2.1 Go内存模型与JVM运行时对比:理论差异与实践迁移陷阱
数据同步机制
Go 依赖 Happens-Before 关系(如 go 语句、channel 通信、sync.Mutex)实现内存可见性;JVM 则基于 JMM(Java Memory Model),以 volatile、synchronized 和 final 字段语义定义同步契约。
典型迁移陷阱
- Go 中无
volatile等价物,误用非同步变量易引发竞态 - JVM 的
Thread.stop()在 Go 中无对应机制,需用context.Context主动协作终止
Channel vs Wait/Notify
// Go:channel 天然同步且传递所有权
ch := make(chan int, 1)
go func() { ch <- 42 }() // 写入即同步可见
val := <-ch // 读取建立 happens-before
该操作隐式完成内存屏障插入与数据同步,无需显式锁;而 JVM 中 Object.wait()/notify() 需严格配合 synchronized 块,否则抛出 IllegalMonitorStateException。
| 维度 | Go | JVM |
|---|---|---|
| 同步原语 | channel / Mutex | synchronized / volatile |
| 内存屏障控制 | 编译器+运行时自动插入 | 依赖关键字显式声明 |
| GC触发时机 | 基于三色标记+写屏障 | 可配置多种GC算法与触发阈值 |
graph TD
A[goroutine A] -->|ch <- x| B[Channel]
B -->|x = <-ch| C[goroutine B]
C --> D[自动建立happens-before]
2.2 Goroutine调度器与线程池演进:从ExecutorService到runtime.Gosched实战调优
Java 的 ExecutorService 本质是固定/可伸缩线程池 + 阻塞队列,线程数受限、上下文切换开销高;Go 则通过 M:N 调度模型(G-P-M) 实现轻量级并发抽象。
调度核心差异对比
| 维度 | Java ExecutorService | Go Goroutine Scheduler |
|---|---|---|
| 并发单元 | Thread(KB级栈,OS级) | Goroutine(2KB初始栈,用户态) |
| 调度主体 | OS内核 | Go runtime(协作式+抢占式) |
| 阻塞处理 | 线程挂起 → 资源闲置 | G移交P → M复用执行其他G |
runtime.Gosched() 的精准干预场景
当长循环中无函数调用(编译器无法插入抢占点)时,需手动让出P:
for i := 0; i < 1e6; i++ {
process(i)
if i%1000 == 0 {
runtime.Gosched() // 主动 relinquish P,允许其他G运行
}
}
runtime.Gosched()不阻塞当前G,仅将G放回全局运行队列,由调度器重新分配P。它不释放OS线程(M),也不触发GC,是低开销的协作式让权。
调度演进路径
graph TD A[Java线程池] –> B[协程库如Quasar] –> C[Go Goroutine + G-P-M] –> D[基于per-P本地队列的批量窃取调度]
2.3 接口设计哲学迁移:Java泛型+抽象类 vs Go接口+组合,重构微服务契约层
契约抽象的本质差异
Java 依赖类型约束与继承层次:
public abstract class OrderService<T extends Order> {
public abstract T create(T order); // 泛型限定 + 抽象方法
}
该设计强制子类继承并实现具体类型逻辑,耦合编译期类型与业务行为。
Go 的契约即能力
Go 接口不绑定实现,仅声明行为契约:
type Validator interface {
Validate() error
}
type Order struct{}
func (o Order) Validate() error { return nil } // 隐式实现
Order 无需显式声明实现 Validator,组合即契约,解耦更彻底。
关键对比维度
| 维度 | Java(泛型+抽象类) | Go(接口+组合) |
|---|---|---|
| 类型绑定时机 | 编译期强绑定 | 运行时隐式满足 |
| 扩展成本 | 修改抽象类影响所有子类 | 新增接口零侵入现有类型 |
微服务契约演进路径
graph TD
A[HTTP JSON Schema] --> B[Java Feign Client<br>+泛型DTO抽象]
B --> C[Go gRPC Gateway<br>+接口组合校验器]
C --> D[契约即代码:接口嵌套+结构体组合]
2.4 错误处理范式转换:Checked Exception消亡与error wrapping链式诊断实践
Java 的 Checked Exception 曾强制调用方处理异常,但现代 Go、Rust 及新版 Java(通过 try-with-resources 与 CompletionStage)转向显式错误传播与包装。
错误包装的核心价值
- 隐藏底层实现细节
- 添加上下文(时间、ID、操作阶段)
- 支持多层诊断追踪
Go 中的 error wrapping 实践
import "fmt"
func fetchUser(id int) error {
if id <= 0 {
return fmt.Errorf("invalid user ID %d: %w", id, ErrInvalidInput)
}
return nil
}
%w 动态嵌入原始错误,支持 errors.Is() 和 errors.Unwrap() 链式判断;id 作为诊断参数注入上下文,便于定位业务逻辑断点。
错误链诊断能力对比
| 范式 | 上下文保留 | 栈追溯能力 | 工具链支持 |
|---|---|---|---|
| Checked Exception | ❌(编译期强制) | ✅(完整栈) | ✅(IDE 集成) |
fmt.Errorf("%w") |
✅(键值注入) | ✅(%+v 展开) |
✅(errors 包) |
graph TD
A[HTTP Handler] --> B[Service Layer]
B --> C[DB Query]
C --> D[Network Timeout]
D -->|Wrap with context| C
C -->|Re-wrap with traceID| B
B -->|Final wrap with operation| A
2.5 构建生态适配:Maven依赖管理→Go Modules版本锁定+proxy镜像配置实战
Java生态中Maven通过pom.xml与中央仓库实现确定性依赖,而Go Modules需同等可靠的版本控制与分发机制。
版本锁定:go.mod + go.sum双保障
# 初始化模块并锁定依赖树
go mod init example.com/app
go mod tidy # 自动生成go.mod(声明)与go.sum(校验和)
go.mod记录精确语义化版本(如 rsc.io/binarytree v0.1.0),go.sum则存储每个模块的SHA-256哈希,防止篡改。
配置国内代理加速依赖拉取
# 设置 GOPROXY(支持多级 fallback)
export GOPROXY=https://goproxy.cn,direct
# 或写入 ~/.bashrc 持久生效
常用镜像源对比
| 镜像源 | 响应延迟 | 支持私有模块 | 是否需认证 |
|---|---|---|---|
| goproxy.cn | ✅(企业版) | 否 | |
| proxy.golang.org | 较高(境外) | ❌ | 否 |
| 私有 Nexus Go Proxy | 可控 | ✅ | ✅ |
graph TD
A[go build] --> B{GOPROXY配置?}
B -->|是| C[向goproxy.cn请求]
B -->|否| D[直连github.com]
C --> E[返回缓存/代理模块]
D --> F[可能超时或限流]
第三章:微服务架构层Go化关键跃迁
3.1 服务注册发现:Nacos Java SDK迁移至go-nacos客户端的零停机灰度方案
核心设计原则
- 双注册双发现:新旧客户端并行注册同一服务名,通过 namespace 或 group 隔离灰度流量
- 健康探针协同:Java SDK 与 go-nacos 共享心跳周期(
beatInterval=5s),避免注册状态抖动
数据同步机制
// 初始化兼容模式客户端(复用原Java服务名+新增灰度标签)
client, _ := NewClient(vo.NacosClientParam{
ServerAddr: "nacos.example.com:8848",
NamespaceId: "public", // 与Java端一致
Username: "nacos",
Password: "nacos",
ContextPath: "/nacos",
})
// 注册时携带灰度元数据,供网关路由识别
_, err := client.RegisterInstance(vo.RegisterInstanceParam{
Ip: "10.0.1.123",
Port: 8080,
ServiceName: "user-service",
GroupName: "DEFAULT_GROUP",
Metadata: map[string]string{"version": "v2", "lang": "go"}, // 关键标识
})
逻辑分析:
Metadata中lang: go作为灰度分流依据;GroupName和NamespaceId严格对齐 Java SDK 配置,确保服务列表全局可见。ServerAddr复用原有 Nacos 集群地址,无需变更基础设施。
灰度发布流程
graph TD
A[Java客户端持续注册] --> B{网关按metadata.lang路由}
C[go-nacos客户端启动注册] --> B
B --> D[lang=java → 老实例]
B --> E[lang=go → 新实例]
D & E --> F[全量验证通过后下线Java客户端]
| 迁移阶段 | Java SDK 状态 | go-nacos 状态 | 流量比例 |
|---|---|---|---|
| Phase 1 | 全量注册 | 仅注册,不承接流量 | 100% → 0% |
| Phase 2 | 注册+承接 | 注册+承接(带灰度标) | 80% → 20% |
| Phase 3 | 停止注册 | 全量承接 | 0% → 100% |
3.2 分布式事务:Seata AT模式→Go生态Saga/TCC双轨落地与补偿逻辑单元测试
Saga 模式核心契约
Saga 将长事务拆解为本地事务序列,每个步骤配对可逆补偿操作。Go 生态中 go-saga 提供声明式编排能力:
// 定义转账 Saga 流程(含正向与补偿)
saga := saga.NewSaga("transfer").
AddStep("debit", debitAccount, compensateDebit).
AddStep("credit", creditAccount, compensateCredit)
debitAccount 执行扣款并持久化本地事务;compensateDebit 在后续失败时执行反向冲正。参数说明:AddStep 接收动作名、正向函数、补偿函数三元组,自动构建执行/回滚拓扑。
TCC 模式适配要点
TCC 要求业务显式实现 Try/Confirm/Cancel 三阶段接口,Go 中需满足幂等性与空回滚约束:
| 阶段 | 调用时机 | 幂等要求 | 空回滚支持 |
|---|---|---|---|
| Try | 全局事务发起时 | ✅ | ❌ |
| Confirm | 所有 Try 成功后 | ✅ | ✅(无状态) |
| Cancel | 任一 Try 失败或超时时 | ✅ | ✅(判空) |
补偿逻辑单元测试策略
使用 testify/mock 构造隔离环境,验证补偿动作的原子性与可重入性:
func TestCompensateDebit(t *testing.T) {
mockRepo := new(MockAccountRepo)
mockRepo.On("UpdateBalance", "u1", 100.0).Return(nil)
comp := compensateDebit(mockRepo)
assert.NoError(t, comp("u1")) // 补偿成功
mockRepo.AssertExpectations(t)
}
该测试断言补偿函数在账户存在时正确调用余额还原,并通过 mock 验证调用契约。
3.3 链路追踪:SkyWalking Java Agent→OpenTelemetry Go SDK注入与Span上下文透传
跨语言上下文透传核心机制
Java服务通过SkyWalking Agent自动注入sw8(SkyWalking v8)格式的HTTP头,Go服务需解析并转换为OpenTelemetry兼容的traceparent(W3C Trace Context)。关键在于Baggage + TraceState双通道映射。
Span上下文转换代码示例
// 从HTTP Header提取并转换上下文
func extractAndConvert(ctx context.Context, r *http.Request) context.Context {
sw8 := r.Header.Get("sw8")
if sw8 == "" {
return ctx
}
// SkyWalking sw8解码 → 构造OTel SpanContext
sc, _ := skywalking.DecodeSW8(sw8) // 自定义解码器,提取traceId、spanId、parentSpanId等
otelCtx := trace.ContextWithSpanContext(ctx, trace.SpanContext{
TraceID: trace.TraceID(sc.TraceId),
SpanID: trace.SpanID(sc.SpanId),
TraceFlags: trace.FlagsSampled,
TraceState: sc.TraceState, // 映射至W3C TraceState
})
return otelCtx
}
逻辑分析:
skywalking.DecodeSW8()将Base64编码的sw8字符串(如1-ABCDE-123-456-789-1)解析为结构化字段;TraceID/SpanID需按OTel规范做字节对齐(16/8字节);TraceState保留跨厂商元数据(如sw=java-agent-9.4.0),确保链路可追溯性。
协议映射对照表
| 字段 | SkyWalking (sw8) |
OpenTelemetry (traceparent) |
说明 |
|---|---|---|---|
| Trace ID | sw8[1](hex) |
traceparent[3-35] |
需补零至32字符 |
| Parent Span ID | sw8[3](hex) |
不直接对应 | OTel用tracestate携带 |
| Sampled Flag | sw8[2] == "1" |
traceparent[2] == "01" |
决定是否采样 |
数据同步机制
graph TD
A[Java服务<br>SkyWalking Agent] -->|HTTP Header<br>sw8: 1-abc-123-456-789-1| B[Go服务<br>HTTP Handler]
B --> C[DecodeSW8 → SpanContext]
C --> D[OTel SDK创建Span<br>继承父Context]
D --> E[上报至OTel Collector]
第四章:工程效能与组织协同升级
4.1 CI/CD流水线重构:Jenkins Pipeline→GitHub Actions+goreleaser多平台制品发布
为何迁移?
Jenkins 的 Groovy Pipeline 维护成本高、依赖运维节点、跨平台构建配置冗余。GitHub Actions 提供原生云原生执行环境,结合 goreleaser 可声明式生成 macOS/Linux/Windows 多架构二进制与 checksums。
核心工作流片段
# .github/workflows/release.yml
- name: Release with goreleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
该步骤自动读取 .goreleaser.yml 配置,触发语义化版本打标、交叉编译(GOOS/GOARCH)、归档打包、校验和生成及 GitHub Release 发布。
构建产物对比
| 项目 | Jenkins Pipeline | GitHub Actions + goreleaser |
|---|---|---|
| 构建耗时 | ~8 min(含节点调度) | ~3.2 min(并行 runner) |
| 平台支持 | 需手动维护多节点池 | 原生 matrix 策略一键覆盖 |
自动化流程
graph TD
A[Push tag v1.2.0] --> B[Trigger release workflow]
B --> C[goreleaser cross-build]
C --> D[Generate artifacts + SHA256]
D --> E[Upload to GitHub Release]
4.2 单元测试体系重建:JUnit 5覆盖率驱动→go test -bench + ginkgo BDD行为验证
Go 生态中,传统 go test 的单元验证已无法满足复杂业务契约与性能双重要求,需融合基准测试与行为驱动验证。
测试范式迁移动因
- JUnit 5 的
@Coverage注解与jacoco报告在 Java 侧强调路径覆盖,但 Go 无原生覆盖率标注机制 go test -bench提供纳秒级性能基线,而ginkgo补足 Given-When-Then 的语义断言能力
典型组合用法
# 并行执行基准测试 + BDD 验证
go test -bench=. -benchmem -run=^$ ./pkg/... && ginkgo -r --focus="UserAuthFlow"
-bench=.运行所有基准函数;-run=^$排除普通测试以避免干扰;--focus精确锚定 BDD 场景。二者通过 Makefile 串联,保障「性能不退化」+「行为不偏离」双重守门。
工具链协同对比
| 维度 | go test -bench | ginkgo | 联合价值 |
|---|---|---|---|
| 执行粒度 | 函数级 | 场景级(It/Describe) | 覆盖「微观性能」与「宏观契约」 |
| 输出指标 | ns/op, MB/s | ✅/❌ + 时序日志 | 可视化回归风险矩阵 |
// benchmark_test.go
func BenchmarkTokenValidation(b *testing.B) {
for i := 0; i < b.N; i++ {
ValidateJWT("valid-token") // 模拟高频鉴权路径
}
}
b.N由go test自适应调整至统计显著性;ValidateJWT必须为无副作用纯函数,否则benchmem内存指标失真。
graph TD
A[源码变更] –> B{go test -bench}
A –> C{ginkgo BDD suite}
B –> D[性能回归预警]
C –> E[行为契约校验]
D & E –> F[CI 门禁通过]
4.3 代码质量门禁:SonarQube Java规则集→golangci-lint定制化规则+pre-commit钩子集成
从Java到Go的质量守门人迁移逻辑
SonarQube的java:S1192(字符串字面量重复)、java:S2259(空指针解引用)等高危规则,在Go生态中需映射为goconst、nilness、staticcheck等对应检查器。
定制化golangci-lint配置示例
# .golangci.yml
linters-settings:
goconst:
min-strings: 3 # 触发重复字符串检测的最小出现次数
staticcheck:
checks: ["all", "-SA1019"] # 启用全部检查,禁用已弃用API警告
该配置将SonarQube中“重复字面量”和“不安全调用”两类问题精准下沉至Go构建前阶段,避免CI层阻塞。
pre-commit集成流程
# .pre-commit-config.yaml
- repo: https://github.com/golangci/golangci-lint
rev: v1.54.2
hooks:
- id: golangci-lint
args: [--fast, --timeout=2m]
| 规则类型 | SonarQube Java规则 | golangci-lint对应linter |
|---|---|---|
| 重复代码 | java:S1192 | goconst |
| 空指针风险 | java:S2259 | nilness / staticcheck |
graph TD
A[git commit] --> B[pre-commit触发]
B --> C[golangci-lint执行]
C --> D{通过?}
D -->|否| E[中断提交并输出违规行号]
D -->|是| F[允许推送]
4.4 团队知识资产沉淀:JavaDoc→Go Doc自动化生成+Swagger+Protobuf IDL双向同步
核心挑战与设计目标
跨语言服务协作中,API契约分散在 JavaDoc、Go 注释、Swagger YAML 和 Protobuf .proto 文件中,易导致文档漂移。需建立单源权威(以 Protobuf IDL 为事实源)驱动的双向同步机制。
数据同步机制
采用 protoc-gen-doc + 自研 idl-syncer 工具链:
- Protobuf → Swagger:通过
protoc --swagger_out=...生成 OpenAPI 3.0; - Protobuf → JavaDoc/Go Doc:解析
.protoAST,注入结构化注释到生成代码的对应位置。
# protoc 插件调用示例(含参数说明)
protoc \
--plugin=protoc-gen-swagger=./bin/protoc-gen-swagger \
--swagger_out=logtostderr=true,allow_merge=true:./openapi \
--go_out=plugins=grpc:. \
--java_out=.:./src/main/java \
api/v1/service.proto
logtostderr=true启用实时错误日志;allow_merge=true支持多文件合并生成单一 Swagger 文档;--go_out和--java_out配合--doc_out插件可自动注入 Go/Java 方法级注释。
工具链协同关系
| 组件 | 输入 | 输出 | 同步方向 |
|---|---|---|---|
protoc-gen-swagger |
.proto |
openapi.yaml |
IDL → Swagger |
idl-syncer |
.proto + javadoc.xml |
同步注释标记 | 双向校验 |
| CI 钩子 | Git push | 自动触发全链路生成 | 强制一致性 |
graph TD
A[Protobuf IDL] -->|生成| B[Swagger YAML]
A -->|注入注释| C[Go struct/doc]
A -->|注入注释| D[Java class/Javadoc]
B -->|CI验证| E[API Gateway Schema Check]
C & D -->|单元测试| F[契约一致性断言]
第五章:未来三年Go技术栈演进趋势与风险预警
云原生基础设施的深度耦合
Kubernetes v1.30+ 已将 Go 1.22+ 作为构建基线,Operator SDK v2.0 要求模块化控制器必须使用 controller-runtime v0.18+,其依赖的 k8s.io/client-go v0.29+ 强制启用 go.work 多模块协同编译。某头部金融平台在迁移至 EKS 1.31 时,因遗留项目仍使用 go mod vendor + client-go v0.24,导致 CRD 注册失败率上升 37%,最终通过重构为 go.work + kubebuilder v4.3 解决。
WebAssembly 运行时的生产级突破
TinyGo 0.30 已支持 net/http 子集与 syscall/js 稳定 API,Shopify 在其前端价格计算引擎中部署了 Go 编译的 WASM 模块,启动耗时从 120ms(V8 JS)降至 42ms,但发现 time.Sleep() 在 WASM 中被静默忽略——需改用 js.Global().Get("setTimeout") 手动调度。以下为关键适配代码片段:
// ✅ 正确的 WASM 延迟实现
func delay(ms int) {
js.Global().Call("setTimeout", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// 执行业务逻辑
return nil
}), ms)
}
内存安全增强机制落地节奏
Go 1.23 将默认启用 -gcflags="-d=checkptr" 的严格指针检查,某支付网关在升级后出现 23 处 unsafe.Pointer 转换告警,集中在 bytes.Buffer 底层切片操作。修复方案需将 (*[n]byte)(unsafe.Pointer(&b.buf[0])) 替换为 unsafe.Slice((*byte)(unsafe.Pointer(&b.buf[0])), len(b.buf)),该函数已在 Go 1.20+ 标准库中提供。
生态碎片化风险预警
| 风险类型 | 典型场景 | 触发版本 | 缓解策略 |
|---|---|---|---|
| 模块版本漂移 | golang.org/x/net v0.25+ 移除 http2.Transport.DialTLSContext |
Go 1.22+ | 锁定 v0.24 并提交兼容补丁 |
| 工具链不兼容 | gofumpt v0.5.0 不支持 go.work 文件格式化 |
Go 1.21+ | 改用 go fmt -modfile=go.work |
构建可观测性新范式
OpenTelemetry Go SDK v1.21 引入 otelhttp.WithFilter 接口,允许按 HTTP 方法动态采样。某电商订单服务通过配置 func(r *http.Request) bool { return r.Method == "POST" && strings.Contains(r.URL.Path, "/order") },将 trace 数据量降低 68%,同时保障核心链路 100% 采样率。其部署拓扑如下:
graph LR
A[Go HTTP Server] --> B[otelhttp.Handler]
B --> C{Filter: POST /order}
C -->|True| D[Jaeger Exporter]
C -->|False| E[No-op Span Processor]
D --> F[Jaeger Collector]
E --> G[Null Exporter]
泛型泛化带来的维护成本
golang.org/x/exp/slices 在 Go 1.22 中正式进入标准库,但 slices.Clone 对 []*T 类型返回 []*T 而非 []T,某日志系统因误用 slices.Clone(logEntries) 导致内存泄漏——原始切片未释放,克隆体持续增长。修复需显式声明 logEntries = append([]*LogEntry(nil), logEntries...)。
供应链攻击面持续扩大
2024 年 Q1,github.com/gorilla/mux v1.8.1 被发现依赖恶意 github.com/evil-dep/v2,该包通过 go.sum 哈希劫持注入 init() 钩子。建议采用 govulncheck + cosign 签名验证双校验流程,并在 CI 中强制执行 go list -m all | grep -E '^(github\.com|golang\.org)' | xargs -I{} go mod verify {}。
