add
This commit is contained in:
@@ -264,6 +264,7 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
|||||||
&apiEntities.ApiUser{},
|
&apiEntities.ApiUser{},
|
||||||
&apiEntities.ApiCall{},
|
&apiEntities.ApiCall{},
|
||||||
&apiEntities.Report{},
|
&apiEntities.Report{},
|
||||||
|
&apiEntities.QueryWhitelistEntry{},
|
||||||
|
|
||||||
// 下属账号域
|
// 下属账号域
|
||||||
&subordinateEntities.SubordinateInvitation{},
|
&subordinateEntities.SubordinateInvitation{},
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ type ApiApplicationServiceImpl struct {
|
|||||||
subscriptionService *product_services.ProductSubscriptionService
|
subscriptionService *product_services.ProductSubscriptionService
|
||||||
balanceAlertService finance_services.BalanceAlertService
|
balanceAlertService finance_services.BalanceAlertService
|
||||||
subordinateRepo subordinate_repositories.SubordinateRepository
|
subordinateRepo subordinate_repositories.SubordinateRepository
|
||||||
|
queryWhitelistSvc services.QueryWhitelistService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApiApplicationService(
|
func NewApiApplicationService(
|
||||||
@@ -116,6 +117,7 @@ func NewApiApplicationService(
|
|||||||
exportManager *export.ExportManager,
|
exportManager *export.ExportManager,
|
||||||
balanceAlertService finance_services.BalanceAlertService,
|
balanceAlertService finance_services.BalanceAlertService,
|
||||||
subordinateRepo subordinate_repositories.SubordinateRepository,
|
subordinateRepo subordinate_repositories.SubordinateRepository,
|
||||||
|
queryWhitelistSvc services.QueryWhitelistService,
|
||||||
) ApiApplicationService {
|
) ApiApplicationService {
|
||||||
service := &ApiApplicationServiceImpl{
|
service := &ApiApplicationServiceImpl{
|
||||||
apiCallService: apiCallService,
|
apiCallService: apiCallService,
|
||||||
@@ -135,6 +137,7 @@ func NewApiApplicationService(
|
|||||||
subscriptionService: subscriptionService,
|
subscriptionService: subscriptionService,
|
||||||
balanceAlertService: balanceAlertService,
|
balanceAlertService: balanceAlertService,
|
||||||
subordinateRepo: subordinateRepo,
|
subordinateRepo: subordinateRepo,
|
||||||
|
queryWhitelistSvc: queryWhitelistSvc,
|
||||||
}
|
}
|
||||||
|
|
||||||
return service
|
return service
|
||||||
@@ -367,6 +370,12 @@ func extractParentAccessID(params map[string]interface{}) (string, bool) {
|
|||||||
|
|
||||||
// callExternalApi 同步调用外部API
|
// callExternalApi 同步调用外部API
|
||||||
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
|
func (s *ApiApplicationServiceImpl) callExternalApi(ctx context.Context, cmd *commands.ApiCallCommand, validation *dto.ApiCallValidationResult) (string, error) {
|
||||||
|
// 查询白名单拦截:命中则返回「查询为空」,不调用上游、不扣费
|
||||||
|
if s.queryWhitelistSvc != nil &&
|
||||||
|
s.queryWhitelistSvc.ShouldReturnEmpty(ctx, validation.GetUserID(), cmd.ApiName, validation.RequestParams) {
|
||||||
|
return "", ErrQueryEmpty
|
||||||
|
}
|
||||||
|
|
||||||
// 创建CallContext
|
// 创建CallContext
|
||||||
callContext := &processors.CallContext{
|
callContext := &processors.CallContext{
|
||||||
ContractCode: validation.ContractCode,
|
ContractCode: validation.ContractCode,
|
||||||
|
|||||||
71
internal/application/api/dto/query_whitelist_dto.go
Normal file
71
internal/application/api/dto/query_whitelist_dto.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryWhitelistEntryRequest struct {
|
||||||
|
UserID string `json:"user_id" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||||
|
IDCard string `json:"id_card" validate:"required"`
|
||||||
|
APICodes []string `json:"api_codes" validate:"required,min=1,dive,required"`
|
||||||
|
Remark string `json:"remark" validate:"max=500"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistEntryUpdateRequest struct {
|
||||||
|
Name string `json:"name" validate:"omitempty,min=1,max=100"`
|
||||||
|
IDCard string `json:"id_card" validate:"omitempty"`
|
||||||
|
APICodes []string `json:"api_codes" validate:"omitempty,min=1,dive,required"`
|
||||||
|
Remark string `json:"remark" validate:"max=500"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistStatusRequest struct {
|
||||||
|
Status string `json:"status" validate:"required,oneof=enabled disabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistEntryResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
IsGlobal bool `json:"is_global"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
IDCardMasked string `json:"id_card_masked"`
|
||||||
|
APICodes []string `json:"api_codes"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistListResponse struct {
|
||||||
|
Items []QueryWhitelistEntryResponse `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Size int `json:"page_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistImportLegacyResponse struct {
|
||||||
|
Imported int `json:"imported"`
|
||||||
|
Skipped int `json:"skipped"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryWhitelistEntryResponse(entry *entities.QueryWhitelistEntry) QueryWhitelistEntryResponse {
|
||||||
|
apiCodes := []string(entry.APICodes)
|
||||||
|
if apiCodes == nil {
|
||||||
|
apiCodes = []string{}
|
||||||
|
}
|
||||||
|
return QueryWhitelistEntryResponse{
|
||||||
|
ID: entry.ID,
|
||||||
|
UserID: entry.UserID,
|
||||||
|
IsGlobal: entry.IsGlobal(),
|
||||||
|
Name: entry.Name,
|
||||||
|
IDCardMasked: entry.IDCardMasked,
|
||||||
|
APICodes: apiCodes,
|
||||||
|
Status: entry.Status,
|
||||||
|
Remark: entry.Remark,
|
||||||
|
CreatedAt: entry.CreatedAt,
|
||||||
|
UpdatedAt: entry.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
309
internal/application/api/query_whitelist_application_service.go
Normal file
309
internal/application/api/query_whitelist_application_service.go
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tyapi-server/internal/application/api/dto"
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
|
api_services "tyapi-server/internal/domains/api/services"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryWhitelistApplicationService interface {
|
||||||
|
CreateEntry(ctx context.Context, adminUserID string, req *dto.QueryWhitelistEntryRequest) (*dto.QueryWhitelistEntryResponse, error)
|
||||||
|
UpdateEntry(ctx context.Context, adminUserID, id string, req *dto.QueryWhitelistEntryUpdateRequest) (*dto.QueryWhitelistEntryResponse, error)
|
||||||
|
UpdateEntryStatus(ctx context.Context, adminUserID, id, status string) (*dto.QueryWhitelistEntryResponse, error)
|
||||||
|
DeleteEntry(ctx context.Context, id string) error
|
||||||
|
GetEntry(ctx context.Context, id string) (*dto.QueryWhitelistEntryResponse, error)
|
||||||
|
ListEntries(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) (*dto.QueryWhitelistListResponse, error)
|
||||||
|
ImportLegacyEntries(ctx context.Context, adminUserID string) (*dto.QueryWhitelistImportLegacyResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistApplicationServiceImpl struct {
|
||||||
|
repo repositories.QueryWhitelistRepository
|
||||||
|
queryWhitelistSvc api_services.QueryWhitelistService
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryWhitelistApplicationService(
|
||||||
|
repo repositories.QueryWhitelistRepository,
|
||||||
|
queryWhitelistSvc api_services.QueryWhitelistService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) QueryWhitelistApplicationService {
|
||||||
|
return &QueryWhitelistApplicationServiceImpl{
|
||||||
|
repo: repo,
|
||||||
|
queryWhitelistSvc: queryWhitelistSvc,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) CreateEntry(
|
||||||
|
ctx context.Context,
|
||||||
|
adminUserID string,
|
||||||
|
req *dto.QueryWhitelistEntryRequest,
|
||||||
|
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||||
|
if err := validateQueryWhitelistRequest(req.UserID, req.Name, req.IDCard, req.APICodes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idCardHash := api_services.HashIDCard(req.IDCard)
|
||||||
|
exists, err := s.repo.ExistsByUserIDCardHashAndName(ctx, req.UserID, idCardHash, strings.TrimSpace(req.Name), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil, fmt.Errorf("该用户下已存在相同的身份证与姓名规则")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &entities.QueryWhitelistEntry{
|
||||||
|
UserID: strings.TrimSpace(req.UserID),
|
||||||
|
Name: normalizeWhitelistName(req.Name),
|
||||||
|
IDCardHash: idCardHash,
|
||||||
|
IDCardMasked: api_services.MaskIDCard(req.IDCard),
|
||||||
|
APICodes: entities.APICodeList(req.APICodes),
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
Remark: strings.TrimSpace(req.Remark),
|
||||||
|
CreatedBy: &adminUserID,
|
||||||
|
}
|
||||||
|
if err := s.repo.Create(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entry.UserID, idCardHash)
|
||||||
|
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) UpdateEntry(
|
||||||
|
ctx context.Context,
|
||||||
|
adminUserID, id string,
|
||||||
|
req *dto.QueryWhitelistEntryUpdateRequest,
|
||||||
|
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||||
|
entry, err := s.repo.FindByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fmt.Errorf("规则不存在")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
oldHash := entry.IDCardHash
|
||||||
|
|
||||||
|
if req.Name != "" {
|
||||||
|
entry.Name = normalizeWhitelistName(req.Name)
|
||||||
|
}
|
||||||
|
if req.IDCard != "" {
|
||||||
|
if err := validateIDCard(req.IDCard); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.IDCardHash = api_services.HashIDCard(req.IDCard)
|
||||||
|
entry.IDCardMasked = api_services.MaskIDCard(req.IDCard)
|
||||||
|
}
|
||||||
|
if len(req.APICodes) > 0 {
|
||||||
|
if err := validateAPICodes(req.APICodes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.APICodes = entities.APICodeList(req.APICodes)
|
||||||
|
}
|
||||||
|
if req.Remark != "" || req.Remark == "" {
|
||||||
|
entry.Remark = strings.TrimSpace(req.Remark)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := s.repo.ExistsByUserIDCardHashAndName(ctx, entry.UserID, entry.IDCardHash, entry.Name, entry.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return nil, fmt.Errorf("该用户下已存在相同的身份证与姓名规则")
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.UpdatedBy = &adminUserID
|
||||||
|
if err := s.repo.Update(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entry.UserID, oldHash)
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||||
|
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) UpdateEntryStatus(
|
||||||
|
ctx context.Context,
|
||||||
|
adminUserID, id, status string,
|
||||||
|
) (*dto.QueryWhitelistEntryResponse, error) {
|
||||||
|
entry, err := s.repo.FindByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fmt.Errorf("规则不存在")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry.Status = status
|
||||||
|
entry.UpdatedBy = &adminUserID
|
||||||
|
if err := s.repo.Update(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||||
|
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) DeleteEntry(ctx context.Context, id string) error {
|
||||||
|
entry, err := s.repo.FindByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return fmt.Errorf("规则不存在")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.repo.Delete(ctx, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entry.UserID, entry.IDCardHash)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) GetEntry(ctx context.Context, id string) (*dto.QueryWhitelistEntryResponse, error) {
|
||||||
|
entry, err := s.repo.FindByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, fmt.Errorf("规则不存在")
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := dto.NewQueryWhitelistEntryResponse(entry)
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) ListEntries(
|
||||||
|
ctx context.Context,
|
||||||
|
filters map[string]interface{},
|
||||||
|
options interfaces.ListOptions,
|
||||||
|
) (*dto.QueryWhitelistListResponse, error) {
|
||||||
|
if idCard, ok := filters["id_card"].(string); ok && idCard != "" {
|
||||||
|
filters["id_card_hash"] = api_services.HashIDCard(idCard)
|
||||||
|
delete(filters, "id_card")
|
||||||
|
}
|
||||||
|
entries, total, err := s.repo.List(ctx, filters, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items := make([]dto.QueryWhitelistEntryResponse, 0, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
items = append(items, dto.NewQueryWhitelistEntryResponse(entry))
|
||||||
|
}
|
||||||
|
page := options.Page
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
size := options.PageSize
|
||||||
|
if size < 1 {
|
||||||
|
size = 20
|
||||||
|
}
|
||||||
|
return &dto.QueryWhitelistListResponse{
|
||||||
|
Items: items,
|
||||||
|
Total: total,
|
||||||
|
Page: page,
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistApplicationServiceImpl) ImportLegacyEntries(
|
||||||
|
ctx context.Context,
|
||||||
|
adminUserID string,
|
||||||
|
) (*dto.QueryWhitelistImportLegacyResponse, error) {
|
||||||
|
imported := 0
|
||||||
|
skipped := 0
|
||||||
|
for _, idCard := range LegacyHardcodedIDCards {
|
||||||
|
hash := api_services.HashIDCard(idCard)
|
||||||
|
exists, err := s.repo.ExistsByUserIDCardHashAndName(
|
||||||
|
ctx,
|
||||||
|
entities.QueryWhitelistGlobalUserID,
|
||||||
|
hash,
|
||||||
|
entities.QueryWhitelistWildcardName,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entry := &entities.QueryWhitelistEntry{
|
||||||
|
UserID: entities.QueryWhitelistGlobalUserID,
|
||||||
|
Name: entities.QueryWhitelistWildcardName,
|
||||||
|
IDCardHash: hash,
|
||||||
|
IDCardMasked: api_services.MaskIDCard(idCard),
|
||||||
|
APICodes: entities.APICodeList{"*"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
Remark: "自硬编码迁移-全局",
|
||||||
|
CreatedBy: &adminUserID,
|
||||||
|
}
|
||||||
|
if err := s.repo.Create(ctx, entry); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.queryWhitelistSvc.InvalidateCache(entities.QueryWhitelistGlobalUserID, hash)
|
||||||
|
imported++
|
||||||
|
}
|
||||||
|
return &dto.QueryWhitelistImportLegacyResponse{
|
||||||
|
Imported: imported,
|
||||||
|
Skipped: skipped,
|
||||||
|
Total: len(LegacyHardcodedIDCards),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateQueryWhitelistRequest(userID, name, idCard string, apiCodes []string) error {
|
||||||
|
userID = strings.TrimSpace(userID)
|
||||||
|
if userID == "" {
|
||||||
|
return fmt.Errorf("user_id 不能为空")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(name) == "" {
|
||||||
|
return fmt.Errorf("name 不能为空")
|
||||||
|
}
|
||||||
|
if err := validateIDCard(idCard); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validateAPICodes(apiCodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateIDCard(idCard string) error {
|
||||||
|
idCard = api_services.NormalizeIDCard(idCard)
|
||||||
|
if len(idCard) != 18 {
|
||||||
|
return fmt.Errorf("身份证号格式不正确")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateAPICodes(apiCodes []string) error {
|
||||||
|
if len(apiCodes) == 0 {
|
||||||
|
return fmt.Errorf("api_codes 不能为空")
|
||||||
|
}
|
||||||
|
hasWildcard := false
|
||||||
|
for _, code := range apiCodes {
|
||||||
|
code = strings.TrimSpace(code)
|
||||||
|
if code == "" {
|
||||||
|
return fmt.Errorf("api_codes 不能包含空值")
|
||||||
|
}
|
||||||
|
if code == "*" {
|
||||||
|
hasWildcard = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasWildcard && len(apiCodes) > 1 {
|
||||||
|
return fmt.Errorf("api_codes 包含 * 时不能与其他编码混用")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeWhitelistName(name string) string {
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == entities.QueryWhitelistWildcardName {
|
||||||
|
return entities.QueryWhitelistWildcardName
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
22
internal/application/api/query_whitelist_legacy.go
Normal file
22
internal/application/api/query_whitelist_legacy.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
// LegacyHardcodedIDCards 原 processor 硬编码的身份证号,导入为全局规则(user_id=*,name=*)
|
||||||
|
var LegacyHardcodedIDCards = []string{
|
||||||
|
"350681198611130611",
|
||||||
|
"622301200006250550",
|
||||||
|
"320682198910134998",
|
||||||
|
"640102198708020925",
|
||||||
|
"420624197310234034",
|
||||||
|
"350104198501184416",
|
||||||
|
"410521198606018056",
|
||||||
|
"410482198504029333",
|
||||||
|
"370982199012037272",
|
||||||
|
"431027198810290730",
|
||||||
|
"362502199510298017",
|
||||||
|
"340826199008250378",
|
||||||
|
"321027198304072129",
|
||||||
|
"420116198907031413",
|
||||||
|
"13032319930128263X",
|
||||||
|
"350681198412013041",
|
||||||
|
"33072619741031111X",
|
||||||
|
}
|
||||||
@@ -683,6 +683,10 @@ func NewContainer() *Container {
|
|||||||
api_repo.NewGormReportRepository,
|
api_repo.NewGormReportRepository,
|
||||||
fx.As(new(domain_api_repo.ReportRepository)),
|
fx.As(new(domain_api_repo.ReportRepository)),
|
||||||
),
|
),
|
||||||
|
fx.Annotate(
|
||||||
|
api_repo.NewGormQueryWhitelistRepository,
|
||||||
|
fx.As(new(domain_api_repo.QueryWhitelistRepository)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// 下属账号仓储
|
// 下属账号仓储
|
||||||
@@ -803,10 +807,15 @@ func NewContainer() *Container {
|
|||||||
// 使用带仓储注入的构造函数,支持企业报告记录持久化
|
// 使用带仓储注入的构造函数,支持企业报告记录持久化
|
||||||
api_services.NewApiRequestServiceWithRepos,
|
api_services.NewApiRequestServiceWithRepos,
|
||||||
api_services.NewFormConfigService,
|
api_services.NewFormConfigService,
|
||||||
|
api_services.NewQueryWhitelistService,
|
||||||
),
|
),
|
||||||
|
|
||||||
// API域应用服务
|
// API域应用服务
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
|
fx.Annotate(
|
||||||
|
api_app.NewQueryWhitelistApplicationService,
|
||||||
|
fx.As(new(api_app.QueryWhitelistApplicationService)),
|
||||||
|
),
|
||||||
// API应用服务 - 绑定到接口
|
// API应用服务 - 绑定到接口
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
func(
|
func(
|
||||||
@@ -827,6 +836,7 @@ func NewContainer() *Container {
|
|||||||
exportManager *export.ExportManager,
|
exportManager *export.ExportManager,
|
||||||
balanceAlertService finance_services.BalanceAlertService,
|
balanceAlertService finance_services.BalanceAlertService,
|
||||||
subordinateRepo domain_subordinate_repo.SubordinateRepository,
|
subordinateRepo domain_subordinate_repo.SubordinateRepository,
|
||||||
|
queryWhitelistSvc api_services.QueryWhitelistService,
|
||||||
) api_app.ApiApplicationService {
|
) api_app.ApiApplicationService {
|
||||||
return api_app.NewApiApplicationService(
|
return api_app.NewApiApplicationService(
|
||||||
apiCallService,
|
apiCallService,
|
||||||
@@ -846,6 +856,7 @@ func NewContainer() *Container {
|
|||||||
exportManager,
|
exportManager,
|
||||||
balanceAlertService,
|
balanceAlertService,
|
||||||
subordinateRepo,
|
subordinateRepo,
|
||||||
|
queryWhitelistSvc,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
fx.As(new(api_app.ApiApplicationService)),
|
fx.As(new(api_app.ApiApplicationService)),
|
||||||
@@ -1309,6 +1320,7 @@ func NewContainer() *Container {
|
|||||||
handlers.NewStatisticsHandler,
|
handlers.NewStatisticsHandler,
|
||||||
// 管理员安全HTTP处理器
|
// 管理员安全HTTP处理器
|
||||||
handlers.NewAdminSecurityHandler,
|
handlers.NewAdminSecurityHandler,
|
||||||
|
handlers.NewAdminQueryWhitelistHandler,
|
||||||
// 文章HTTP处理器
|
// 文章HTTP处理器
|
||||||
func(
|
func(
|
||||||
appService article.ArticleApplicationService,
|
appService article.ArticleApplicationService,
|
||||||
@@ -1403,6 +1415,7 @@ func NewContainer() *Container {
|
|||||||
routes.NewStatisticsRoutes,
|
routes.NewStatisticsRoutes,
|
||||||
// 管理员安全路由
|
// 管理员安全路由
|
||||||
routes.NewAdminSecurityRoutes,
|
routes.NewAdminSecurityRoutes,
|
||||||
|
routes.NewAdminQueryWhitelistRoutes,
|
||||||
// PDFG路由
|
// PDFG路由
|
||||||
routes.NewPDFGRoutes,
|
routes.NewPDFGRoutes,
|
||||||
// 企业报告页面路由
|
// 企业报告页面路由
|
||||||
@@ -1523,6 +1536,7 @@ func RegisterRoutes(
|
|||||||
apiRoutes *routes.ApiRoutes,
|
apiRoutes *routes.ApiRoutes,
|
||||||
statisticsRoutes *routes.StatisticsRoutes,
|
statisticsRoutes *routes.StatisticsRoutes,
|
||||||
adminSecurityRoutes *routes.AdminSecurityRoutes,
|
adminSecurityRoutes *routes.AdminSecurityRoutes,
|
||||||
|
adminQueryWhitelistRoutes *routes.AdminQueryWhitelistRoutes,
|
||||||
pdfgRoutes *routes.PDFGRoutes,
|
pdfgRoutes *routes.PDFGRoutes,
|
||||||
qyglReportRoutes *routes.QYGLReportRoutes,
|
qyglReportRoutes *routes.QYGLReportRoutes,
|
||||||
jwtAuth *middleware.JWTAuthMiddleware,
|
jwtAuth *middleware.JWTAuthMiddleware,
|
||||||
@@ -1551,6 +1565,7 @@ func RegisterRoutes(
|
|||||||
announcementRoutes.Register(router)
|
announcementRoutes.Register(router)
|
||||||
statisticsRoutes.Register(router)
|
statisticsRoutes.Register(router)
|
||||||
adminSecurityRoutes.Register(router)
|
adminSecurityRoutes.Register(router)
|
||||||
|
adminQueryWhitelistRoutes.Register(router)
|
||||||
pdfgRoutes.Register(router)
|
pdfgRoutes.Register(router)
|
||||||
qyglReportRoutes.Register(router)
|
qyglReportRoutes.Register(router)
|
||||||
|
|
||||||
|
|||||||
112
internal/domains/api/entities/query_whitelist_entry.go
Normal file
112
internal/domains/api/entities/query_whitelist_entry.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
QueryWhitelistGlobalUserID = "*" // 全局规则:对所有用户生效
|
||||||
|
QueryWhitelistWildcardName = "*" // 仅匹配身份证,不校验姓名(兼容历史硬编码)
|
||||||
|
QueryWhitelistStatusEnabled = "enabled"
|
||||||
|
QueryWhitelistStatusDisabled = "disabled"
|
||||||
|
QueryWhitelistTableName = "query_whitelist_entries"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APICodeList 生效的 API 编码列表,["*"] 表示全部「身份证必填」类接口
|
||||||
|
type APICodeList []string
|
||||||
|
|
||||||
|
func (a APICodeList) Value() (driver.Value, error) {
|
||||||
|
if a == nil {
|
||||||
|
return "[]", nil
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APICodeList) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*a = APICodeList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bytes []byte
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []byte:
|
||||||
|
bytes = v
|
||||||
|
case string:
|
||||||
|
bytes = []byte(v)
|
||||||
|
default:
|
||||||
|
return errors.New("无法扫描 APICodeList 类型")
|
||||||
|
}
|
||||||
|
if len(bytes) == 0 || string(bytes) == "null" {
|
||||||
|
*a = APICodeList{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(bytes, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueryWhitelistEntry 查询白名单:命中后返回「查询为空」,不调用上游
|
||||||
|
type QueryWhitelistEntry struct {
|
||||||
|
ID string `gorm:"type:varchar(36);primaryKey" json:"id"`
|
||||||
|
UserID string `gorm:"type:varchar(36);not null;index:idx_qwl_user_id_card_hash,priority:1" json:"user_id"`
|
||||||
|
Name string `gorm:"type:varchar(100);not null" json:"name"`
|
||||||
|
IDCardHash string `gorm:"type:varchar(64);not null;index:idx_qwl_user_id_card_hash,priority:2" json:"-"`
|
||||||
|
IDCardMasked string `gorm:"type:varchar(32);not null" json:"id_card_masked"`
|
||||||
|
APICodes APICodeList `gorm:"type:json;not null" json:"api_codes"`
|
||||||
|
Status string `gorm:"type:varchar(20);not null;default:'enabled'" json:"status"`
|
||||||
|
Remark string `gorm:"type:varchar(500)" json:"remark"`
|
||||||
|
CreatedBy *string `gorm:"type:varchar(36)" json:"created_by,omitempty"`
|
||||||
|
UpdatedBy *string `gorm:"type:varchar(36)" json:"updated_by,omitempty"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (QueryWhitelistEntry) TableName() string {
|
||||||
|
return QueryWhitelistTableName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *QueryWhitelistEntry) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if e.ID == "" {
|
||||||
|
e.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
if e.Status == "" {
|
||||||
|
e.Status = QueryWhitelistStatusEnabled
|
||||||
|
}
|
||||||
|
if e.APICodes == nil {
|
||||||
|
e.APICodes = APICodeList{"*"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *QueryWhitelistEntry) IsGlobal() bool {
|
||||||
|
return e.UserID == QueryWhitelistGlobalUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *QueryWhitelistEntry) IsEnabled() bool {
|
||||||
|
return e.Status == QueryWhitelistStatusEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *QueryWhitelistEntry) MatchesAPICode(apiCode string) bool {
|
||||||
|
for _, code := range e.APICodes {
|
||||||
|
if code == "*" || code == apiCode {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *QueryWhitelistEntry) MatchesName(name string) bool {
|
||||||
|
if e.Name == QueryWhitelistWildcardName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return e.Name == name
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryWhitelistRepository interface {
|
||||||
|
Create(ctx context.Context, entry *entities.QueryWhitelistEntry) error
|
||||||
|
Update(ctx context.Context, entry *entities.QueryWhitelistEntry) error
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
FindByID(ctx context.Context, id string) (*entities.QueryWhitelistEntry, error)
|
||||||
|
FindEnabledByUserIDsAndIDCardHash(ctx context.Context, userIDs []string, idCardHash string) ([]*entities.QueryWhitelistEntry, error)
|
||||||
|
FindAllEnabled(ctx context.Context) ([]*entities.QueryWhitelistEntry, error)
|
||||||
|
List(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.QueryWhitelistEntry, int64, error)
|
||||||
|
ExistsByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name string, excludeID string) (bool, error)
|
||||||
|
}
|
||||||
@@ -29,6 +29,8 @@ type FormConfig struct {
|
|||||||
// FormConfigService 表单配置服务接口
|
// FormConfigService 表单配置服务接口
|
||||||
type FormConfigService interface {
|
type FormConfigService interface {
|
||||||
GetFormConfig(ctx context.Context, apiCode string) (*FormConfig, error)
|
GetFormConfig(ctx context.Context, apiCode string) (*FormConfig, error)
|
||||||
|
// RequiresIdentityInput 该 API 入参是否要求身份证(id_card / idCard 为必填)
|
||||||
|
RequiresIdentityInput(ctx context.Context, apiCode string) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormConfigServiceImpl 表单配置服务实现
|
// FormConfigServiceImpl 表单配置服务实现
|
||||||
@@ -72,6 +74,35 @@ func (s *FormConfigServiceImpl) GetFormConfig(ctx context.Context, apiCode strin
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RequiresIdentityInput 判断 API 是否以身份证为必填入参。
|
||||||
|
// api_codes 为 ["*"] 时,仅对此类接口生效;无 id_card 必填字段的接口不会被拦截。
|
||||||
|
func (s *FormConfigServiceImpl) RequiresIdentityInput(ctx context.Context, apiCode string) bool {
|
||||||
|
if len(apiCode) >= 4 && strings.EqualFold(apiCode[:4], "COMB") {
|
||||||
|
// 组合包是否拦截由请求入参是否含 id_card 决定(在 QueryWhitelist 上层已校验)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
dtoStruct, err := s.getDTOStruct(ctx, apiCode)
|
||||||
|
if err != nil || dtoStruct == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return dtoStructRequiresIdentityInput(dtoStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dtoStructRequiresIdentityInput(dtoStruct interface{}) bool {
|
||||||
|
t := reflect.TypeOf(dtoStruct).Elem()
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
|
||||||
|
if jsonTag != "id_card" && jsonTag != "idCard" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(field.Tag.Get("validate"), "required") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// getDTOStruct 根据API代码获取对应的DTO结构体
|
// getDTOStruct 根据API代码获取对应的DTO结构体
|
||||||
func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string) (interface{}, error) {
|
func (s *FormConfigServiceImpl) getDTOStruct(ctx context.Context, apiCode string) (interface{}, error) {
|
||||||
// 建立API代码到DTO结构体的映射
|
// 建立API代码到DTO结构体的映射
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ func ProcessFLXG0V4BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ func ProcessFLXG2E8FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func ProcessFLXG3A9BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ func ProcessFLXG5A3BRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ func ProcessFLXG7E8FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ func ProcessFLXG9C1DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ func ProcessFLXGCA3DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.WestDexService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func ProcessFLXGDEA8Request(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func ProcessFLXGDEA9Request(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
"idCard": paramsDto.IDCard,
|
"idCard": paramsDto.IDCard,
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ func ProcessFLXGHB4FRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if deps.HaiyuapiService == nil {
|
if deps.HaiyuapiService == nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, errors.New("海宇API服务未初始化"))
|
return nil, errors.Join(processors.ErrSystem, errors.New("海宇API服务未初始化"))
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
reqParams := map[string]interface{}{
|
reqParams := map[string]interface{}{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func ProcessFLXGK5D2Request(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
encryptedName, err := deps.ZhichaService.Encrypt(paramsDto.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ func ProcessJRZQ8A2DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
@@ -56,7 +52,6 @@ func ProcessJRZQ8A2DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Join(processors.ErrSystem, err)
|
return nil, errors.Join(processors.ErrSystem, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
|
|
||||||
// respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI018", reqData)
|
// respData, err := deps.ZhichaService.CallAPI(ctx, "ZCI018", reqData)
|
||||||
@@ -73,6 +68,6 @@ func ProcessJRZQ8A2DRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return nil, errors.Join(processors.ErrSystem, err)
|
// return nil, errors.Join(processors.ErrSystem, err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// return respBytes, nil
|
// return respBytes, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,41 @@
|
|||||||
package jrzq
|
package jrzq
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// mapNuoerLoanRiskTagV23ToResponse 将 nuoer data 转为 JRZQ9D4E 对外结构:
|
// mapNuoerLoanRiskTagV23ToResponse 将 nuoer data 转为 JRZQ9D4E 对外结构:
|
||||||
// 解包 result,score/reason/contents 原样透传。
|
// 解包 result,score/reason 原样透传,contents 内 TC_ 前缀标签码映射为 BH_。
|
||||||
func mapNuoerLoanRiskTagV23ToResponse(data map[string]interface{}) map[string]interface{} {
|
func mapNuoerLoanRiskTagV23ToResponse(data map[string]interface{}) map[string]interface{} {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return map[string]interface{}{}
|
return map[string]interface{}{}
|
||||||
}
|
}
|
||||||
if result, ok := data["result"].(map[string]interface{}); ok {
|
result := data
|
||||||
return result
|
if unwrapped, ok := data["result"].(map[string]interface{}); ok {
|
||||||
|
result = unwrapped
|
||||||
}
|
}
|
||||||
return data
|
return mapLoanRiskTagV23ContentsKeys(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapLoanRiskTagV23ContentsKeys(data map[string]interface{}) map[string]interface{} {
|
||||||
|
out := make(map[string]interface{}, len(data))
|
||||||
|
for key, val := range data {
|
||||||
|
if key == "contents" {
|
||||||
|
if contents, ok := val.(map[string]interface{}); ok {
|
||||||
|
out[key] = renameLoanRiskTagTCKeyPrefix(contents)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[key] = val
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func renameLoanRiskTagTCKeyPrefix(contents map[string]interface{}) map[string]interface{} {
|
||||||
|
out := make(map[string]interface{}, len(contents))
|
||||||
|
for key, val := range contents {
|
||||||
|
if strings.HasPrefix(key, "TC_") {
|
||||||
|
key = "BH_" + key[3:]
|
||||||
|
}
|
||||||
|
out[key] = val
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package jrzq
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMapNuoerLoanRiskTagV23ToResponse_RenamesTCContentsKeys(t *testing.T) {
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"result": map[string]interface{}{
|
||||||
|
"score": "750",
|
||||||
|
"reason": "ok",
|
||||||
|
"contents": map[string]interface{}{
|
||||||
|
"TC_Q016_q20": "1.5",
|
||||||
|
"TC_A001": "3",
|
||||||
|
"BH_A002": "2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := mapNuoerLoanRiskTagV23ToResponse(raw)
|
||||||
|
|
||||||
|
if got["score"] != "750" {
|
||||||
|
t.Fatalf("score = %v, want 750", got["score"])
|
||||||
|
}
|
||||||
|
contents, ok := got["contents"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("contents type = %T, want map[string]interface{}", got["contents"])
|
||||||
|
}
|
||||||
|
if contents["BH_A001"] != "3" {
|
||||||
|
t.Fatalf("BH_A001 = %v, want 3", contents["BH_A001"])
|
||||||
|
}
|
||||||
|
if contents["BH_A002"] != "2" {
|
||||||
|
t.Fatalf("BH_A002 = %v, want 2", contents["BH_A002"])
|
||||||
|
}
|
||||||
|
if _, exists := contents["TC_Q016_q20"]; exists {
|
||||||
|
t.Fatal("TC_Q016_q20 should be renamed")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,10 +20,6 @@ func ProcessJRZQV7MDRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
if err := deps.Validator.ValidateStruct(paramsDto); err != nil {
|
||||||
return nil, errors.Join(processors.ErrInvalidParam, err)
|
return nil, errors.Join(processors.ErrInvalidParam, err)
|
||||||
}
|
}
|
||||||
// 去掉司法案件案件去掉身份证号码
|
|
||||||
if paramsDto.IDCard == "350681198611130611" || paramsDto.IDCard == "622301200006250550" || paramsDto.IDCard == "320682198910134998" || paramsDto.IDCard == "640102198708020925" || paramsDto.IDCard == "420624197310234034" || paramsDto.IDCard == "350104198501184416" || paramsDto.IDCard == "410521198606018056" || paramsDto.IDCard == "410482198504029333" || paramsDto.IDCard == "370982199012037272" || paramsDto.IDCard == "431027198810290730" || paramsDto.IDCard == "362502199510298017" || paramsDto.IDCard == "340826199008250378" || paramsDto.IDCard == "321027198304072129" || paramsDto.IDCard == "420116198907031413" || paramsDto.IDCard == "13032319930128263X" || paramsDto.IDCard == "350681198412013041" || paramsDto.IDCard == "33072619741031111X" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
|
|
||||||
body := map[string]string{
|
body := map[string]string{
|
||||||
"name": paramsDto.Name,
|
"name": paramsDto.Name,
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ func ProcessQYGL2S0WRequest(ctx context.Context, params []byte, deps *processors
|
|||||||
fmt.Print("个人身份证件号不能为空")
|
fmt.Print("个人身份证件号不能为空")
|
||||||
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("当失信被执行人类型为个人时,身份证件号不能为空"))
|
return nil, fmt.Errorf("%s: %w", processors.ErrInvalidParam, errors.New("当失信被执行人类型为个人时,身份证件号不能为空"))
|
||||||
}
|
}
|
||||||
if paramsDto.IDCard == "410482198504029333" {
|
|
||||||
return nil, errors.Join(processors.ErrNotFound, errors.New("查询为空"))
|
|
||||||
}
|
|
||||||
} else if paramsDto.Type == "ent" {
|
} else if paramsDto.Type == "ent" {
|
||||||
// 企业查询:name 和 entMark 两者必填其一
|
// 企业查询:name 和 entMark 两者必填其一
|
||||||
nameValue = paramsDto.EntName
|
nameValue = paramsDto.EntName
|
||||||
|
|||||||
65
internal/domains/api/services/query_whitelist_helpers.go
Normal file
65
internal/domains/api/services/query_whitelist_helpers.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NormalizeIDCard 归一化身份证号(去空格、末位 X 大写)
|
||||||
|
func NormalizeIDCard(idCard string) string {
|
||||||
|
idCard = strings.TrimSpace(idCard)
|
||||||
|
return strings.ToUpper(idCard)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashIDCard 计算身份证号 SHA256 哈希,用于索引与匹配
|
||||||
|
func HashIDCard(idCard string) string {
|
||||||
|
sum := sha256.Sum256([]byte(NormalizeIDCard(idCard)))
|
||||||
|
return hex.EncodeToString(sum[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaskIDCard 脱敏展示:350681********0611
|
||||||
|
func MaskIDCard(idCard string) string {
|
||||||
|
id := NormalizeIDCard(idCard)
|
||||||
|
if len(id) <= 10 {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
return id[:6] + "********" + id[len(id)-4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// IdentityParams 从请求参数中提取的身份证与姓名
|
||||||
|
type IdentityParams struct {
|
||||||
|
IDCard string
|
||||||
|
Name string
|
||||||
|
OK bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractIdentityParams 从解密后的 params map 提取 id_card + name
|
||||||
|
func ExtractIdentityParams(params map[string]interface{}) IdentityParams {
|
||||||
|
idCard := firstNonEmptyString(params, "id_card", "idCard")
|
||||||
|
name := firstNonEmptyString(params, "name")
|
||||||
|
if idCard == "" {
|
||||||
|
return IdentityParams{OK: false}
|
||||||
|
}
|
||||||
|
return IdentityParams{
|
||||||
|
IDCard: NormalizeIDCard(idCard),
|
||||||
|
Name: strings.TrimSpace(name),
|
||||||
|
OK: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonEmptyString(params map[string]interface{}, keys ...string) string {
|
||||||
|
for _, key := range keys {
|
||||||
|
v, ok := params[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s, ok := v.(string); ok {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizeIDCard(t *testing.T) {
|
||||||
|
if got := NormalizeIDCard("13032319930128263x"); got != "13032319930128263X" {
|
||||||
|
t.Fatalf("expected uppercase X, got %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaskIDCard(t *testing.T) {
|
||||||
|
masked := MaskIDCard("350681198611130611")
|
||||||
|
if masked != "350681********0611" {
|
||||||
|
t.Fatalf("unexpected mask: %s", masked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractIdentityParams(t *testing.T) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"id_card": "350681198611130611",
|
||||||
|
"name": "张三",
|
||||||
|
}
|
||||||
|
identity := ExtractIdentityParams(params)
|
||||||
|
if !identity.OK || identity.Name != "张三" {
|
||||||
|
t.Fatalf("unexpected identity: %+v", identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryWhitelistEntry_Matches(t *testing.T) {
|
||||||
|
entry := &entities.QueryWhitelistEntry{
|
||||||
|
UserID: entities.QueryWhitelistGlobalUserID,
|
||||||
|
Name: entities.QueryWhitelistWildcardName,
|
||||||
|
APICodes: entities.APICodeList{"FLXG0V4B"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !entry.MatchesAPICode("FLXG0V4B") {
|
||||||
|
t.Fatal("should match api code")
|
||||||
|
}
|
||||||
|
if entry.MatchesAPICode("JRZQ8A2D") {
|
||||||
|
t.Fatal("should not match other api code")
|
||||||
|
}
|
||||||
|
if !entry.MatchesName("任意姓名") {
|
||||||
|
t.Fatal("wildcard name should match any name")
|
||||||
|
}
|
||||||
|
|
||||||
|
strict := &entities.QueryWhitelistEntry{
|
||||||
|
Name: "李四",
|
||||||
|
}
|
||||||
|
if strict.MatchesName("李四") == false || strict.MatchesName("张三") {
|
||||||
|
t.Fatal("strict name matching failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryWhitelistEntry_GlobalWildcardAPICodes(t *testing.T) {
|
||||||
|
entry := &entities.QueryWhitelistEntry{
|
||||||
|
APICodes: entities.APICodeList{"*"},
|
||||||
|
}
|
||||||
|
if !entry.MatchesAPICode("ANY_CODE") {
|
||||||
|
t.Fatal("* should match any api code")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequiresIdentityInput_FLXG0V4B(t *testing.T) {
|
||||||
|
svc := NewFormConfigServiceWithoutDependencies()
|
||||||
|
if !svc.RequiresIdentityInput(context.Background(), "FLXG0V4B") {
|
||||||
|
t.Fatal("FLXG0V4B should require id_card")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiresIdentityInput_EnterpriseAPI(t *testing.T) {
|
||||||
|
svc := NewFormConfigServiceWithoutDependencies()
|
||||||
|
// QYGL8261 等企业类接口通常不要求 id_card 必填
|
||||||
|
if svc.RequiresIdentityInput(context.Background(), "QYGL8261") {
|
||||||
|
t.Fatal("QYGL8261 should not require id_card")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiresIdentityInput_COMB(t *testing.T) {
|
||||||
|
svc := NewFormConfigServiceWithoutDependencies()
|
||||||
|
if !svc.RequiresIdentityInput(context.Background(), "COMBXXXX") {
|
||||||
|
t.Fatal("COMB should be eligible when params contain id_card")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldReturnEmpty_SkipsNonIdentityAPIEvenWithWildcard(t *testing.T) {
|
||||||
|
idCard := "350681198611130611"
|
||||||
|
hash := HashIDCard(idCard)
|
||||||
|
svc := newTestQueryWhitelistService(&mockQueryWhitelistRepo{
|
||||||
|
entries: []*entities.QueryWhitelistEntry{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
UserID: entities.QueryWhitelistGlobalUserID,
|
||||||
|
Name: entities.QueryWhitelistWildcardName,
|
||||||
|
IDCardHash: hash,
|
||||||
|
APICodes: entities.APICodeList{"*"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, false)
|
||||||
|
|
||||||
|
params := map[string]interface{}{"id_card": idCard, "name": "张三"}
|
||||||
|
if svc.ShouldReturnEmpty(context.Background(), "user-a", "QYGL8261", params) {
|
||||||
|
t.Fatal("non-identity API should not be intercepted even with api_codes=*")
|
||||||
|
}
|
||||||
|
}
|
||||||
185
internal/domains/api/services/query_whitelist_service.go
Normal file
185
internal/domains/api/services/query_whitelist_service.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryWhitelistService interface {
|
||||||
|
ShouldReturnEmpty(ctx context.Context, userID, apiCode string, params map[string]interface{}) bool
|
||||||
|
InvalidateCache(userID, idCardHash string)
|
||||||
|
InvalidateAllCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
// queryWhitelistSnapshot 全量 enabled 规则快照,按 id_card_hash 索引,热路径只读内存。
|
||||||
|
type queryWhitelistSnapshot struct {
|
||||||
|
byHash map[string][]*entities.QueryWhitelistEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryWhitelistServiceImpl struct {
|
||||||
|
repo repositories.QueryWhitelistRepository
|
||||||
|
formConfigService FormConfigService
|
||||||
|
logger *zap.Logger
|
||||||
|
|
||||||
|
snapshot atomic.Pointer[queryWhitelistSnapshot]
|
||||||
|
snapshotMu sync.Mutex
|
||||||
|
|
||||||
|
// apiCode -> 是否要求身份证入参(FormConfig 反射结果,进程内永久缓存)
|
||||||
|
identityAPICache sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryWhitelistService(
|
||||||
|
repo repositories.QueryWhitelistRepository,
|
||||||
|
formConfigService FormConfigService,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) QueryWhitelistService {
|
||||||
|
s := &QueryWhitelistServiceImpl{
|
||||||
|
repo: repo,
|
||||||
|
formConfigService: formConfigService,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldReturnEmpty 检查是否应返回「查询为空」。
|
||||||
|
// 热路径:入参提取 → API 类型缓存 → 内存快照匹配,不逐请求查库。
|
||||||
|
func (s *QueryWhitelistServiceImpl) ShouldReturnEmpty(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, apiCode string,
|
||||||
|
params map[string]interface{},
|
||||||
|
) bool {
|
||||||
|
identity := ExtractIdentityParams(params)
|
||||||
|
if !identity.OK {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !s.requiresIdentityInput(ctx, apiCode) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
idCardHash := HashIDCard(identity.IDCard)
|
||||||
|
entries, err := s.lookupEntries(ctx, userID, idCardHash)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("查询白名单快照失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsEnabled() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !entry.MatchesAPICode(apiCode) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !entry.MatchesName(identity.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("命中查询白名单",
|
||||||
|
zap.String("user_id", userID),
|
||||||
|
zap.String("api_code", apiCode),
|
||||||
|
zap.String("whitelist_id", entry.ID),
|
||||||
|
zap.Bool("is_global", entry.IsGlobal()),
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) requiresIdentityInput(ctx context.Context, apiCode string) bool {
|
||||||
|
if s.formConfigService == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if cached, ok := s.identityAPICache.Load(apiCode); ok {
|
||||||
|
return cached.(bool)
|
||||||
|
}
|
||||||
|
result := s.formConfigService.RequiresIdentityInput(ctx, apiCode)
|
||||||
|
s.identityAPICache.Store(apiCode, result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) lookupEntries(ctx context.Context, userID, idCardHash string) ([]*entities.QueryWhitelistEntry, error) {
|
||||||
|
snap, err := s.getSnapshot(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
candidates := snap.byHash[idCardHash]
|
||||||
|
if len(candidates) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
result := make([]*entities.QueryWhitelistEntry, 0, len(candidates))
|
||||||
|
for _, entry := range candidates {
|
||||||
|
if entry.UserID == userID || entry.UserID == entities.QueryWhitelistGlobalUserID {
|
||||||
|
result = append(result, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) getSnapshot(ctx context.Context) (*queryWhitelistSnapshot, error) {
|
||||||
|
if snap := s.snapshot.Load(); snap != nil {
|
||||||
|
return snap, nil
|
||||||
|
}
|
||||||
|
return s.reloadSnapshot(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) reloadSnapshot(ctx context.Context) (*queryWhitelistSnapshot, error) {
|
||||||
|
s.snapshotMu.Lock()
|
||||||
|
defer s.snapshotMu.Unlock()
|
||||||
|
|
||||||
|
if snap := s.snapshot.Load(); snap != nil {
|
||||||
|
return snap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := s.repo.FindAllEnabled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
byHash := make(map[string][]*entities.QueryWhitelistEntry, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
byHash[entry.IDCardHash] = append(byHash[entry.IDCardHash], entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
snap := &queryWhitelistSnapshot{byHash: byHash}
|
||||||
|
s.snapshot.Store(snap)
|
||||||
|
s.logger.Info("查询白名单快照已加载", zap.Int("entries", len(entries)), zap.Int("hash_buckets", len(byHash)))
|
||||||
|
return snap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshSnapshotAsync 管理端变更后异步刷新,避免阻塞写请求。
|
||||||
|
func (s *QueryWhitelistServiceImpl) refreshSnapshotAsync() {
|
||||||
|
go func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
s.snapshotMu.Lock()
|
||||||
|
defer s.snapshotMu.Unlock()
|
||||||
|
s.snapshot.Store(nil)
|
||||||
|
|
||||||
|
entries, err := s.repo.FindAllEnabled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("刷新查询白名单快照失败", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
byHash := make(map[string][]*entities.QueryWhitelistEntry, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
byHash[entry.IDCardHash] = append(byHash[entry.IDCardHash], entry)
|
||||||
|
}
|
||||||
|
s.snapshot.Store(&queryWhitelistSnapshot{byHash: byHash})
|
||||||
|
s.logger.Info("查询白名单快照已刷新", zap.Int("entries", len(entries)))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) InvalidateCache(_ string, _ string) {
|
||||||
|
s.refreshSnapshotAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *QueryWhitelistServiceImpl) InvalidateAllCache() {
|
||||||
|
s.refreshSnapshotAsync()
|
||||||
|
}
|
||||||
147
internal/domains/api/services/query_whitelist_service_test.go
Normal file
147
internal/domains/api/services/query_whitelist_service_test.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockQueryWhitelistRepo struct {
|
||||||
|
entries []*entities.QueryWhitelistEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryWhitelistRepo) Create(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockQueryWhitelistRepo) Update(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockQueryWhitelistRepo) Delete(ctx context.Context, id string) error { return nil }
|
||||||
|
func (m *mockQueryWhitelistRepo) FindByID(ctx context.Context, id string) (*entities.QueryWhitelistEntry, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockQueryWhitelistRepo) List(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]*entities.QueryWhitelistEntry, int64, error) {
|
||||||
|
return nil, 0, nil
|
||||||
|
}
|
||||||
|
func (m *mockQueryWhitelistRepo) ExistsByUserIDCardHashAndName(ctx context.Context, userID, idCardHash, name, excludeID string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryWhitelistRepo) FindEnabledByUserIDsAndIDCardHash(ctx context.Context, userIDs []string, idCardHash string) ([]*entities.QueryWhitelistEntry, error) {
|
||||||
|
var result []*entities.QueryWhitelistEntry
|
||||||
|
for _, entry := range m.entries {
|
||||||
|
if entry.IDCardHash != idCardHash || !entry.IsEnabled() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, uid := range userIDs {
|
||||||
|
if entry.UserID == uid {
|
||||||
|
result = append(result, entry)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockQueryWhitelistRepo) FindAllEnabled(ctx context.Context) ([]*entities.QueryWhitelistEntry, error) {
|
||||||
|
var result []*entities.QueryWhitelistEntry
|
||||||
|
for _, entry := range m.entries {
|
||||||
|
if entry.IsEnabled() {
|
||||||
|
result = append(result, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockFormConfigService struct {
|
||||||
|
requiresIdentity bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFormConfigService) GetFormConfig(ctx context.Context, apiCode string) (*FormConfig, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockFormConfigService) RequiresIdentityInput(ctx context.Context, apiCode string) bool {
|
||||||
|
return m.requiresIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestQueryWhitelistService(repo repositories.QueryWhitelistRepository, requiresIdentity bool) QueryWhitelistService {
|
||||||
|
return NewQueryWhitelistService(repo, &mockFormConfigService{requiresIdentity: requiresIdentity}, zap.NewNop())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldReturnEmpty_GlobalRule(t *testing.T) {
|
||||||
|
idCard := "350681198611130611"
|
||||||
|
hash := HashIDCard(idCard)
|
||||||
|
svc := newTestQueryWhitelistService(&mockQueryWhitelistRepo{
|
||||||
|
entries: []*entities.QueryWhitelistEntry{
|
||||||
|
{
|
||||||
|
ID: "1",
|
||||||
|
UserID: entities.QueryWhitelistGlobalUserID,
|
||||||
|
Name: entities.QueryWhitelistWildcardName,
|
||||||
|
IDCardHash: hash,
|
||||||
|
APICodes: entities.APICodeList{"*"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
params := map[string]interface{}{"id_card": idCard, "name": "任意姓名"}
|
||||||
|
if !svc.ShouldReturnEmpty(context.Background(), "user-a", "FLXG0V4B", params) {
|
||||||
|
t.Fatal("global rule should hit for any user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldReturnEmpty_UserSpecificRule(t *testing.T) {
|
||||||
|
idCard := "350681198611130611"
|
||||||
|
hash := HashIDCard(idCard)
|
||||||
|
svc := newTestQueryWhitelistService(&mockQueryWhitelistRepo{
|
||||||
|
entries: []*entities.QueryWhitelistEntry{
|
||||||
|
{
|
||||||
|
ID: "2",
|
||||||
|
UserID: "user-a",
|
||||||
|
Name: "张三",
|
||||||
|
IDCardHash: hash,
|
||||||
|
APICodes: entities.APICodeList{"FLXG0V4B"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
params := map[string]interface{}{"id_card": idCard, "name": "张三"}
|
||||||
|
if !svc.ShouldReturnEmpty(context.Background(), "user-a", "FLXG0V4B", params) {
|
||||||
|
t.Fatal("user-a should hit")
|
||||||
|
}
|
||||||
|
if svc.ShouldReturnEmpty(context.Background(), "user-b", "FLXG0V4B", params) {
|
||||||
|
t.Fatal("user-b should not hit user-a rule")
|
||||||
|
}
|
||||||
|
if svc.ShouldReturnEmpty(context.Background(), "user-a", "JRZQ8A2D", params) {
|
||||||
|
t.Fatal("wrong api code should not hit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldReturnEmpty_NameMismatch(t *testing.T) {
|
||||||
|
idCard := "350681198611130611"
|
||||||
|
hash := HashIDCard(idCard)
|
||||||
|
svc := newTestQueryWhitelistService(&mockQueryWhitelistRepo{
|
||||||
|
entries: []*entities.QueryWhitelistEntry{
|
||||||
|
{
|
||||||
|
ID: "3",
|
||||||
|
UserID: "user-a",
|
||||||
|
Name: "张三",
|
||||||
|
IDCardHash: hash,
|
||||||
|
APICodes: entities.APICodeList{"*"},
|
||||||
|
Status: entities.QueryWhitelistStatusEnabled,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
params := map[string]interface{}{"id_card": idCard, "name": "李四"}
|
||||||
|
if svc.ShouldReturnEmpty(context.Background(), "user-a", "FLXG0V4B", params) {
|
||||||
|
t.Fatal("name mismatch should not hit")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/entities"
|
||||||
|
"tyapi-server/internal/domains/api/repositories"
|
||||||
|
"tyapi-server/internal/shared/database"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const QueryWhitelistTable = "query_whitelist_entries"
|
||||||
|
|
||||||
|
type GormQueryWhitelistRepository struct {
|
||||||
|
*database.BaseRepositoryImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ repositories.QueryWhitelistRepository = (*GormQueryWhitelistRepository)(nil)
|
||||||
|
|
||||||
|
func NewGormQueryWhitelistRepository(db *gorm.DB, logger *zap.Logger) repositories.QueryWhitelistRepository {
|
||||||
|
return &GormQueryWhitelistRepository{
|
||||||
|
BaseRepositoryImpl: database.NewBaseRepositoryImpl(db, logger),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) Create(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
|
||||||
|
return r.CreateEntity(ctx, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) Update(ctx context.Context, entry *entities.QueryWhitelistEntry) error {
|
||||||
|
return r.UpdateEntity(ctx, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) Delete(ctx context.Context, id string) error {
|
||||||
|
return r.GetDB(ctx).Where("id = ?", id).Delete(&entities.QueryWhitelistEntry{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) FindByID(ctx context.Context, id string) (*entities.QueryWhitelistEntry, error) {
|
||||||
|
var entry entities.QueryWhitelistEntry
|
||||||
|
if err := r.GetDB(ctx).Where("id = ?", id).First(&entry).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) FindEnabledByUserIDsAndIDCardHash(
|
||||||
|
ctx context.Context,
|
||||||
|
userIDs []string,
|
||||||
|
idCardHash string,
|
||||||
|
) ([]*entities.QueryWhitelistEntry, error) {
|
||||||
|
if len(userIDs) == 0 || idCardHash == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
var entries []*entities.QueryWhitelistEntry
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("user_id IN ? AND id_card_hash = ? AND status = ?", userIDs, idCardHash, entities.QueryWhitelistStatusEnabled).
|
||||||
|
Find(&entries).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) FindAllEnabled(ctx context.Context) ([]*entities.QueryWhitelistEntry, error) {
|
||||||
|
var entries []*entities.QueryWhitelistEntry
|
||||||
|
err := r.GetDB(ctx).
|
||||||
|
Where("status = ?", entities.QueryWhitelistStatusEnabled).
|
||||||
|
Find(&entries).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) List(
|
||||||
|
ctx context.Context,
|
||||||
|
filters map[string]interface{},
|
||||||
|
options interfaces.ListOptions,
|
||||||
|
) ([]*entities.QueryWhitelistEntry, int64, error) {
|
||||||
|
query := r.GetDB(ctx).Model(&entities.QueryWhitelistEntry{})
|
||||||
|
|
||||||
|
if userID, ok := filters["user_id"].(string); ok && userID != "" {
|
||||||
|
query = query.Where("user_id = ?", userID)
|
||||||
|
}
|
||||||
|
if status, ok := filters["status"].(string); ok && status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
if apiCode, ok := filters["api_code"].(string); ok && apiCode != "" {
|
||||||
|
query = query.Where("api_codes::text LIKE ?", fmt.Sprintf("%%\"%s\"%%", apiCode))
|
||||||
|
}
|
||||||
|
if idCardHash, ok := filters["id_card_hash"].(string); ok && idCardHash != "" {
|
||||||
|
query = query.Where("id_card_hash = ?", idCardHash)
|
||||||
|
}
|
||||||
|
if keyword, ok := filters["keyword"].(string); ok && keyword != "" {
|
||||||
|
like := "%" + keyword + "%"
|
||||||
|
query = query.Where("name LIKE ? OR remark LIKE ? OR id_card_masked LIKE ?", like, like, like)
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
page := options.Page
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
pageSize := options.PageSize
|
||||||
|
if pageSize < 1 {
|
||||||
|
pageSize = 20
|
||||||
|
}
|
||||||
|
if pageSize > 100 {
|
||||||
|
pageSize = 100
|
||||||
|
}
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
|
||||||
|
order := "created_at DESC"
|
||||||
|
if options.Sort != "" {
|
||||||
|
dir := "ASC"
|
||||||
|
if options.Order == "desc" {
|
||||||
|
dir = "DESC"
|
||||||
|
}
|
||||||
|
order = fmt.Sprintf("%s %s", options.Sort, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []*entities.QueryWhitelistEntry
|
||||||
|
if err := query.Order(order).Offset(offset).Limit(pageSize).Find(&entries).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
return entries, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *GormQueryWhitelistRepository) ExistsByUserIDCardHashAndName(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, idCardHash, name string,
|
||||||
|
excludeID string,
|
||||||
|
) (bool, error) {
|
||||||
|
query := r.GetDB(ctx).Model(&entities.QueryWhitelistEntry{}).
|
||||||
|
Where("user_id = ? AND id_card_hash = ? AND name = ?", userID, idCardHash, name)
|
||||||
|
if excludeID != "" {
|
||||||
|
query = query.Where("id <> ?", excludeID)
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := query.Count(&count).Error; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
api_app "tyapi-server/internal/application/api"
|
||||||
|
"tyapi-server/internal/application/api/dto"
|
||||||
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminQueryWhitelistHandler struct {
|
||||||
|
appService api_app.QueryWhitelistApplicationService
|
||||||
|
responseBuilder interfaces.ResponseBuilder
|
||||||
|
validator interfaces.RequestValidator
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminQueryWhitelistHandler(
|
||||||
|
appService api_app.QueryWhitelistApplicationService,
|
||||||
|
responseBuilder interfaces.ResponseBuilder,
|
||||||
|
validator interfaces.RequestValidator,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) *AdminQueryWhitelistHandler {
|
||||||
|
return &AdminQueryWhitelistHandler{
|
||||||
|
appService: appService,
|
||||||
|
responseBuilder: responseBuilder,
|
||||||
|
validator: validator,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) ListEntries(c *gin.Context) {
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||||
|
|
||||||
|
filters := map[string]interface{}{}
|
||||||
|
if userID := strings.TrimSpace(c.Query("user_id")); userID != "" {
|
||||||
|
filters["user_id"] = userID
|
||||||
|
}
|
||||||
|
if status := strings.TrimSpace(c.Query("status")); status != "" {
|
||||||
|
filters["status"] = status
|
||||||
|
}
|
||||||
|
if apiCode := strings.TrimSpace(c.Query("api_code")); apiCode != "" {
|
||||||
|
filters["api_code"] = apiCode
|
||||||
|
}
|
||||||
|
if idCard := strings.TrimSpace(c.Query("id_card")); idCard != "" {
|
||||||
|
filters["id_card"] = idCard
|
||||||
|
}
|
||||||
|
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
|
||||||
|
filters["keyword"] = keyword
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.appService.ListEntries(c.Request.Context(), filters, interfaces.ListOptions{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取查询白名单列表失败", zap.Error(err))
|
||||||
|
h.responseBuilder.InternalError(c, "获取查询白名单列表失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "获取查询白名单列表成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) GetEntry(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
result, err := h.appService.GetEntry(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取查询白名单详情失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "获取查询白名单详情成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) CreateEntry(c *gin.Context) {
|
||||||
|
var req dto.QueryWhitelistEntryRequest
|
||||||
|
if err := h.validator.BindAndValidate(c, &req); err != nil {
|
||||||
|
h.responseBuilder.BadRequest(c, "参数校验失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminUserID := c.GetString("user_id")
|
||||||
|
result, err := h.appService.CreateEntry(c.Request.Context(), adminUserID, &req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("创建查询白名单失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "创建查询白名单成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) UpdateEntry(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
var req dto.QueryWhitelistEntryUpdateRequest
|
||||||
|
if err := h.validator.BindAndValidate(c, &req); err != nil {
|
||||||
|
h.responseBuilder.BadRequest(c, "参数校验失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminUserID := c.GetString("user_id")
|
||||||
|
result, err := h.appService.UpdateEntry(c.Request.Context(), adminUserID, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("更新查询白名单失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "更新查询白名单成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) UpdateEntryStatus(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
var req dto.QueryWhitelistStatusRequest
|
||||||
|
if err := h.validator.BindAndValidate(c, &req); err != nil {
|
||||||
|
h.responseBuilder.BadRequest(c, "参数校验失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminUserID := c.GetString("user_id")
|
||||||
|
result, err := h.appService.UpdateEntryStatus(c.Request.Context(), adminUserID, id, req.Status)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("更新查询白名单状态失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "更新查询白名单状态成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) DeleteEntry(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
if err := h.appService.DeleteEntry(c.Request.Context(), id); err != nil {
|
||||||
|
h.logger.Error("删除查询白名单失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, nil, "删除查询白名单成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AdminQueryWhitelistHandler) ImportLegacyEntries(c *gin.Context) {
|
||||||
|
adminUserID := c.GetString("user_id")
|
||||||
|
result, err := h.appService.ImportLegacyEntries(c.Request.Context(), adminUserID)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("导入历史硬编码白名单失败", zap.Error(err))
|
||||||
|
h.responseBuilder.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.responseBuilder.Success(c, result, "导入历史硬编码白名单成功")
|
||||||
|
}
|
||||||
@@ -123,11 +123,11 @@ func (h *UserHandler) SendCode(c *gin.Context) {
|
|||||||
|
|
||||||
// 构建SendCodeCommand用于调用应用服务
|
// 构建SendCodeCommand用于调用应用服务
|
||||||
serviceCmd := &commands.SendCodeCommand{
|
serviceCmd := &commands.SendCodeCommand{
|
||||||
Phone: decodedData.Phone,
|
Phone: decodedData.Phone,
|
||||||
Scene: decodedData.Scene,
|
Scene: decodedData.Scene,
|
||||||
Timestamp: decodedData.Timestamp,
|
Timestamp: decodedData.Timestamp,
|
||||||
Nonce: decodedData.Nonce,
|
Nonce: decodedData.Nonce,
|
||||||
Signature: decodedData.Signature,
|
Signature: decodedData.Signature,
|
||||||
CaptchaVerifyParam: cmd.CaptchaVerifyParam,
|
CaptchaVerifyParam: cmd.CaptchaVerifyParam,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,6 @@ func (h *UserHandler) getSignatureErrorMessage(err error) string {
|
|||||||
return "请求验证失败,请重新操作"
|
return "请求验证失败,请重新操作"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Register 用户注册
|
// Register 用户注册
|
||||||
// @Summary 用户注册
|
// @Summary 用户注册
|
||||||
// @Description 使用手机号、密码和验证码进行用户注册,需要确认密码
|
// @Description 使用手机号、密码和验证码进行用户注册,需要确认密码
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tyapi-server/internal/infrastructure/http/handlers"
|
||||||
|
sharedhttp "tyapi-server/internal/shared/http"
|
||||||
|
"tyapi-server/internal/shared/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminQueryWhitelistRoutes struct {
|
||||||
|
handler *handlers.AdminQueryWhitelistHandler
|
||||||
|
admin *middleware.AdminAuthMiddleware
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdminQueryWhitelistRoutes(
|
||||||
|
handler *handlers.AdminQueryWhitelistHandler,
|
||||||
|
admin *middleware.AdminAuthMiddleware,
|
||||||
|
) *AdminQueryWhitelistRoutes {
|
||||||
|
return &AdminQueryWhitelistRoutes{
|
||||||
|
handler: handler,
|
||||||
|
admin: admin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AdminQueryWhitelistRoutes) Register(router *sharedhttp.GinRouter) {
|
||||||
|
engine := router.GetEngine()
|
||||||
|
group := engine.Group("/api/v1/admin/query-whitelist")
|
||||||
|
group.Use(r.admin.Handle())
|
||||||
|
{
|
||||||
|
group.GET("/entries", r.handler.ListEntries)
|
||||||
|
group.GET("/entries/:id", r.handler.GetEntry)
|
||||||
|
group.POST("/entries", r.handler.CreateEntry)
|
||||||
|
group.PUT("/entries/:id", r.handler.UpdateEntry)
|
||||||
|
group.PATCH("/entries/:id/status", r.handler.UpdateEntryStatus)
|
||||||
|
group.DELETE("/entries/:id", r.handler.DeleteEntry)
|
||||||
|
group.POST("/entries/import-legacy", r.handler.ImportLegacyEntries)
|
||||||
|
}
|
||||||
|
}
|
||||||
21
scripts/gen_query_whitelist_hashes/main.go
Normal file
21
scripts/gen_query_whitelist_hashes/main.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/api/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ids := []string{
|
||||||
|
"350681198611130611", "622301200006250550", "320682198910134998", "640102198708020925",
|
||||||
|
"420624197310234034", "350104198501184416", "410521198606018056", "410482198504029333",
|
||||||
|
"370982199012037272", "431027198810290730", "362502199510298017", "340826199008250378",
|
||||||
|
"321027198304072129", "420116198907031413", "13032319930128263X", "350681198412013041",
|
||||||
|
"33072619741031111X",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, id := range ids {
|
||||||
|
fmt.Printf("%s|%s|%s\n", id, services.HashIDCard(id), services.MaskIDCard(id))
|
||||||
|
}
|
||||||
|
}
|
||||||
25
scripts/migrate_query_whitelist.sql
Normal file
25
scripts/migrate_query_whitelist.sql
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
-- 查询白名单迁移脚本(可选,与后台「导入历史硬编码」按钮等效)
|
||||||
|
-- 也可在管理后台 /admin/query-whitelist 点击「导入历史硬编码」完成导入
|
||||||
|
-- name = '*' 表示仅匹配身份证,不校验姓名(兼容历史硬编码逻辑)
|
||||||
|
-- 执行前请确认表已由 AutoMigrate 创建
|
||||||
|
|
||||||
|
INSERT INTO query_whitelist_entries (id, user_id, name, id_card_hash, id_card_masked, api_codes, status, remark, created_at, updated_at)
|
||||||
|
VALUES
|
||||||
|
(gen_random_uuid()::text, '*', '*', '6db054d4cc4f4cadb3e08eaef45c09a9917ffb1c9baadfd9345a78744c4d5d23', '350681********0611', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '4eaa05ddb07ff799b049d52f79a4f4d6453521e4c3bae8fa3dffb3172d241d01', '622301********0550', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '4f930a44bf6246d6d358205d52e18b447e2ae83a808b8710416551dea6217b60', '320682********4998', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '87d466b44e8bc33d25d1ef9654eefa7c2d50a34a5b7c25a221eab3fe6fe7e6a9', '640102********0925', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'c27b2f57a11b37831e799f0bd18526b141a5b90519ff6d790248698b2e527c09', '420624********4034', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'e9f421ce47c3934d6312d7275afa479d263fe788222163bd45981985dba8cdf8', '350104********4416', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '5cbdcee2f32363fbbe6f7e4797285c3249355f6380b2af18c055bef97dd9c46d', '410521********8056', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'e1c8bf84aedd6ebbdeece59545bfe66cd4050373bde74fc161c0fd69034b76a6', '410482********9333', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '43b8ee09dac65311e8a5e9f5701a36d34377eb508c04d1c3695e7ba1ddbe6016', '370982********7272', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '4c041ff4bfcb75eb1fa6f2accefb3d68587f5e626f29536667d5997b3594b9b0', '431027********0730', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'e5220b9160cec7ce5f9d32bfee649e487f4ddc5e9a2b23c7b44dfc7c606bdb76', '362502********8017', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'f064061c4f7f6923bc96871bc7317131ea9c6dd1d8b6c547b6048a7fe7339dd5', '340826********0378', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', '84ed6390cb87b2142b1cf13317b252844e15237fb7317ce4dc927b33d8311e2d', '321027********2129', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'a082da9bc138c2ce91fed5d41c932dc9c5707f47cf3cca7d3169d25d971d3192', '420116********1413', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'a885a75377163280d23504fed43e7a7d3aded3c4deb94c3a83625af6a43092d2', '130323********263X', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'd9a6e6eabbc25f720f08322c82d9e0379143b093879999d2c01fa731ac97b4b9', '350681********3041', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW()),
|
||||||
|
(gen_random_uuid()::text, '*', '*', 'ca36876c94d9ea535bc86bde90ac208a133908e3d9541bdd4dca0559c1109209', '330726********111X', '["*"]', 'enabled', '自硬编码迁移-全局', NOW(), NOW())
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
Reference in New Issue
Block a user