实名认证对接--ok
This commit is contained in:
@@ -24,3 +24,7 @@ ALIYUN_ACCESS_KEY_SECRET=
|
|||||||
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
|
ALIYUN_SMS_ENDPOINT=dysmsapi.aliyuncs.com
|
||||||
ALIYUN_SMS_SIGN_NAME=
|
ALIYUN_SMS_SIGN_NAME=
|
||||||
ALIYUN_SMS_LOGIN_TEMPLATE_CODE=
|
ALIYUN_SMS_LOGIN_TEMPLATE_CODE=
|
||||||
|
|
||||||
|
REALNAME_PROVIDER=mock
|
||||||
|
REALNAME_CLOUDMARKET_URL=https://sinocheck2.market.alicloudapi.com/fortest/ttttt
|
||||||
|
REALNAME_CLOUDMARKET_APPCODE=
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Config struct {
|
|||||||
JWTSecret string
|
JWTSecret string
|
||||||
Storage StorageConfig
|
Storage StorageConfig
|
||||||
SMS SMSConfig
|
SMS SMSConfig
|
||||||
|
Realname RealnameConfig
|
||||||
Log LogConfig
|
Log LogConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,12 @@ type SMSConfig struct {
|
|||||||
AliyunLoginTemplateCode string
|
AliyunLoginTemplateCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RealnameConfig struct {
|
||||||
|
Provider string
|
||||||
|
CloudMarketURL string
|
||||||
|
CloudMarketAppCode string
|
||||||
|
}
|
||||||
|
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
Level string
|
Level string
|
||||||
Dir string
|
Dir string
|
||||||
@@ -64,6 +71,11 @@ func Load() Config {
|
|||||||
AliyunSignName: getEnv("ALIYUN_SMS_SIGN_NAME", ""),
|
AliyunSignName: getEnv("ALIYUN_SMS_SIGN_NAME", ""),
|
||||||
AliyunLoginTemplateCode: getEnv("ALIYUN_SMS_LOGIN_TEMPLATE_CODE", ""),
|
AliyunLoginTemplateCode: getEnv("ALIYUN_SMS_LOGIN_TEMPLATE_CODE", ""),
|
||||||
},
|
},
|
||||||
|
Realname: RealnameConfig{
|
||||||
|
Provider: getEnv("REALNAME_PROVIDER", "mock"),
|
||||||
|
CloudMarketURL: getEnv("REALNAME_CLOUDMARKET_URL", "https://sinocheck2.market.alicloudapi.com/fortest/ttttt"),
|
||||||
|
CloudMarketAppCode: getEnv("REALNAME_CLOUDMARKET_APPCODE", ""),
|
||||||
|
},
|
||||||
Log: LogConfig{
|
Log: LogConfig{
|
||||||
Level: getEnv("LOG_LEVEL", "info"),
|
Level: getEnv("LOG_LEVEL", "info"),
|
||||||
Dir: getEnv("LOG_DIR", "logs"),
|
Dir: getEnv("LOG_DIR", "logs"),
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package realname
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cloudMarketProviderName = "aliyun_cloudmarket"
|
||||||
|
|
||||||
|
type CloudMarketConfig struct {
|
||||||
|
URL string
|
||||||
|
AppCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloudMarketProvider struct {
|
||||||
|
url string
|
||||||
|
appCode string
|
||||||
|
client *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloudMarketResponse struct {
|
||||||
|
RequestID string `json:"request_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Result json.RawMessage `json:"result"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloudMarketProvider(cfg CloudMarketConfig) (*CloudMarketProvider, error) {
|
||||||
|
endpoint := strings.TrimSpace(cfg.URL)
|
||||||
|
appCode := strings.TrimSpace(cfg.AppCode)
|
||||||
|
if endpoint == "" || appCode == "" {
|
||||||
|
return nil, ErrProviderConfigInvalid
|
||||||
|
}
|
||||||
|
if _, err := url.ParseRequestURI(endpoint); err != nil {
|
||||||
|
return nil, fmt.Errorf("realname cloud market url invalid: %w", err)
|
||||||
|
}
|
||||||
|
return &CloudMarketProvider{
|
||||||
|
url: endpoint,
|
||||||
|
appCode: appCode,
|
||||||
|
client: &http.Client{Timeout: 10 * time.Second},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CloudMarketProvider) Name() string {
|
||||||
|
return cloudMarketProviderName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CloudMarketProvider) Start(ctx context.Context, req StartRequest) (ProviderResult, error) {
|
||||||
|
endpoint, err := url.Parse(p.url)
|
||||||
|
if err != nil {
|
||||||
|
return ProviderResult{}, err
|
||||||
|
}
|
||||||
|
query := endpoint.Query()
|
||||||
|
query.Set("name", strings.TrimSpace(req.Name))
|
||||||
|
query.Set("id_number", strings.TrimSpace(req.IDNo))
|
||||||
|
endpoint.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return ProviderResult{}, err
|
||||||
|
}
|
||||||
|
httpReq.Header.Set("Authorization", "APPCODE "+p.appCode)
|
||||||
|
httpReq.Header.Set("Accept", "application/json")
|
||||||
|
|
||||||
|
resp, err := p.client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return ProviderResult{}, fmt.Errorf("realname cloud market request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusForbidden {
|
||||||
|
return ProviderResult{}, ErrProviderRateLimited
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||||
|
if err != nil {
|
||||||
|
return ProviderResult{}, fmt.Errorf("%w: read cloud market response: %v", ErrProviderUnavailable, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload cloudMarketResponse
|
||||||
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
|
return ProviderResult{}, fmt.Errorf("%w: invalid cloud market response: %v", ErrProviderUnavailable, err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
|
||||||
|
return ProviderResult{}, cloudMarketHTTPError(resp.StatusCode, payload.Reason)
|
||||||
|
}
|
||||||
|
if strings.ToUpper(strings.TrimSpace(payload.Status)) != "OK" {
|
||||||
|
return ProviderResult{
|
||||||
|
ProviderOrderNo: payload.RequestID,
|
||||||
|
Status: StatusRejected,
|
||||||
|
MaskedName: maskName(req.Name),
|
||||||
|
MaskedIDNo: maskIDNo(req.IDNo),
|
||||||
|
FailReason: firstNonEmpty(payload.Reason, "实名认证请求失败"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := parseCloudMarketResult(payload.Result)
|
||||||
|
if err != nil {
|
||||||
|
return ProviderResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
base := ProviderResult{
|
||||||
|
ProviderOrderNo: payload.RequestID,
|
||||||
|
MaskedName: maskName(req.Name),
|
||||||
|
MaskedIDNo: maskIDNo(req.IDNo),
|
||||||
|
}
|
||||||
|
switch result {
|
||||||
|
case 1:
|
||||||
|
base.Status = StatusVerified
|
||||||
|
base.VerifiedAt = &now
|
||||||
|
case 2:
|
||||||
|
base.Status = StatusRejected
|
||||||
|
base.FailReason = "姓名与身份证号不一致"
|
||||||
|
case 3:
|
||||||
|
base.Status = StatusRejected
|
||||||
|
base.FailReason = "公安库无匹配记录"
|
||||||
|
case 110:
|
||||||
|
return ProviderResult{}, ErrProviderRateLimited
|
||||||
|
default:
|
||||||
|
base.Status = StatusRejected
|
||||||
|
base.FailReason = fmt.Sprintf("未知核验结果:%d", result)
|
||||||
|
}
|
||||||
|
return base, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudMarketHTTPError(statusCode int, reason string) error {
|
||||||
|
reason = strings.TrimSpace(reason)
|
||||||
|
switch statusCode {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return fmt.Errorf("%w: %s", ErrInvalidRealnameInput, firstNonEmpty(reason, "cloud market bad request"))
|
||||||
|
case http.StatusUnauthorized, http.StatusNotFound:
|
||||||
|
return fmt.Errorf("%w: cloud market http status %d: %s", ErrProviderConfigInvalid, statusCode, reason)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%w: cloud market http status %d: %s", ErrProviderUnavailable, statusCode, reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCloudMarketResult(raw json.RawMessage) (int, error) {
|
||||||
|
if len(raw) == 0 || string(raw) == "null" {
|
||||||
|
return 0, fmt.Errorf("realname cloud market result missing")
|
||||||
|
}
|
||||||
|
var number int
|
||||||
|
if err := json.Unmarshal(raw, &number); err == nil {
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
|
var text string
|
||||||
|
if err := json.Unmarshal(raw, &text); err == nil {
|
||||||
|
switch strings.TrimSpace(text) {
|
||||||
|
case "1":
|
||||||
|
return 1, nil
|
||||||
|
case "2":
|
||||||
|
return 2, nil
|
||||||
|
case "3":
|
||||||
|
return 3, nil
|
||||||
|
case "110":
|
||||||
|
return 110, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("realname cloud market result invalid: %s", string(raw))
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonEmpty(values ...string) string {
|
||||||
|
for _, value := range values {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package realname
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCloudMarketProviderStartVerified(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
t.Fatalf("请求方法不正确:%s", r.Method)
|
||||||
|
}
|
||||||
|
if r.Header.Get("Authorization") != "APPCODE test-code" {
|
||||||
|
t.Fatalf("APPCODE 请求头不正确:%s", r.Header.Get("Authorization"))
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("name") != "张三" {
|
||||||
|
t.Fatalf("姓名参数不正确:%s", r.URL.Query().Get("name"))
|
||||||
|
}
|
||||||
|
if r.URL.Query().Get("id_number") != "110101199001011234" {
|
||||||
|
t.Fatalf("身份证参数不正确:%s", r.URL.Query().Get("id_number"))
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"request_id":"TID123","status":"OK","result":1}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||||
|
URL: server.URL,
|
||||||
|
AppCode: "test-code",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 Provider 失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := provider.Start(t.Context(), StartRequest{
|
||||||
|
UserID: 1,
|
||||||
|
Name: "张三",
|
||||||
|
IDNo: "110101199001011234",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("实名认证请求失败:%v", err)
|
||||||
|
}
|
||||||
|
if result.Status != StatusVerified {
|
||||||
|
t.Fatalf("状态不正确:%s", result.Status)
|
||||||
|
}
|
||||||
|
if result.ProviderOrderNo != "TID123" {
|
||||||
|
t.Fatalf("请求流水不正确:%s", result.ProviderOrderNo)
|
||||||
|
}
|
||||||
|
if result.VerifiedAt == nil {
|
||||||
|
t.Fatal("通过时间不能为空")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudMarketProviderStartRejected(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = w.Write([]byte(`{"request_id":"TID456","status":"OK","result":"2"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||||
|
URL: server.URL,
|
||||||
|
AppCode: "test-code",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 Provider 失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := provider.Start(t.Context(), StartRequest{
|
||||||
|
UserID: 1,
|
||||||
|
Name: "李四",
|
||||||
|
IDNo: "110101199001011234",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("实名认证请求失败:%v", err)
|
||||||
|
}
|
||||||
|
if result.Status != StatusRejected {
|
||||||
|
t.Fatalf("状态不正确:%s", result.Status)
|
||||||
|
}
|
||||||
|
if result.FailReason == "" {
|
||||||
|
t.Fatal("拒绝原因不能为空")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloudMarketProviderStartRateLimited(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, _ = w.Write([]byte(`{"request_id":"TID789","status":"OK","result":110}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
provider, err := NewCloudMarketProvider(CloudMarketConfig{
|
||||||
|
URL: server.URL,
|
||||||
|
AppCode: "test-code",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("创建 Provider 失败:%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = provider.Start(t.Context(), StartRequest{
|
||||||
|
UserID: 1,
|
||||||
|
Name: "王五",
|
||||||
|
IDNo: "110101199001011234",
|
||||||
|
})
|
||||||
|
if !errors.Is(err, ErrProviderRateLimited) {
|
||||||
|
t.Fatalf("期望限流错误,实际:%v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,6 +74,12 @@ func writeRealnameError(c *gin.Context, err error) {
|
|||||||
response.ServiceUnavailable(c, "数据库未连接")
|
response.ServiceUnavailable(c, "数据库未连接")
|
||||||
case errors.Is(err, ErrInvalidRealnameInput):
|
case errors.Is(err, ErrInvalidRealnameInput):
|
||||||
response.BadRequest(c, "姓名或身份证号格式不正确")
|
response.BadRequest(c, "姓名或身份证号格式不正确")
|
||||||
|
case errors.Is(err, ErrProviderConfigInvalid):
|
||||||
|
response.ServiceUnavailable(c, "实名认证服务未配置")
|
||||||
|
case errors.Is(err, ErrProviderRateLimited):
|
||||||
|
response.Error(c, http.StatusTooManyRequests, "realname_rate_limited", "实名认证请求过于频繁,请稍后再试")
|
||||||
|
case errors.Is(err, ErrProviderUnavailable):
|
||||||
|
response.Error(c, http.StatusBadGateway, "realname_provider_unavailable", "实名认证服务调用失败,请稍后再试")
|
||||||
default:
|
default:
|
||||||
response.Error(c, http.StatusInternalServerError, "internal_error", "实名认证服务暂时不可用")
|
response.Error(c, http.StatusInternalServerError, "internal_error", "实名认证服务暂时不可用")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Provider interface {
|
type Provider interface {
|
||||||
|
Name() string
|
||||||
Start(ctx context.Context, req StartRequest) (ProviderResult, error)
|
Start(ctx context.Context, req StartRequest) (ProviderResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,6 +35,10 @@ func NewMockProvider() *MockProvider {
|
|||||||
return &MockProvider{}
|
return &MockProvider{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *MockProvider) Name() string {
|
||||||
|
return "mock"
|
||||||
|
}
|
||||||
|
|
||||||
func (p *MockProvider) Start(_ context.Context, req StartRequest) (ProviderResult, error) {
|
func (p *MockProvider) Start(_ context.Context, req StartRequest) (ProviderResult, error) {
|
||||||
if strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.IDNo) == "" {
|
if strings.TrimSpace(req.Name) == "" || strings.TrimSpace(req.IDNo) == "" {
|
||||||
return ProviderResult{}, errors.New("name and id no are required")
|
return ProviderResult{}, errors.New("name and id no are required")
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (r *Repository) SaveResult(userID uint64, provider string, result ProviderR
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
userStatus := "realname_failed"
|
userStatus := StatusRejected
|
||||||
if result.Status == StatusVerified {
|
if result.Status == StatusVerified {
|
||||||
userStatus = "verified"
|
userStatus = "verified"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,22 +7,28 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hfb_sys/backend/internal/model"
|
"hfb_sys/backend/internal/model"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StatusPending = "pending"
|
StatusPending = "pending"
|
||||||
StatusVerified = "verified"
|
StatusVerified = "verified"
|
||||||
StatusFailed = "failed"
|
StatusRejected = "rejected"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrDependencyUnavailable = errors.New("dependency unavailable")
|
ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||||
ErrInvalidRealnameInput = errors.New("invalid realname input")
|
ErrInvalidRealnameInput = errors.New("invalid realname input")
|
||||||
|
ErrProviderConfigInvalid = errors.New("realname provider config invalid")
|
||||||
|
ErrProviderRateLimited = errors.New("realname provider rate limited")
|
||||||
|
ErrProviderUnavailable = errors.New("realname provider unavailable")
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
repo *Repository
|
repo *Repository
|
||||||
provider Provider
|
provider Provider
|
||||||
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
type PublicStatus struct {
|
type PublicStatus struct {
|
||||||
@@ -35,8 +41,8 @@ type PublicStatus struct {
|
|||||||
FailReason string `json:"fail_reason,omitempty"`
|
FailReason string `json:"fail_reason,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(repo *Repository, provider Provider) *Service {
|
func NewService(repo *Repository, provider Provider, log *zap.Logger) *Service {
|
||||||
return &Service{repo: repo, provider: provider}
|
return &Service{repo: repo, provider: provider, log: log}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Start(ctx context.Context, userID uint64, name string, idNo string) (*model.UserRealname, error) {
|
func (s *Service) Start(ctx context.Context, userID uint64, name string, idNo string) (*model.UserRealname, error) {
|
||||||
@@ -54,9 +60,18 @@ func (s *Service) Start(ctx context.Context, userID uint64, name string, idNo st
|
|||||||
IDNo: idNo,
|
IDNo: idNo,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if s.log != nil {
|
||||||
|
s.log.Warn(
|
||||||
|
"realname provider verify failed",
|
||||||
|
zap.String("provider", s.provider.Name()),
|
||||||
|
zap.Uint64("user_id", userID),
|
||||||
|
zap.String("masked_id_no", maskIDNo(idNo)),
|
||||||
|
zap.Error(err),
|
||||||
|
)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return s.repo.SaveResult(userID, "mock", result)
|
return s.repo.SaveResult(userID, s.provider.Name(), result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Status(userID uint64) (PublicStatus, error) {
|
func (s *Service) Status(userID uint64) (PublicStatus, error) {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package realname
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type UnavailableProvider struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUnavailableProvider(err error) *UnavailableProvider {
|
||||||
|
return &UnavailableProvider{err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnavailableProvider) Name() string {
|
||||||
|
return "unavailable"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UnavailableProvider) Start(ctx context.Context, req StartRequest) (ProviderResult, error) {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return ProviderResult{}, err
|
||||||
|
}
|
||||||
|
if p.err != nil {
|
||||||
|
return ProviderResult{}, p.err
|
||||||
|
}
|
||||||
|
return ProviderResult{}, ErrProviderConfigInvalid
|
||||||
|
}
|
||||||
@@ -79,7 +79,7 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
|||||||
if deps.DB != nil {
|
if deps.DB != nil {
|
||||||
realnameRepo = realname.NewRepository(deps.DB)
|
realnameRepo = realname.NewRepository(deps.DB)
|
||||||
}
|
}
|
||||||
realnameService := realname.NewService(realnameRepo, realname.NewMockProvider())
|
realnameService := realname.NewService(realnameRepo, newRealnameProvider(cfg, logger), logger)
|
||||||
realnameHandler := realname.NewHandler(realnameService)
|
realnameHandler := realname.NewHandler(realnameService)
|
||||||
var listingRepo *listing.Repository
|
var listingRepo *listing.Repository
|
||||||
if deps.DB != nil {
|
if deps.DB != nil {
|
||||||
@@ -360,3 +360,20 @@ func newSMSProvider(cfg config.Config, logger *zap.Logger) smsintegration.Provid
|
|||||||
return smsintegration.NewMockProvider(logger)
|
return smsintegration.NewMockProvider(logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newRealnameProvider(cfg config.Config, logger *zap.Logger) realname.Provider {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(cfg.Realname.Provider)) {
|
||||||
|
case "cloudmarket", "aliyun_cloudmarket":
|
||||||
|
provider, err := realname.NewCloudMarketProvider(realname.CloudMarketConfig{
|
||||||
|
URL: cfg.Realname.CloudMarketURL,
|
||||||
|
AppCode: cfg.Realname.CloudMarketAppCode,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("cloud market realname provider unavailable; realname verify will fail", zap.Error(err))
|
||||||
|
return realname.NewUnavailableProvider(err)
|
||||||
|
}
|
||||||
|
return provider
|
||||||
|
default:
|
||||||
|
return realname.NewMockProvider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
接口信息
|
||||||
|
调用地址:
|
||||||
|
https://sinocheck2.market.alicloudapi.com/fortest/ttttt
|
||||||
|
|
||||||
|
请求方式:
|
||||||
|
POST
|
||||||
|
返回类型:
|
||||||
|
JSON
|
||||||
|
API 调用:
|
||||||
|
API 简单身份认证调用方法(APPCODE)
|
||||||
|
API 签名认证调用方法(AppKey & AppSecret)
|
||||||
|
|
||||||
|
获取认证:
|
||||||
|
AppKey & AppCode
|
||||||
|
接口参数
|
||||||
|
请求参数(Header)
|
||||||
|
请求参数(Query)
|
||||||
|
字段名称
|
||||||
|
必填
|
||||||
|
字段名称 必填 字段详情
|
||||||
|
name string
|
||||||
|
Y
|
||||||
|
姓名
|
||||||
|
|
||||||
|
id_number string
|
||||||
|
Y
|
||||||
|
身份证号
|
||||||
|
|
||||||
|
|
||||||
|
成功响应
|
||||||
|
{
|
||||||
|
"request_id": "TIDxxxxxxx", //请求ID,联系提供给客服,有助于问题排查
|
||||||
|
"status": "OK", //状态参数,OK为完成查询请求。
|
||||||
|
"result": 1 //身份核验结果,"result": 1(验证一致);2(不一致); 3 (库无);110 请求次数过多:同一身份证号一天内最多请求【10】次;
|
||||||
|
}
|
||||||
|
温馨提示:
|
||||||
|
1.当 "status": "OK"时,仅代表接口请求成功,"result"的返回值为身份核验结果
|
||||||
|
|
||||||
|
2.当 "result": 3 ,核验结果为'库无',有以下几种原因
|
||||||
|
(1)现役军人、武警官兵、特殊部门人员及特殊级别官员;
|
||||||
|
(2)退役不到2年的军人和士兵(根据军衔、兵种不同,时间会有所不同,一般为2年);
|
||||||
|
(3)户口迁出,且没有在新的迁入地迁入;
|
||||||
|
(4)户口迁入新迁入地,当地公安系统未将迁移信息上报到公安部(上报时间地域不同而有所差异);
|
||||||
|
(5)更改姓名,当地公安系统未将更改信息上报到公安部(上报时间因地域不同而有所差异);
|
||||||
|
(6)移民;
|
||||||
|
(7)未更换二代身份证;
|
||||||
|
(8)死亡。
|
||||||
|
(9)身份证号确实不存在
|
||||||
|
|
||||||
|
|
||||||
|
失败响应
|
||||||
|
|
||||||
|
{
|
||||||
|
"status": "INVALID_ARGUMENT",
|
||||||
|
"reason": "Invalid id_number",
|
||||||
|
"request_id": "TID2af751b4bff24be781d60af10bf84101"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
====
|
||||||
|
错误码
|
||||||
|
错误码
|
||||||
|
错误信息
|
||||||
|
描述
|
||||||
|
200
|
||||||
|
请求次数过多:同一身份证号一天内最多请求【4】次
|
||||||
|
请求次数过多:同一身份证号一天内最多请求【10】次
|
||||||
|
400
|
||||||
|
请求参数错误,原因:未输入姓名
|
||||||
|
请求参数错误,原因:未输入姓名
|
||||||
|
401
|
||||||
|
账号或密钥错误
|
||||||
|
账号或密钥错误
|
||||||
|
403
|
||||||
|
调用频率过高
|
||||||
|
调用频率过高
|
||||||
|
404
|
||||||
|
请求路径错误
|
||||||
|
请求路径错误
|
||||||
|
500
|
||||||
|
数据服务异常
|
||||||
|
数据服务异常
|
||||||
|
400
|
||||||
|
参数非UTF-8编码
|
||||||
|
参数非UTF-8编码
|
||||||
|
400
|
||||||
|
请求参数错误,原因:姓名为空
|
||||||
|
请求参数错误,原因:姓名为空
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user