Files
break c64f3d8b80
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s
全局审计完成
2026-08-07 11:02:52 +08:00

577 lines
24 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package auditcoverage 提供全系统入口的可复核审计覆盖扫描。
package auditcoverage
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/bytedance/sonic"
)
// Entry 是一个必须经过审计分类的 HTTP、Worker 或定时任务入口。
type Entry struct {
Key string `json:"key"`
Kind string `json:"kind"`
CodeEntry string `json:"code_entry"`
Owner string `json:"owner"`
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
Summary string `json:"summary"`
AuditEvent string `json:"audit_event"`
DomainLedger string `json:"domain_ledger"`
IntegrationLog string `json:"integration_log"`
Outbox string `json:"outbox"`
ActionCode string `json:"action_code,omitempty"`
ActionName string `json:"action_name,omitempty"`
Category string `json:"category,omitempty"`
Risk string `json:"risk,omitempty"`
PrimaryResource string `json:"primary_resource,omitempty"`
AffectedResource string `json:"affected_resource,omitempty"`
ActorSource string `json:"actor_source"`
Visibility string `json:"visibility"`
Transaction string `json:"transaction"`
FailureStrategy string `json:"failure_strategy"`
SensitivePolicy string `json:"sensitive_policy"`
BeforeAfterPolicy string `json:"before_after_policy"`
TestSeam string `json:"test_seam"`
NAReason string `json:"na_reason,omitempty"`
}
// Scan 扫描当前仓库中对外 HTTP、Asynq Worker 和定时任务注册入口。
func Scan(root string) ([]Entry, error) {
var entries []Entry
files := []string{
"internal/routes", "internal/application", "internal/domain", "internal/service",
"internal/handler", "internal/infrastructure", "internal/polling", "pkg/queue", "cmd/worker",
}
for _, directory := range files {
err := filepath.Walk(filepath.Join(root, directory), func(path string, info os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
if info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
found, err := scanFile(root, path)
if err != nil {
return err
}
entries = append(entries, found...)
return nil
})
if err != nil {
return nil, err
}
}
sort.Slice(entries, func(i, j int) bool { return entries[i].Key < entries[j].Key })
return entries, nil
}
// LoadManifest 读取经评审的显式覆盖快照。
func LoadManifest(path string) ([]Entry, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var entries []Entry
if err := sonic.Unmarshal(data, &entries); err != nil {
return nil, err
}
return entries, nil
}
// MarshalManifest 将扫描结果输出为稳定、便于评审的 JSON。
func MarshalManifest(entries []Entry) ([]byte, error) {
return sonic.ConfigStd.MarshalIndent(entries, "", " ")
}
func scanFile(root, path string) ([]Entry, error) {
set := token.NewFileSet()
file, err := parser.ParseFile(set, path, nil, 0)
if err != nil {
return nil, err
}
relative, err := filepath.Rel(root, path)
if err != nil {
return nil, err
}
var entries []Entry
ast.Inspect(file, func(node ast.Node) bool {
call, ok := node.(*ast.CallExpr)
if !ok {
return true
}
position := set.Position(call.Pos())
if identifier, ok := call.Fun.(*ast.Ident); ok && identifier.Name == "Register" && len(call.Args) >= 7 {
method, methodOK := stringLiteral(call.Args[3])
pathSuffix, pathOK := stringLiteral(call.Args[4])
if !pathOK {
pathSuffix = expression(call.Args[4])
}
if methodOK {
entry := classifyHTTP(relative, position.Line, method, pathSuffix, expression(call.Args[5]), routeSummary(call.Args[6]))
entries = append(entries, entry)
}
return true
}
selector, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
switch selector.Sel.Name {
case "HandleFunc":
if len(call.Args) >= 2 {
entries = append(entries, classifyWorker(relative, position.Line, expression(call.Args[0]), expression(call.Args[1])))
}
case "Register":
if strings.HasPrefix(relative, "cmd/worker/") {
if taskType, schedule, ok := scheduledTask(call); ok {
entries = append(entries, classifySchedule(relative, position.Line, taskType, schedule))
} else if strings.Contains(expression(selector.X), "outboxConsumers") && len(call.Args) >= 2 {
entries = append(entries, classifyOutboxConsumer(relative, position.Line, expression(call.Args[0]), expression(call.Args[1])))
}
}
case "LogOperation":
entries = append(entries, classifyLegacyWriter(relative, position.Line, expression(call.Fun)))
case "Start", "Complete", "RecordInbound", "ClaimExpiredInboundPending":
if selector.Sel.Name == "ClaimExpiredInboundPending" || isIntegrationLogCall(relative, expression(selector.X)) {
entries = append(entries, classifyIntegrationLog(relative, position.Line, expression(call.Fun)))
}
}
return true
})
if strings.HasPrefix(relative, "internal/application/") || strings.HasPrefix(relative, "internal/domain/") ||
strings.HasPrefix(relative, "internal/service/") {
for _, declaration := range file.Decls {
function, ok := declaration.(*ast.FuncDecl)
if !ok || function.Recv == nil || !isBusinessMethod(function) {
continue
}
position := set.Position(function.Pos())
entries = append(entries, classifyBusinessMethod(relative, position.Line, function.Name.Name))
}
}
return entries, nil
}
func classifyHTTP(file string, line int, method, path, handler, summary string) Entry {
owner := strings.TrimSuffix(filepath.Base(file), ".go")
if summary == "" {
summary = handler
}
entry := Entry{
Key: fmt.Sprintf("http:%s:%d:%s:%s", file, line, method, path), Kind: "http",
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, handler), Owner: owner,
Method: method, Path: path, Summary: summary, ActorSource: httpActorSource(file, path, handler),
DomainLedger: ledgerDecision(owner), IntegrationLog: integrationDecision(file, path),
Outbox: "按用例是否存在提交后可靠副作用决定;无可靠副作用时 N/A",
Visibility: httpVisibility(file, path, handler),
SensitivePolicy: "禁止字段删除手机号、IP、ICCID、金额和第三方单号按权限脱敏单字段 16KB 上限",
BeforeAfterPolicy: "写操作保存脱敏后的直接字段变化;批量命令保存摘要和权威明细引用",
TestSeam: "真实 Fiber + Application/Service 公共用例 + PostgreSQL 事实;覆盖门禁静态比对本入口",
}
if isReadOnlyHTTP(method, path) && !isSensitiveRead(file, path, summary) {
entry.AuditEvent = "N/A"
entry.Transaction = "N/A"
entry.FailureStrategy = "Access Log 记录统一错误;普通读取不创建业务审计"
entry.NAReason = "普通只读查询,不改变业务事实且不返回需二次授权的完整敏感值"
return entry
}
entry.AuditEvent = "必须"
entry.ActionCode = actionCode(owner, handler)
entry.ActionName = summary
entry.Category = categoryFor(owner)
entry.Risk = riskFor(owner, path, summary)
entry.PrimaryResource = owner
entry.AffectedResource = "由对应 Application/Service 用例按直接影响资源显式填写,禁止递归扩展"
entry.Transaction = "成功事件与关键业务事实同一 GORM 事务;敏感读取在返回前写入"
entry.FailureStrategy = "业务回滚后的 failed/denied 使用独立短事务;二次失败保留业务错误并记录 critical"
return entry
}
func classifyWorker(file string, line int, taskType, handler string) Entry {
owner := workerOwner(taskType)
entry := Entry{
Key: fmt.Sprintf("worker:%s:%d:%s", file, line, taskType), Kind: "worker",
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, handler), Owner: owner,
Summary: "处理异步任务 " + taskType, AuditEvent: "按状态变化、人工触发、连续失败或高风险异常决定",
DomainLedger: ledgerDecision(owner), IntegrationLog: workerIntegrationDecision(taskType),
Outbox: "任务来源 Outbox/业务任务事实;消费端按稳定事件或任务 ID 幂等",
ActionCode: actionCode(owner, handler), ActionName: "处理异步任务(" + taskType + "",
Category: categoryFor(owner), Risk: riskFor(owner, taskType, handler),
PrimaryResource: owner, AffectedResource: "任务载荷定位的直接业务资源",
ActorSource: "system_task/asynq", Transaction: "业务状态变化、领域流水和 Audit Event 按用例原子提交",
Visibility: "内部系统入口;外部主体只读取对应业务安全投影",
FailureStrategy: "Worker 返回错误由公共重试恢复;终态失败保存中文安全摘要,禁止裸 goroutine 审计",
SensitivePolicy: "不记录完整任务载荷、文件内容、外部正文、凭证或签名 URL",
BeforeAfterPolicy: "状态变化保存直接前后值;无业务变化时仅保留 Integration Log",
TestSeam: "公开 Asynq Handler + PostgreSQL/Redis 事实 + 重复消费幂等数据核对;覆盖门禁静态比对本入口",
}
return entry
}
func classifySchedule(file string, line int, taskType, schedule string) Entry {
return Entry{
Key: fmt.Sprintf("schedule:%s:%d:%s", file, line, taskType), Kind: "scheduled_job",
CodeEntry: fmt.Sprintf("%s:%d", file, line), Owner: workerOwner(taskType), Summary: "按 " + schedule + " 调度 " + taskType,
AuditEvent: "N/A", DomainLedger: "N/A", IntegrationLog: "N/A", Outbox: "N/A",
ActorSource: "system_task/scheduled_job", Transaction: "N/A",
Visibility: "内部系统入口,不直接对用户展示",
FailureStrategy: "调度注册失败阻止 Worker 启动;执行结果由对应 Worker 入口负责",
SensitivePolicy: "调度日志仅记录任务类型与安全时间信息",
BeforeAfterPolicy: "N/A调度入口不修改业务事实",
TestSeam: "调度注册公开函数 + 覆盖门禁静态比对本入口",
NAReason: "本入口只产生调度信号,不直接读取或修改业务事实;审计责任位于对应 Worker",
}
}
func classifyOutboxConsumer(file string, line int, eventType, consumer string) Entry {
return Entry{
Key: fmt.Sprintf("outbox_consumer:%s:%d:%s", file, line, eventType), Kind: "outbox_consumer",
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, consumer), Owner: workerOwner(eventType),
Summary: "注册 Outbox 消费者 " + eventType,
AuditEvent: "N/A", DomainLedger: "N/A", IntegrationLog: "N/A", Outbox: "必须:消费已提交的可靠事件",
ActorSource: "system_task/outbox_consumer", Transaction: "N/A",
Visibility: "内部系统装配入口,不直接对用户展示",
FailureStrategy: "注册失败阻止 Worker 启动;实际消费失败由 Outbox 重试,业务审计由消费者用例负责",
SensitivePolicy: "注册入口不读取或记录事件载荷与安全凭据",
BeforeAfterPolicy: "N/A注册入口不修改业务事实",
TestSeam: "静态扫描注册点、消费者实现和对应业务动作",
NAReason: "本入口只注册事件类型与消费者;实际业务事实和 Audit Event 由对应 Consumer/Application 完整用例负责",
}
}
func classifyBusinessMethod(file string, line int, method string) Entry {
parts := strings.Split(file, "/")
layer := parts[1]
owner := strings.TrimSuffix(filepath.Base(filepath.Dir(file)), ".go")
if owner == "service" || owner == "application" || owner == "domain" {
owner = strings.TrimSuffix(filepath.Base(file), ".go")
}
entry := Entry{
Key: fmt.Sprintf("%s:%s:%d:%s", layer, file, line, method), Kind: layer,
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, method), Owner: owner,
Summary: "业务方法 " + method, DomainLedger: ledgerDecision(owner),
IntegrationLog: businessIntegrationDecision(file, method),
Outbox: "存在提交后可靠副作用时必须在同一事务追加;否则 N/A",
ActionCode: actionCode(owner, method), Risk: riskFor(owner, file, method),
PrimaryResource: owner, AffectedResource: "完整用例直接修改或引用的资源",
ActorSource: "由调用入口传入操作者与来源快照",
Visibility: "由完整用例决定平台完整视图、主体安全投影或 internal_only",
SensitivePolicy: "禁止字段删除;受控字段脱敏;批量明细留在领域任务或制品",
BeforeAfterPolicy: "完整用例保存脱敏后的直接业务变化Domain 方法由 Application 投影",
TestSeam: "Application/Service 公共方法 + PostgreSQL 事实Domain 使用静态检查与数据核对;覆盖门禁静态比对本入口",
}
if layer == "domain" {
entry.AuditEvent = "N/A"
entry.Transaction = "由 Application 组合根负责"
entry.FailureStrategy = "返回领域错误,由 Application 在回滚后裁决 failed/denied 审计"
entry.NAReason = "Domain 只维护业务不变量和领域事实不依赖审计基础设施Audit Event 由 Application 写入"
entry.ActionCode = ""
entry.ActionName = ""
entry.Category = ""
entry.Risk = ""
entry.PrimaryResource = ""
return entry
}
entry.AuditEvent = "必须"
entry.ActionName = "执行业务方法(" + method + ""
entry.Category = categoryFor(owner)
entry.Transaction = "关键成功事件与业务事实同一 GORM 事务"
entry.FailureStrategy = "业务回滚后的 failed/denied 使用独立短事务;审计二次失败记录 critical"
return entry
}
func classifyLegacyWriter(file string, line int, call string) Entry {
return Entry{
Key: fmt.Sprintf("legacy_writer:%s:%d:%s", file, line, call), Kind: "legacy_writer",
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, call), Owner: filepath.Base(filepath.Dir(file)),
Summary: "调用旧 Operation Log Writer", AuditEvent: "必须迁移到统一 Audit Event 后停写旧表",
DomainLedger: "既有业务表仍是权威事实,旧 Operation Log 不是 Domain Ledger",
IntegrationLog: "N/A旧 Writer 仅记录内部操作;实际外部交互由 Integration Log 单独记录",
Outbox: "由原完整用例决定,旧 Writer 不得替代 Outbox",
ActionCode: actionCode(filepath.Base(filepath.Dir(file)), call), ActionName: "迁移旧审计写入",
Category: categoryFor(file), Risk: riskFor(file, call, ""), PrimaryResource: filepath.Base(filepath.Dir(file)),
AffectedResource: "按原完整业务用例登记实际资源", ActorSource: "沿用原调用入口真实操作者",
Visibility: "旧表仅保留平台历史入口;新事件按 Registry 生成主体安全投影",
Transaction: "迁移后关键成功与业务事实同事务,旧异步 Writer 停写",
FailureStrategy: "迁移后业务回滚的 failed/denied 使用独立短事务;禁止裸 goroutine",
SensitivePolicy: "迁移时删除密码、Token、Secret、私钥、Cookie、签名 URL 等安全凭据",
BeforeAfterPolicy: "按资源保存本次直接变化,不复制旧单体 JSON",
TestSeam: "静态调用归零扫描 + 对应业务入口与数据库抽样核对",
}
}
func classifyIntegrationLog(file string, line int, call string) Entry {
return Entry{
Key: fmt.Sprintf("integration_log:%s:%d:%s", file, line, call), Kind: "integration_log",
CodeEntry: fmt.Sprintf("%s:%d %s", file, line, call), Owner: filepath.Base(filepath.Dir(file)),
Summary: "记录外部交互尝试或终态", AuditEvent: "N/A",
DomainLedger: "N/AIntegration Log 只记录外部交互事实,不替代内部业务表",
IntegrationLog: "必须:保存实际请求、未发送裁决、入站回调或终态安全摘要",
Outbox: "存在提交后可靠副作用时由业务用例另行登记;本调用点不替代 Outbox",
ActorSource: "external_system 或发起外呼的真实 Application/Worker/Callback",
Visibility: "仅平台内部调查完整可见;代理/企业不得读取外部交互细节",
Transaction: "按外部尝试生命周期写入;内部状态变化另由业务事务记录 Audit Event",
FailureStrategy: "保留真实 failed/unknown/not_sent 结果,不把记录失败伪装成业务成功",
SensitivePolicy: "请求、响应和 metadata 写入前删除凭据,历史读取再次清理",
BeforeAfterPolicy: "N/A保存外部尝试结构化摘要和本地状态是否变化",
TestSeam: "Integration Log 数据抽样 + 调用链 correlation/series/attempt 核对",
NAReason: "该入口只记录外部交互事实;只有改变内部业务事实时才由业务用例另写 Audit Event",
}
}
func routeSummary(expr ast.Expr) string {
composite, ok := expr.(*ast.CompositeLit)
if !ok {
return ""
}
for _, element := range composite.Elts {
pair, ok := element.(*ast.KeyValueExpr)
if !ok || expression(pair.Key) != "Summary" {
continue
}
value, _ := stringLiteral(pair.Value)
return value
}
return ""
}
func scheduledTask(call *ast.CallExpr) (string, string, bool) {
if len(call.Args) < 2 {
return "", "", false
}
schedule, _ := stringLiteral(call.Args[0])
taskCall, ok := call.Args[1].(*ast.CallExpr)
if !ok {
return "", "", false
}
selector, ok := taskCall.Fun.(*ast.SelectorExpr)
if !ok || selector.Sel.Name != "NewTask" || len(taskCall.Args) == 0 {
return "", "", false
}
return expression(taskCall.Args[0]), schedule, true
}
func stringLiteral(expr ast.Expr) (string, bool) {
literal, ok := expr.(*ast.BasicLit)
if !ok || literal.Kind != token.STRING {
return "", false
}
value, err := strconv.Unquote(literal.Value)
return value, err == nil
}
func expression(expr ast.Expr) string {
switch value := expr.(type) {
case *ast.Ident:
return value.Name
case *ast.SelectorExpr:
return expression(value.X) + "." + value.Sel.Name
case *ast.BasicLit:
return value.Value
case *ast.CallExpr:
return expression(value.Fun)
case *ast.BinaryExpr:
return expression(value.X) + value.Op.String() + expression(value.Y)
default:
return fmt.Sprintf("%T", expr)
}
}
func httpActorSource(file, path, handler string) string {
text := strings.ToLower(file + " " + path + " " + handler)
switch {
case strings.Contains(text, "callback") || strings.Contains(path, "/carriers/"):
return "external_system/callback"
case strings.HasSuffix(file, "personal.go"):
return "personal_customer/personal_api"
case strings.HasSuffix(file, "order.go"):
return "按路由分为 admin_user/admin_api 或外部回调入口"
default:
return "登录账号快照/admin_api"
}
}
func httpVisibility(file, path, handler string) string {
text := strings.ToLower(file + " " + path + " " + handler)
switch {
case strings.Contains(text, "callback"):
return "外部回调入口;只记录内部完整事实,不直接向外部主体展示"
case strings.Contains(text, "/audit"):
return "仅超级管理员和平台账号可见"
case strings.Contains(text, "enterprise"):
return "企业认证上下文范围内可见;内部审计字段不可见"
case strings.Contains(text, "personal"):
return "当前个人客户本人范围内可见"
default:
return "按认证账号类型和现有数据权限可见;审计调查另按平台/主体投影隔离"
}
}
func isIntegrationLogCall(file, receiver string) bool {
if strings.Contains(file, "/integrationlog/") {
return false
}
receiver = strings.ToLower(receiver)
return strings.Contains(receiver, "integration")
}
func isSensitiveRead(file, path, summary string) bool {
text := strings.ToLower(file + " " + path + " " + summary)
for _, marker := range []string{"download", "realname-link", "realname/link", "实名链接", "敏感", "realtime-status"} {
if strings.Contains(text, marker) {
return true
}
}
return strings.HasSuffix(file, "wecom.go") && path == "/applications" ||
strings.HasSuffix(file, "export_task.go") && path == "/:id"
}
func isReadOnlyHTTP(method, path string) bool {
if method == "GET" {
return true
}
return strings.Contains(path, "purchase-check") || strings.Contains(path, "verify-asset")
}
func integrationDecision(file, path string) string {
text := strings.ToLower(file + " " + path)
if strings.Contains(text, "callback") || strings.HasSuffix(file, "order.go") &&
(strings.Contains(path, "pay") || strings.Contains(path, "alipay")) {
return "必须:业务处理前保存入站安全摘要与幂等标识"
}
return "无外部交互时 N/A用例调用 Gateway、支付、企微或运营商时必须"
}
func workerIntegrationDecision(taskType string) string {
text := strings.ToLower(taskType)
if strings.Contains(text, "polling") {
return "必须:每次实际请求或未发送裁决均记录"
}
return "Worker 调用外部系统时必须;纯本地处理 N/A"
}
func businessIntegrationDecision(file, method string) string {
text := strings.ToLower(file + " " + method)
for _, marker := range []string{"polling", "gateway", "payment", "wechat", "wecom", "carrier", "sms"} {
if strings.Contains(text, marker) {
return "调用外部系统或处理回调时必须;纯本地分支 N/A"
}
}
return "N/A当前方法按代码位置属于本地业务用例后续新增外部调用必须重新分类"
}
func ledgerDecision(owner string) string {
for _, marker := range []string{"order", "recharge", "refund", "commission", "wallet"} {
if strings.Contains(owner, marker) {
return "必须:订单、充值、退款、钱包流水等既有业务表是领域权威"
}
}
if strings.Contains(owner, "import") || strings.Contains(owner, "export") {
return "业务任务及明细表是批量结果权威"
}
return "既有业务表是状态事实Audit Event 不替代业务模型"
}
func riskFor(owner, path, summary string) string {
text := strings.ToLower(owner + " " + path + " " + summary)
for _, marker := range []string{"wallet", "refund", "recharge", "permission", "role", "password", "config", "权限", "资金", "退款", "充值"} {
if strings.Contains(text, marker) {
return "high"
}
}
return "normal"
}
func categoryFor(owner string) string {
text := strings.ToLower(owner)
for _, marker := range []string{"wallet", "refund", "recharge", "commission", "order"} {
if strings.Contains(text, marker) {
return "finance"
}
}
for _, marker := range []string{"account", "role", "permission", "auth"} {
if strings.Contains(text, marker) {
return "security"
}
}
for _, marker := range []string{"card", "device", "asset", "polling"} {
if strings.Contains(text, marker) {
return "asset"
}
}
return "business"
}
func actionCode(owner, handler string) string {
method := handler
if index := strings.LastIndex(method, "."); index >= 0 {
method = method[index+1:]
}
return normalize(owner) + "." + normalize(method)
}
func workerOwner(taskType string) string {
return normalize(strings.TrimPrefix(taskType, "constants.TaskType"))
}
func isBusinessMethod(function *ast.FuncDecl) bool {
name := function.Name.Name
if strings.HasPrefix(name, "Set") && !hasContextParameter(function) {
return false
}
for _, prefix := range []string{
"Create", "Update", "Delete", "Set", "Assign", "Remove", "Cancel", "Reject", "Approve",
"Import", "Allocate", "Recall", "Stop", "Resume", "Bind", "Unbind", "Reset", "Activate",
"Deactivate", "Trigger", "Handle", "Process", "Execute", "Replay", "Release", "Change", "Pay",
"Refund", "Recharge", "Withdraw", "Grant", "Revoke", "Deduct", "Credit", "Debit", "Freeze",
"Unfreeze", "Resolve", "Expire", "Invalidate", "Archive", "Cleanup", "Adjust", "Add", "Batch",
"Enable", "Disable", "Login", "Logout", "Refresh", "Upload", "Download", "Save", "Restore",
"Submit", "Sync", "Migrate", "Send",
} {
if strings.HasPrefix(name, prefix) {
return true
}
}
return false
}
func hasContextParameter(function *ast.FuncDecl) bool {
if function.Type.Params == nil {
return false
}
for _, field := range function.Type.Params.List {
selector, ok := field.Type.(*ast.SelectorExpr)
if ok && expression(selector) == "context.Context" {
return true
}
}
return false
}
func normalize(value string) string {
value = strings.Trim(value, "\"")
var output []rune
for index, current := range []rune(value) {
if current >= 'A' && current <= 'Z' {
if index > 0 {
output = append(output, '_')
}
current += 'a' - 'A'
}
if current == '-' || current == ':' || current == '/' {
current = '_'
}
output = append(output, current)
}
return strings.Trim(strings.ReplaceAll(string(output), "__", "_"), "_")
}