// Package idverify 身份证二要素(姓名 + 身份证号)实名核验。 // // 与其它 sys 子系统不同,本包**不是启动时初始化的单例**:核验服务配在后台 // 「第三方服务配置」里(类别 comm.SvcCatIdVerify),按应用作用域存在 svc_config, // 调用时才解析出凭据。所以这里只提供无状态的工厂 + 接口,由业务侧每次带配置进来。 // // ⚠️ 这类服务的凭据是**云账号主 AK/SK**,comm.IsServerOnlySvc 会保证它们 // 永不随 user_getthirdsvcs / user_getappconfig 下发给客户端。 package idverify import ( "context" "errors" "fmt" "strings" ) // 服务商标识(与 console 内置模板 ThirdSvcTemplate.Provider 一致)。 const ( ProviderAliyun = "aliyun" ProviderTencent = "tencent" ProviderChuanglan = "chuanglan" ) var ( // ErrUnsupportedProvider 配了本包不认识的服务商。 ErrUnsupportedProvider = errors.New("idverify: 不支持的服务商") // ErrMissingCredential 服务商凭据字段缺失。 ErrMissingCredential = errors.New("idverify: 凭据字段缺失") ) // Result 一次核验的结果。 // // 注意区分两种"失败": // - Matched=false 且 err=nil:服务商**明确判定不一致**(正常业务结果,已计费)。 // - err!=nil:调用本身失败(网络/鉴权/额度耗尽/参数被拒),**结论未知**,不能当作"不一致"。 // // 把后者当成"不一致"会让用户在服务商欠费时看到"您填的信息有误",是最难排查的一类线上问题。 type Result struct { Matched bool // 姓名与身份证号是否一致 BizCode string // 服务商返回的原始结果码(阿里 ResultObject.BizCode / 腾讯 Result / 创蓝 data.result),便于对账排查 Message string // 服务商返回的可读说明 } // Verifier 一个已装配好凭据的核验客户端。 type Verifier interface { // Verify 执行二要素核验。realname/idcardno 为明文,调用方负责不落库。 Verify(ctx context.Context, realname, idcardno string) (Result, error) // Provider 返回服务商标识,用于落库记录来源。 Provider() string } // New 按服务商与字段表装配一个核验客户端。 // fields 来自 comm.ResolveSvcPlainFields 的解密结果(键名与 console 内置模板一致)。 func New(provider string, fields map[string]string) (Verifier, error) { switch strings.TrimSpace(strings.ToLower(provider)) { case ProviderAliyun: return newAliyun(fields) case ProviderTencent: return newTencent(fields) case ProviderChuanglan: return newChuanglan(fields) default: return nil, fmt.Errorf("%w: %s", ErrUnsupportedProvider, provider) } } // field 取字段并去空白;缺失返回空串。 func field(fields map[string]string, key string) string { return strings.TrimSpace(fields[key]) } // requireFields 校验必填凭据字段齐全,返回第一个缺失的字段名。 func requireFields(fields map[string]string, keys ...string) error { for _, k := range keys { if field(fields, k) == "" { return fmt.Errorf("%w: %s", ErrMissingCredential, k) } } return nil }