第一章:吉林祥云Go开发框架设计哲学与落地背景
吉林祥云是面向政务云与行业数字化场景深度定制的Go语言企业级开发框架,其诞生源于吉林省“数字政府”建设中对高并发、强合规、可审计、易运维微服务架构的迫切需求。在统一身份认证、电子证照核验、跨部门数据协同等典型业务中,传统Spring Boot方案面临内存占用高、冷启动慢、国产化适配成本高等瓶颈,而社区主流Go框架又缺乏对等保三级、信创中间件(如东方通TongWeb、达梦DM8)、国密SM2/SM4算法栈的原生支持。
核心设计哲学
- 契约先行:强制通过OpenAPI 3.0规范驱动服务契约,生成类型安全的客户端SDK与服务端骨架代码
- 零信任嵌入:将JWT鉴权、国密签名验签、操作留痕日志作为默认中间件,不可绕过
- 治理即配置:服务注册、熔断策略、链路采样率全部通过YAML声明式定义,避免硬编码
落地背景关键约束
| 约束维度 | 具体要求 | 框架响应方式 |
|---|---|---|
| 合规性 | 等保2.0三级、密码法、政务云安全基线 | 内置SM4-GCM加密传输、审计日志自动落库至国产时序数据库TDengine |
| 国产化 | 鲲鹏+麒麟OS、飞腾+统信UOS双栈支持 | 编译脚本自动识别CPU架构,注入对应国产SSL库(如GMSSL) |
| 运维友好 | 支持无Agent探针采集指标 | /healthz /metrics /debug/pprof 统一路由前缀,Prometheus直接抓取 |
初始化一个符合政务云规范的服务项目只需三步:
# 1. 安装祥云CLI工具(已预编译适配ARM64/x86_64国产平台)
curl -sSL https://xiangyun.jl.gov.cn/install.sh | sh
# 2. 创建项目(自动启用国密通信与等保日志模块)
xiangyun new my-gov-service --with-sm2 --with-audit-log
# 3. 启动并验证基础能力
cd my-gov-service && make run
# 访问 http://localhost:8080/healthz 应返回 {"status":"ok","sm2_enabled":true}
该框架已在吉林省政务服务“一网通办”平台、医保结算中心等12个核心系统稳定运行超18个月,单服务实例平均内存占用低于45MB,P99接口延迟控制在87ms以内。
第二章:RBAC权限引擎的标准化实现
2.1 RBAC模型在政务云场景下的合规性重构与角色粒度设计
政务云需满足《网络安全法》《等保2.0》及行业数据分类分级要求,传统RBAC的静态角色难以适配“最小权限+动态职责分离”原则。
合规驱动的角色分层设计
- 基础层:系统管理员(仅限基础设施运维)
- 业务层:社保经办员、医保审核员(绑定具体业务域+数据分级标签)
- 审计层:独立日志分析角色(只读+不可绕过审计通道)
角色-权限映射增强策略
# role-definition.yaml:嵌入数据敏感等级与操作上下文约束
role: "medical-audit-officer"
permissions:
- action: "read"
resource: "patient-record"
constraints:
data_classification: "L3" # 对应等保三级敏感数据
time_window: "09:00-17:00" # 符合政务办公时段合规要求
该配置强制权限生效前校验数据密级与操作时间,避免越权访问;data_classification对接政务数据目录平台元数据API,实现动态分级授权。
政务角色粒度对比表
| 角色类型 | 平均权限数 | 是否支持数据分级 | 是否可跨部门复用 |
|---|---|---|---|
| 通用IT管理员 | 142 | ❌ | ✅ |
| 社保初审专员 | 17 | ✅(L2/L3) | ❌(绑定属地) |
graph TD
A[用户登录] --> B{身份认证+部门归属}
B --> C[加载角色模板]
C --> D[注入数据分级策略]
D --> E[实时校验操作上下文]
E --> F[授权决策引擎]
2.2 基于Casbin v3的策略动态加载与多租户上下文隔离实践
多租户策略命名空间设计
Casbin v3 支持 g(grouping)与 p(policy)的多维度模型扩展,通过 tenant_id 作为域(domain)字段实现租户级策略隔离:
// 初始化支持 domain 的 RBAC 模型
e, _ := casbin.NewEnforcer("model.conf", adapter)
// model.conf 中定义:[matchers] m = r.sub == p.sub && r.dom == p.dom && keyMatch(r.obj, p.obj)
此配置使
enforce("alice", "resource1", "read", "tenant-a")仅匹配tenant-a下的策略,避免跨租户越权。
动态策略热加载机制
采用 Watch 接口监听数据库变更,触发增量策略同步:
- 监听 PostgreSQL 的
pg_notify事件 - 解析变更行生成
PolicyOp操作指令 - 调用
e.LoadPolicy()或细粒度e.AddPolicy()/e.RemovePolicy()
租户上下文透传流程
graph TD
A[HTTP Request] --> B{Extract tenant_id from JWT}
B --> C[Set context.WithValue(ctx, “tenant”, id)]
C --> D[Policy Enforcement with dom=tenant_id]
| 组件 | 作用 |
|---|---|
DomainAdapter |
支持按 domain 分片加载策略 |
Watcher |
实现秒级策略生效延迟 ≤ 500ms |
CachedEnforcer |
带租户键前缀的本地策略缓存 |
2.3 权限校验中间件的零侵入集成与HTTP/GRPC双协议适配
零侵入设计核心在于协议无关的拦截抽象层:将鉴权逻辑从路由、序列化、传输层彻底剥离,仅依赖统一的上下文接口 AuthContext。
双协议适配机制
- HTTP 请求通过
gin.HandlerFunc提取Authorization头与路径参数 - gRPC 请求通过
grpc.UnaryServerInterceptor解析metadata.MD与方法全名 - 二者均注入相同
AuthContext{UserID, Scope, Resource, Action}实例
统一校验入口示例
func AuthMiddleware(next interface{}) interface{} {
return func(ctx context.Context, req interface{}) (interface{}, error) {
authCtx, err := ExtractAuthContext(ctx) // 自动识别HTTP/gRPC来源
if err != nil { return nil, err }
if !rbac.Check(authCtx) { return nil, errors.New("forbidden") }
return next.(func(context.Context, interface{}) (interface{}, error))(ctx, req)
}
}
ExtractAuthContext内部通过ctx.Value()的协议标识键(如protocolKey="http"或"grpc")动态选择解析策略;rbac.Check接收标准化上下文,屏蔽底层差异。
| 协议 | 上下文提取源 | 元数据载体 |
|---|---|---|
| HTTP | *http.Request |
Header + URL Path |
| gRPC | context.Context |
metadata.MD |
graph TD
A[请求入口] --> B{协议类型}
B -->|HTTP| C[Parse Header & Route]
B -->|gRPC| D[Parse Metadata & Method]
C & D --> E[构建AuthContext]
E --> F[RBAC引擎校验]
F -->|允许| G[转发至业务Handler]
F -->|拒绝| H[返回403/PermissionDenied]
2.4 实时权限变更通知机制:基于Redis Pub/Sub的缓存一致性保障
数据同步机制
当用户权限更新时,系统触发 PUBLISH permission:change {userId},所有监听该频道的服务实例即时收到通知并刷新本地缓存。
核心实现代码
# Redis发布端(权限更新服务)
redis_client.publish("permission:change", json.dumps({"user_id": 1001, "version": 123}))
逻辑分析:permission:change 为统一通知频道;version 字段用于幂等校验与缓存淘汰策略联动;JSON序列化确保跨语言兼容性。
订阅端处理流程
# Redis订阅端(网关/业务服务)
pubsub = redis_client.pubsub()
pubsub.subscribe("permission:change")
for msg in pubsub.listen():
if msg["type"] == "message":
data = json.loads(msg["data"])
cache.delete(f"auth:{data['user_id']}") # 主动失效旧缓存
参数说明:msg["data"] 为原始字节流,需显式解码;cache.delete() 避免脏读,比被动过期更及时。
消息可靠性对比
| 方式 | 实时性 | 丢失风险 | 适用场景 |
|---|---|---|---|
| Pub/Sub | 毫秒级 | 存在(无ACK) | 高频、可容忍重试的权限变更 |
| Stream | 持久化 | 极低 | 审计日志等强一致场景 |
graph TD
A[权限更新事件] --> B[Redis PUBLISH]
B --> C[Pub/Sub广播]
C --> D[网关服务]
C --> E[订单服务]
C --> F[报表服务]
D --> G[清除本地auth缓存]
E --> G
F --> G
2.5 权限审计回溯能力:操作快照生成与策略变更链路追踪
权限变更的可追溯性是零信任架构落地的关键支柱。系统需在每次策略写入时自动捕获完整上下文快照。
操作快照生成机制
采用不可变日志+结构化元数据双写模式,确保审计证据防篡改:
# 策略变更事件快照序列化示例
snapshot = {
"policy_id": "p-789a",
"version": "v2.3",
"timestamp": "2024-06-15T08:22:14Z",
"operator": {"id": "u-456", "role": "security_admin"},
"diff": {"added": ["resource:db-prod"], "removed": ["resource:db-staging"]},
"source_ip": "203.0.113.42"
}
该结构保留策略差异(diff)、执行主体、时空坐标及来源网络指纹,支撑精准归因。
策略变更链路追踪
通过 Mermaid 构建策略演化图谱:
graph TD
A[v1.0 - initial] -->|update by Alice| B[v1.1 - added API scope]
B -->|rollback triggered| C[v1.0 - restored]
C -->|auto-patch| D[v1.2 - fixed RBAC rule]
审计数据模型
| 字段名 | 类型 | 说明 |
|---|---|---|
trace_id |
UUID | 全局唯一变更链标识 |
parent_id |
UUID | 上一版本快照ID(空表示初始) |
impact_score |
float | 自动计算的权限扩张系数 |
此设计支持跨多租户、多策略引擎的线性回溯与影响面分析。
第三章:审计日志中间件的全链路治理
3.1 政务系统审计合规要求映射:等保2.0与《吉林省政务数据安全管理办法》条目对齐
政务系统需同时满足国家等保2.0三级要求与地方规章刚性约束。核心在于建立双向映射机制,实现技术控制项与管理条款的语义对齐。
映射逻辑示例(等保2.0→吉林办法)
- 等保2.0“8.1.4 审计日志留存≥180天” → 吉林办法第十七条“政务数据操作日志保存期不得少于六个月”
- 等保2.0“8.1.5 日志内容包含用户、时间、事件、结果” → 吉林办法第二十条“日志应记录行为主体、对象、时间戳及操作结果”
关键字段标准化校验脚本
# 验证日志字段完整性(符合双规范共性要求)
required_fields = {"user_id", "timestamp", "operation_type", "resource_id", "status_code"}
log_entry = {"user_id": "gov_jl_2023", "timestamp": "2024-06-15T09:22:18Z", "operation_type": "QUERY"}
missing = required_fields - log_entry.keys() # 返回缺失字段集合
assert not missing, f"日志缺失字段:{missing}"
该脚本强制校验审计日志必填字段,status_code确保操作结果可追溯,timestamp采用ISO 8601格式以满足两地法规对时间精度的统一要求。
映射关系简表
| 等保2.0条款 | 对应吉林办法条款 | 技术落地要点 |
|---|---|---|
| 8.1.4 | 第十七条 | 日志存储采用WORM策略,防篡改+自动归档 |
| 8.1.5 | 第二十条 | 所有API网关注入统一审计中间件 |
graph TD
A[原始日志流] --> B{字段完整性检查}
B -->|通过| C[时间戳标准化 ISO8601]
B -->|失败| D[拒绝写入并告警]
C --> E[双策略存储:实时ES+冷备OSS]
E --> F[按吉林办法生成月度审计报告]
3.2 结构化审计日志设计:OpenTelemetry Schema兼容的字段规范与敏感信息脱敏策略
核心字段映射原则
严格遵循 OpenTelemetry Logs Schema v1.2,关键字段对齐如下:
| OpenTelemetry 字段 | 审计语义含义 | 是否必需 |
|---|---|---|
trace_id |
关联分布式追踪链路 | ✅ |
attributes["user.id"] |
经脱敏处理的用户标识 | ✅ |
attributes["action"] |
create, delete, update 等标准化动词 |
✅ |
severity_text |
INFO / WARN / ERROR |
✅ |
敏感字段动态脱敏策略
采用正则+白名单双控机制,在日志采集端(OTLP exporter 前)拦截:
import re
def mask_sensitive_attrs(attrs: dict) -> dict:
# 白名单:仅允许透出脱敏后的 user.id 和 resource.type
safe_keys = {"user.id", "resource.type", "action", "status.code"}
masked = {k: (re.sub(r"\d{4,}", "****", str(v)) if k not in safe_keys else v)
for k, v in attrs.items()}
# 强制重写 user.id 为哈希前缀(避免原始ID泄露)
if "user.id" in masked:
masked["user.id"] = f"uid_{hash(masked['user.id']) & 0xFFFF:04x}"
return masked
逻辑说明:该函数在日志序列化前执行;
hash() & 0xFFFF生成可重现的4位十六进制标识,兼顾可追溯性与不可逆性;正则\d{4,}覆盖身份证、卡号等长数字串,避免规则过度泛化。
脱敏流程时序
graph TD
A[原始审计事件] --> B{字段分类}
B -->|敏感键| C[正则掩码]
B -->|身份键| D[哈希截断]
B -->|元数据键| E[直通]
C & D & E --> F[OTLP 兼容结构]
3.3 异步高吞吐日志采集:基于Gin middleware + Kafka Producer的背压控制实现
核心设计思想
将日志采集与HTTP请求处理解耦,通过内存缓冲池+动态限流实现平滑背压,避免Kafka生产者阻塞Web服务。
背压控制关键组件
sync.Pool管理日志消息结构体复用semaphore.Weighted控制并发写入Kafka的goroutine数channel缓冲区(带超时丢弃策略)
Kafka Producer 配置要点
| 参数 | 推荐值 | 说明 |
|---|---|---|
BatchSize |
16384 | 平衡延迟与吞吐 |
MaxInFlight |
1 | 避免乱序,保障日志时序性 |
RetryBackoff |
100ms | 防雪崩退避 |
func logMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 非阻塞采集:仅入队,不等待Kafka响应
msg := &LogMsg{
Path: c.Request.URL.Path,
Code: c.Writer.Status(),
Time: time.Now().UnixMilli(),
}
if err := logQueue.Send(context.Background(), msg); err != nil {
// 丢弃或降级写本地文件
log.Warn("log queue full, dropped")
}
c.Next()
}
}
该中间件将日志序列化后投递至有界通道,配合Send()内部的sem.Acquire()实现信号量限流,确保峰值QPS下Kafka客户端不OOM。
graph TD
A[HTTP Request] --> B[Gin Middleware]
B --> C{logQueue.Send}
C -->|成功| D[Kafka Producer Batch]
C -->|失败| E[降级到本地磁盘]
D --> F[ACK or Retry]
第四章:统一错误码规范与可观测性体系构建
4.1 吉林祥云错误码分级体系:业务域(B)、系统域(S)、平台域(P)三级编码规则与语义约定
吉林祥云采用三层正交错误码结构,实现故障归因与责任边界清晰化:
- 业务域(B):标识业务场景(如
B01表示医保结算,B02表示电子健康档案) - 系统域(S):定位子系统模块(如
S03对应统一身份认证中心) - 平台域(P):反映基础设施层异常(如
P05代表分布式事务协调器超时)
// 示例:生成标准化错误码
public String buildErrorCode(String businessCode, String systemCode, String platformCode) {
return String.format("ERR-%s-%s-%s", businessCode, systemCode, platformCode);
// → ERR-B01-S03-P05:医保结算中,认证中心调用事务协调器超时
}
该方法强制约束三段式拼接逻辑,确保各域代码独立可替换;businessCode 必须来自白名单枚举,systemCode 由服务注册中心动态同步,platformCode 由运维平台统一发布。
| 域类型 | 编码长度 | 取值范围 | 语义示例 |
|---|---|---|---|
| B | 2位数字 | B01–B99 | B07:跨省异地就医 |
| S | 2位数字 | S01–S99 | S12:电子凭证网关 |
| P | 2位数字 | P01–P99 | P08:服务网格Sidecar熔断 |
graph TD
A[客户端请求] --> B{错误发生}
B --> C[业务逻辑校验失败] --> D[ERR-Bxx-xxx-xxx]
B --> E[下游系统响应异常] --> F[ERR-xxx-Sxx-xxx]
B --> G[K8s Pod不可达] --> H[ERR-xxx-xxx-Pxx]
4.2 错误上下文增强:HTTP状态码、gRPC Code、TraceID、RequestID的自动注入与透传
在分布式系统中,错误定位依赖可追溯的上下文。现代中间件需在请求生命周期内自动注入并透传关键标识。
标准化错误元数据注入点
- HTTP入站:
X-Request-ID(生成)、X-Trace-ID(继承或新建) - gRPC入站:
grpc-status+grpc-message与trace_idmetadata 双向同步 - 出站调用:自动将当前
TraceID和RequestID注入下游 headers/metadata
自动注入示例(Go Middleware)
func ContextEnhancer(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 优先从上游提取,缺失则生成
reqID := r.Header.Get("X-Request-ID")
if reqID == "" {
reqID = uuid.New().String()
}
traceID := r.Header.Get("X-Trace-ID")
if traceID == "" {
traceID = reqID // fallback
}
// 注入上下文
ctx := context.WithValue(r.Context(), "request_id", reqID)
ctx = context.WithValue(ctx, "trace_id", traceID)
r = r.WithContext(ctx)
// 响应头回写
w.Header().Set("X-Request-ID", reqID)
w.Header().Set("X-Trace-ID", traceID)
next.ServeHTTP(w, r)
})
}
该中间件确保每个请求携带唯一 RequestID 与可继承 TraceID,并在响应中透传,为链路追踪与错误归因提供基础支撑。
错误码映射表(HTTP ↔ gRPC)
| HTTP Status | gRPC Code | 语义说明 |
|---|---|---|
| 400 | InvalidArgument | 请求参数校验失败 |
| 404 | NotFound | 资源不存在 |
| 503 | Unavailable | 后端服务不可用 |
错误传播流程
graph TD
A[Client Request] --> B{Middleware}
B --> C[Inject RequestID/TraceID]
C --> D[Call Service]
D --> E[Error Occurs]
E --> F[Attach HTTP/gRPC Code + Context]
F --> G[Propagate to Client]
4.3 全局错误处理管道:从panic recover到Error Wrapper的标准化封装与分类响应
统一错误入口:recover + 中间件拦截
Go HTTP 服务中,recover() 捕获 panic 后需立即转为结构化错误,避免裸 panic 泄露敏感信息:
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 封装为统一 ErrorWrapper 实例
wrapped := NewErrorWrapper(ErrInternalServer, fmt.Sprintf("%v", err))
c.AbortWithStatusJSON(http.StatusInternalServerError, wrapped.ToResponse())
}
}()
c.Next()
}
}
NewErrorWrapper接收错误码(如ErrInternalServer = 50001)与原始 panic 消息,内部自动标记Category: "system"和TraceID;ToResponse()输出标准化 JSON:{"code":50001,"msg":"internal error","data":null}。
错误分类响应策略
| 类别 | HTTP 状态码 | 响应示例 msg | 是否透出详情 |
|---|---|---|---|
| 客户端错误 | 400 | “invalid param” | 仅开发环境 |
| 系统错误 | 500 | “internal error” | 否(屏蔽堆栈) |
| 业务异常 | 409 | “order exists” | 是 |
错误包装器核心流程
graph TD
A[panic] --> B{recover()}
B --> C[NewErrorWrapper]
C --> D[Attach Category/TraceID]
D --> E[ToResponse]
E --> F[HTTP JSON Response]
4.4 错误行为分析看板:Prometheus指标埋点与ELK日志聚类告警联动实践
数据同步机制
通过 Prometheus remote_write 将异常指标(如 http_request_errors_total{job="api",status=~"5.."} > 0)实时推送至 Logstash 的 HTTP input 插件,触发日志关联查询。
# prometheus.yml 片段
remote_write:
- url: "http://logstash:8080/prometheus/metrics"
queue_config:
max_samples_per_send: 1000 # 控制批量吞吐,避免ELK过载
该配置确保高基数错误指标以可控节奏进入日志管道,为后续聚类提供时间对齐的上下文锚点。
告警联动逻辑
graph TD
A[Prometheus 指标突增] --> B{触发 remote_write}
B --> C[Logstash 解析指标标签]
C --> D[ES 查询同 timestamp+service 的日志]
D --> E[调用 ML 进行日志文本聚类]
E --> F[生成异常行为簇 ID]
F --> G[看板渲染热力图与根因建议]
聚类结果映射表
| 簇ID | 关键词特征 | 关联指标标签 | 建议动作 |
|---|---|---|---|
| C-72 | “timeout”, “pool exhausted” | job=“auth”, instance=“10.2.3.4:8080” | 扩容连接池 + 检查DB延迟 |
第五章:开源模板交付与持续演进路线
模板仓库的标准化交付流程
我们以 Apache Flink 实时数仓模板(flink-warehouse-template)为例,该模板已托管于 GitHub 组织 datastack-org 下,采用语义化版本(v2.4.0 → v2.5.0),每次发布均附带完整的 CI/CD 验证流水线。交付物包含:docker-compose.yml(支持单机快速验证)、helm-chart/(Kubernetes 生产部署)、sql/ddl/(统一建模脚本)及 test/e2e/(基于 TestContainers 的端到端测试用例)。所有模板均通过 GitHub Actions 自动执行三阶段校验:静态扫描(Trivy + Semgrep)、环境兼容性测试(Ubuntu 22.04 / Rocky Linux 9)、以及真实集群冒烟测试(向本地 MinIO + Kafka + Flink Standalone 提交作业并验证结果表写入)。
社区驱动的演进机制
模板的迭代并非由单一团队闭门设计,而是依托 RFC(Request for Comments)机制推进。例如,2024 年 Q2 关于“支持 Iceberg 主键约束自动推导”的提案(RFC-017),经 12 名核心贡献者评审、3 轮社区投票后合并;其落地代码包含 4 类变更:新增 iceberg-primary-key-infer.py 工具、扩展 template-config.yaml schema、更新 Helm values 文件默认行为,并同步修改 7 处文档示例。所有变更均关联 Jira 看板(FLINKTPL-289),并在 PR 描述中嵌入 Mermaid 流程图说明数据流重构逻辑:
flowchart LR
A[原始 DDL] --> B{是否含 PRIMARY KEY?}
B -->|是| C[生成 Iceberg 表 WITH primary_key]
B -->|否| D[调用 infer_primary_key.py]
D --> E[基于非空+唯一性分析推导候选键]
E --> F[写入 template-config.yaml]
版本兼容性保障策略
为避免破坏性升级,我们实施严格的跨版本兼容矩阵管理。下表展示 v2.4.x 至 v2.5.x 的关键兼容项:
| 兼容维度 | v2.4.3 | v2.5.0 | 兼容性说明 |
|---|---|---|---|
| Helm API 版本 | v2 | v2 | values.yaml 结构完全向后兼容 |
| SQL 语法支持 | ✅ | ✅ | 所有 DDL/DML 语句可无修改复用 |
| Kafka 连接器 | 3.3.0 | 3.4.1 | 协议层兼容,但需重启任务生效 |
| Iceberg 运行时 | 1.3.0 | 1.4.3 | 新增 write.target-file-size-bytes 参数,默认继承旧值 |
模板生命周期监控看板
生产环境中,每个模板实例均上报健康指标至 Prometheus:template_deploy_duration_seconds(部署耗时)、job_restarts_total(Flink 任务重启次数)、schema_drift_count(DDL 变更导致的结构不一致告警)。Grafana 看板实时聚合全量模板实例状态,并对连续 3 次部署失败或 schema drift 超过阈值(>5 次/天)自动触发 Slack 告警,通知对应 SIG(Special Interest Group)负责人。2024 年 6 月,该机制成功捕获某金融客户因误删 cdc_offset 字段引发的 17 个下游模板实例异常,平均响应时间缩短至 8 分钟。
开源协作的效能度量
自 2023 年 9 月上线以来,模板仓库累计接收 214 个外部 PR,其中 87% 由企业用户提交(含工商银行、顺丰科技、小红书等),平均合并周期为 3.2 天。我们通过 git log --since="2024-01-01" --oneline | wc -l 统计,仅上半年即完成 142 次 commit,覆盖功能增强(42%)、缺陷修复(33%)、文档优化(18%)及依赖升级(7%)。所有贡献者均纳入 CONTRIBUTORS.md 动态生成列表,并按季度颁发 Open Source Contributor Badge。
